Windows Service Installer for .NET

March 20th, 2012 No comments

Back in my Delphi days the Windows Service template provided a nice feature whereby you could supply command-line arguments to install and uninstall the built service, however the .NET side has always sadly lacked such a feature and usually defers to the installutil.exe which you have to find and call yourself.

Whilst creating a new Windows Service in C# recently I wrote a class which helps alleviate this issue and supports a number of features such as silently installing and uninstalling the service and generating native images using NGEN. You can see the kinds of arguments you can pass below.




The code for this class can be found below. You will need to put the class directly in the service project (i.e where Program.cs is) in order for it to function properly and you must also have a project installer for the service, however if you are already using installutil.exe then this shouldn’t be a problem for you.

using System;
using System.Collections.Generic;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Text;
using System.Windows.Forms;

namespace Storm.API.Logging
{

    public static class ServiceInstaller
    {

        public static bool ProcessArguments(string serviceName, string[] args)
        {
            string parms = String.Concat(args);
            bool silent = false;
            bool ngen = false;
            bool ui = Environment.UserInteractive;

            if (parms.Contains("/silent") || parms.Contains("/s")  || parms.Contains("--silent") || parms.Contains("-s")) silent = true;
            if (parms.Contains("/ngen") || parms.Contains("/n") || parms.Contains("--ngen") || parms.Contains("-n")) ngen = true;

            if (parms.Contains("/install") || parms.Contains("/i") || parms.Contains("--install") || parms.Contains("-i")) {
                try {
                    // Check service isn't already installed
                    if (IsServiceInstalled(serviceName)) {
                        // If interactive, report
                        if (!silent && ui) MessageBox.Show("Service already appears to be installed.","Install Service",MessageBoxButtons.OK,MessageBoxIcon.Warning);

                        // Return
                        return true;
                    }

                    // Install service
                    ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location });

                    // If NGEN, perform generation
                    if (ngen) NativeImage(true);

                    // If interactive, report
                    if (!silent && ui) MessageBox.Show("Service was successfully installed.","Install Service",MessageBoxButtons.OK,MessageBoxIcon.Information);
                } catch (Exception e) {
                    // Show error
                    if (!silent && ui) MessageBox.Show("Service could not be installed due to an exception:\r\n\r\n" + e.Message,"Install Service",MessageBoxButtons.OK,MessageBoxIcon.Error);
                }

                // Return
                return  true;
            } else if (parms.Contains("/uninstall") || parms.Contains("/u") || parms.Contains("--uninstall") || parms.Contains("-u")) {
                try {
                    // Check service isn't already uninstalled
                    if (!IsServiceInstalled(serviceName)) {
                        // If interactive, report
                        if (!silent && ui) MessageBox.Show("Service already appears to be uninstalled.","Uninstall Service",MessageBoxButtons.OK,MessageBoxIcon.Warning);

                        // Return
                        return true;
                    }

                    // Uninstall service
                    ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location });

                    // If NGEN, perform generation
                    if (ngen) NativeImage(false);

                    // If interactive, report
                    if (!silent && ui) MessageBox.Show("Service was successfully uninstalled.","Uninstall Service",MessageBoxButtons.OK,MessageBoxIcon.Information);
                } catch (Exception e) {
                    // Show error
                    if (!silent && ui) MessageBox.Show("Service could not be uninstalled due to an exception:\r\n\r\n" + e.Message,"Uninstall Service",MessageBoxButtons.OK,MessageBoxIcon.Error);
                }

                // Return
                return true;
            } else if (parms.Contains("/?") || parms.Contains("/help") || parms.Contains("--help") || parms.Contains("-h")) {
                // Get service filename
                string filename = Path.GetFileName(Assembly.GetExecutingAssembly().Location);

                // Show help
                if (!silent && ui) {
                    StringBuilder builder = new StringBuilder();

                    builder.AppendLine("Usage: " + filename + " [options]");
                    builder.AppendLine();
                    builder.AppendLine("/install, /i, --install, -i");
                    builder.AppendLine("    Installs the service onto the current computer.");
                    builder.AppendLine();
                    builder.AppendLine("/uninstall, /u, --uninstall, -u");
                    builder.AppendLine("    Uninstalls the server from the current computer.");
                    builder.AppendLine();
                    builder.AppendLine("/silent, /s, --silent, -s");
                    builder.AppendLine("    Marks the install/uninstall process as silent, no message dialogs will be displayed.");
                    builder.AppendLine("    Must be used in combination with the install or uninstall switches.");
                    builder.AppendLine();
                    builder.AppendLine("/ngen, /n, --ngen, -n");
                    builder.AppendLine("    Instructs the .NET Framework to generate a native image using NGEN.");
                    builder.AppendLine("    Must be used in combination with the install or uninstall switches.");
                    builder.AppendLine();
                    builder.AppendLine("/help, /?, --help, -h");
                    builder.AppendLine("    Displays this help and usage dialog.");
                    builder.AppendLine();

                    MessageBox.Show(builder.ToString(),"Service Help",MessageBoxButtons.OK,MessageBoxIcon.Information);
                }

                // Return
                return true;
            } else {
                // Return
                return false;
            }
        }

        private static bool IsServiceInstalled(string serviceName)
        {
            // Get a list of current services
            ServiceController[] services = ServiceController.GetServices();

            // Look for our service
            foreach(ServiceController service in services) {
                if (String.Compare(serviceName,service.ServiceName,true) == 0) return true;
            }

            // Return
            return false;
        }

        private static void NativeImage(bool install)
        {
            // Generate required filenames
            string service_filename = Assembly.GetExecutingAssembly().Location;
	        string runtime_dir = RuntimeEnvironment.GetRuntimeDirectory();
	        string ngen_filename = Path.Combine(runtime_dir,"ngen.exe");

            // Work out arguments
            string args = String.Format("{0} \"{1}\"",(install ? "install" : "uninstall"),service_filename);

            // Create process
            Process process = new Process();

            process.StartInfo.FileName = ngen_filename;
            process.StartInfo.Arguments = args;
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

            // Start process and wait for it to complete
            process.Start();
            process.WaitForExit();
        }

    }

}

After you have done this using the class is very straightforward, you simple call one static method, ProcessArguments and pass in the arguments supplied to the service. An example of usage can be seen below, taken directly from my service.

        static void Main(string[] args)
        {
            // Process any arguments
            if (ServiceInstaller.ProcessArguments(SERVICE_NAME,args)) return;

            // Run the service
            ServiceBase.Run(new Service());
        }

I hope you will find this useful, I’m open to suggestions for improvements and changes so please feel free to contact me. I’ve included a direct link to the source code below to make things easier for you.

svcinst.zip
2 KB
Categories: C#, Services, Windows, Windows Forms Tags:

NGINX Service for Windows

November 14th, 2011 No comments

Recently I had use for NGINX on Windows, if you don’t know what that is then check out articles here and here. Thankfully there was a Windows build available and I got that up and running nominally in no time.

However, sadly as per usual with a lot of stuff coming from UNIX, support for the way Windows does things such as services was non existent, so I had to put my programming skills to use and have made a little Windows service wrapper for it. I’m providing binaries and source free in the hope that others may find this useful, you can download them here or you can find them on my Github.

It was wrote using Visual Studio 2010 and targets the .NET 2.0 framework, which does mean you need .NET installed, but that’s pretty much a given on most modern installs of Windows thesedays.

nginx-svc-bin.zip
5 KB
nginx-svc-src.zip
16 KB
Categories: Caching, Github, HTTP, NGINX, Web Development, Windows Tags:

New web site!

October 14th, 2011 No comments

Announcing a new website!



For some time now I’ve had the pastepal.net domain with the intent to write my own pastebin. However due to time constraints this never came to fruition and the domain just sat there wasting away.

Thankfully an enterprising individual called Sayak Banerjee has wrote a free open source pastebin system in PHP which has allowed me to move ahead with the domain and pastebin.

Thus, I can now introduce to you PastePal. It’s pretty much fully functional thanks to Sayak’s hard work and some of my own custom modifications so feel free to check it out and use it.

Click on the image above or click here to go there now.


Categories: Hosting, Web Development Tags:

Kilimanjaro 2011 Success!

October 8th, 2011 No comments

Kilimanjaro Summit

Well I made it, my official summit date and time was 7:58am in the morning, having set off at 11:30pm the night before!

It was definately an experience and I have more expeditions lined up.

Categories: Charity, Climbing, Uncategorized Tags:

Kilimanjaro 2011

July 20th, 2011 No comments

Haven’t posted in a while, working on several projects in my spare time and have a full time day job that keeps me busy! I hopefully have something special coming for people late this autumn!

Kilimanjaro

Before then in late September I’m off climbing Kilimanjaro in Africa! I’d be much obliged if you would donate to the charity I’m doing this in aid of, Parkinsons UK which does research and provides help for people with this still incurable and debilitating condition.

You can find my charity page at here: http://www.justgiving.com/lloydk

Categories: Charity, Climbing Tags:

Back! SharpAuctioneer and more…

June 13th, 2011 No comments

I haven’t posted for a while, been busy with work and finishing my degree at university, but that’s done now and I suddenly have more free time to waste it seems.

To that end, I’ve been playing around with World of Warcraft and the Auctioneer add-on recently and a way to read the scan data it collects in my applications. The result was SharpAuctioneer – http://sharpauctioneer.codeplex.com/

Categories: C#, Interop, World of Warcraft Tags:

AllocateHWND for .NET

February 8th, 2011 No comments

Back in my Delphi days there was a useful set of functions in the RTL that allowed you to create a hidden window with which to deal with Windows messages without the need for a windowed component, such as one derived from TWinControl. These functions were AllocateHWnd and DeallocateHWnd.

However, moving on to .NET and Winforms I couldn’t find a simple or straightforward way to do this using the existing framework controls so set about last evening creating my own implementation, the result was a new static class called MessageWindow.

MessageWindow uses a lot of P/Invoke calls unfortunately and is thoroughly non-portable but then, if you have to intercept Windows messages you’re most likely going to be on Windows.

Disregarding non-public members the MessageWindow class exposed looks something like this:

    public static class MessageWindow
    {

        public static MessageWindowInstance Create(MessageWindowProc windowProc);
        public static void Destroy(MessageWindowInstance windowInstance);

    }

There are two simple calls, Create and Destroy which allow you to create a window for messaging and then once you’re done, destroy it. In-fact, I go one step further here, because Create returns a MessageWindowInstance, which represents our window you can actually just call MessageWindowInstance.Dispose() and that will release the window as well.

The MessageWindowInstance class looks something like this:

    public class MessageWindowInstance : IDisposable
    {

        public void Dispose();

        public IntPtr Handle
        {
            get;
        }

    }

Like AllocateHWND which expected a TWndMethod, our method requires you pass in a delegate, MessageWindowProc which follows the same pattern as the framework’s message handlers:

public delegate void MessageWindowProc(ref Message msg);

So putting it all together you first create your window proc:

private static void WindowProc(ref Message msg)
{
    // Handle message here
}

Next, you create your window:

MessageWindowInstance window = MessageWindow.Create(new MessageWindowProc(WindowProc));

And finally, once you’re done, you release your window:

// First way
MessageWindow.Destroy(window);

// Second way
window.Dispose();

Hopefully this will work as well as the Delphi versions, I’m currently using it in a clipboard chain. The code is available on my Github or you can download the source and binaries directly from here.

msgwin.zip
64 KB
Categories: C#, Delphi, Interop, Windows Forms Tags:

Now available on Github!

February 8th, 2011 No comments

I’ve finally decided to be like all the other cool kids and signed myself up to Github, I’ll host the majority of my free code on there from now on.

You can find my Github at: https://github.com/lkinsella

I’ve also added a Github icon under the search box at the top right along with the usual RSS and Twitter icons.

Categories: Github Tags:

New blog!

January 21st, 2011 1 comment

Finally! A new host and new blog.

I’ve imported my old posts but some parts may be broke etc, feel free to mail me (webmaster@lloydkinsella.net) if you find any issues.

Categories: Uncategorized Tags:

Website Move

January 20th, 2011 No comments

At some point very soon this blog will disappear, I’ve finally had enough of Webhost4Life and bought myself a VPS so I’ll be moving content over to a new environment over the next month or so.

I also hope to re-design this blog a little bit, the current theme is getting old now and it’s time for a change. I’ll keep you posted!

Categories: Uncategorized Tags: