Click here to Skip to main content
Click here to Skip to main content

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

, 1 Jun 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

CooperWu
Software Developer (Senior) Thomson Reuters
China China
I started to programming in 2002, started when I was grade 2 in university, I participated some part-time projects at that time, I have much experiences on Windows, includes C#, ASP.NET, Visual Basic, Visual C++, AJAX, Power Shell Script, JavaScript, XML..etc, I am learning design and architect.

Comments and Discussions

 
QuestionrunFromService vs Environment.UserInteractive PinmemberEtienne Divina1-Jun-12 22:10 
AnswerRe: runFromService vs Environment.UserInteractive PinmemberCooperWu2-Jun-12 5:24 
QuestionI use hte topshelf api to develop services Pinmemberxoox1-Jun-12 6:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150224.1 | Last Updated 1 Jun 2012
Article Copyright 2012 by CooperWu
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid