Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Serviceable console application (merge console program and Windows service into one program)

0.00/5 (No votes)
1 Jun 2012 1  
One console program, which supports running as both a console program and a Windows service.
  • Download source - 4.4 KB (executable program compiled with .NET Framework 4.0, please compile<command: csc.exe Coc.CodeOfCooper.ServiceableConsoleProgramDemo.cs> with source code if you are using lower version)

Background

Generally, when developing a Windows service program, we may like to also include a console program for testing or debugging. Because we can't directly debug a service, we have to run a service first, then try to attach the service process quickly, otherwise we may miss the break point after we attach it, or (the third way) we have to write helpful code which allows us to attach a slower and catch up process go to the break point, we usually add, e.g., Thread.Sleep(10000). This article will introduce a different way to handle this problem. We merge the console program and service program together, and it is quiet easy.

Demo

In the above demo, I have shown two usages: the first one is a console program, another one is a Windows service, which can be created and deleted by sc.exe. sc.exe is a Windows system program, more info please check out here: http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/sc.mspx?mfr=true, . there is another way to created and deleted windows service by .NET Framework tool(v2.0 above), which is InstallUtil.exe(http://msdn.microsoft.com/en-us/library/50614e95(v=vs.80).aspx), our Demo program doesn't support InstallUtil yet, because we didn't include any Installer, and Installer is required to install by InstallUtil.exe, we need to add a Installer for support InstallUtil.

Using the code

Very easy to implement, detaled steps are following:

  1. Download attached ZIP file, and unzip file to somewhere, e.g. c:\work
  2. Rewrite or replace the business logic class: ComponentRunner
  3. Rewrite or replace the service class: MainService, in demo, the service just run one time, and doesn't run anymore, its behave doesn't a service, you need to rewrite or replace it to make your service running always...

Explanation of Source Code

AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);  
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{ 
  Console.WriteLine("CurrentDomain_UnhandledException " + e.ExceptionObject.ToString());
}

It's a good idea to handle all unhandled exceptions here, once somewhere we forget to catch the exception, we have a final place to jot something.

Process parentProcess = ParentProcessUtilities.GetParentProcess();

// Windows service host program is "services.exe", can get this from Process Explorer
//   Please download Process Explorer here: http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx
bool runFromService = (parentProcess != null &&
   string.Compare(parentProcess.ProcessName, "services", true) == 0);

Console.WriteLine("started from service? {0}", runFromService);

if (runFromService)
{
   Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;
   ServiceBase.Run(new ServiceBase[] { new MainService() });
}
else
{
   new ComponentRunner().Run();
   Console.WriteLine("\r\nPress any key to quit(still running)...");
   Console.ReadLine();
}

We get the parent process of the current process by a utility of the process ParentProcessUtilities, then check is its process name equals 'services'. This is a Windows system process, it manages all service processes, you can see it and its subordinates from the below image:

This image is captured from the tool Process Explorer which is a useful and must have tool for Windows programmers. You can get more information here: http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx. The next code shows the ParentProcessUtilities class.

[StructLayout(LayoutKind.Sequential)]
public struct ParentProcessUtilities
{
    // These members must match PROCESS_BASIC_INFORMATION
    internal IntPtr Reserved1;
    internal IntPtr PebBaseAddress;
    internal IntPtr Reserved2_0;
    internal IntPtr Reserved2_1;
    internal IntPtr UniqueProcessId;
    internal IntPtr InheritedFromUniqueProcessId;

    [DllImport("ntdll.dll")]
    private static extern int NtQueryInformationProcess(IntPtr processHandle, 
        int processInformationClass, 
        ref ParentProcessUtilities processInformation, 
        int processInformationLength, 
        out int returnLength);

    /// <summary>
    /// Gets the parent process of the current process.
    /// </summary>
    /// <returns>An instance of the Process class.</returns>
    public static Process GetParentProcess()
    {
        return GetParentProcess(Process.GetCurrentProcess().Handle);
    }

    /// <summary>
    /// Gets the parent process of specified process.
    /// </summary>
    /// <param name="id">The process id.</param>
    /// <returns>An instance of the Process class.</returns>
    public static Process GetParentProcess(int id)
    {
        Process process = Process.GetProcessById(id);
        return GetParentProcess(process.Handle);
    }

    /// <summary>
    /// Gets the parent process of a specified process.
    /// </summary>
    /// <param name="handle">The process handle.</param>
    /// <returns>An instance of the Process class.</returns>
    public static Process GetParentProcess(IntPtr handle)
    {
        ParentProcessUtilities pbi = new ParentProcessUtilities();
        int returnLength;
        int status = NtQueryInformationProcess(handle, 0, ref pbi, 
                           Marshal.SizeOf(pbi), out returnLength);
        if (status != 0)
            throw new Win32Exception(status);

        try
        {
            return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
        }
        catch (ArgumentException)
        {
            // not found
            return null;
        }
    }
}

This code use the Windows API NtQueryInformationProcess to obtain the parent process. This code was referenced from internet, http://stackoverflow.com/questions/394816/how-to-get-parent-process-in-net-in-managed-way, this page also includes a managed way to get the parent process, but it fails when working from a service.

Enjoy the code.

History

Version 0.1, created on 2012-06-01.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here