Convert a C++ Program to a Windows Service

Converting a c# commandline app to a Windows service

To run a console app as either a Windows Service or a Console application, write a single console application and use command line arguments to determine if you should run directly or start up the service. Include an installer/uninstaller to install as a windows service with the right command line arguments.

Here's a base class we use that provides this functionality.

using System;
using System.Collections;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.ServiceProcess;
using System.Windows.Forms;
using Microsoft.Win32;

namespace Console40
{
public abstract class AbstractService : ServiceBase
{
public static AbstractService Current { get; private set; }

protected virtual string HelpTextPattern
{
get
{
#region Help Text

return
@"
USAGE

{0} [command]

WHERE [command] is one of

/console - run as a console application, for debugging
/service - run as a windows service
/install - install as a windows service
/uninstall - uninstall windows service

";

#endregion
}
}

public abstract string DisplayName { get; }

public ServiceExecutionMode ServiceExecutionMode { get; private set; }

protected abstract Guid UninstallGuid { get; }

protected virtual string UninstallRegKeyPath
{
get
{
return @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
}
}

protected AbstractService(string serviceName)
{
ServiceName = serviceName;
if (Current != null)
{
throw new InvalidOperationException(String.Format(
"Service {0} is instantiating but service {1} is already instantiated as current. References to AbstractService.Current will only point to the first service.",
GetType().FullName,
Current.GetType().FullName));
}
Current = this;
}

public void Run(string[] args)
{
Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;

if (args.Length == 0 && Debugger.IsAttached)
{
args = new[] { "/console" };
}

if (args.Length == 0)
{
Console.WriteLine(HelpTextPattern, Path.GetFileName(GetType().Assembly.CodeBase));
}
else
{
switch (args[0].ToLower())
{
case "/service":
ServiceExecutionMode = ServiceExecutionMode.Service;
Run(new[] { this });
break;

case "/console":
ServiceExecutionMode = ServiceExecutionMode.Console;
Console.WriteLine("Starting Service...");
OnStart(new string[0]);
OnStartCommandLine();
OnStop();
break;

case "/install":
ServiceExecutionMode = ServiceExecutionMode.Install;
InstallService();
break;

case "/uninstall":
ServiceExecutionMode = ServiceExecutionMode.Uninstall;
UninstallService();
break;

case "/uninstallprompt":
ServiceExecutionMode = ServiceExecutionMode.Uninstall;
if (ConfirmUninstall())
{
UninstallService();
InformUninstalled();
}
break;

default:
if (!OnCustomCommandLine(args))
{
Console.WriteLine(HelpTextPattern, Path.GetFileName(GetType().Assembly.CodeBase));
}
break;
}
}
}

protected override void OnStart(string[] args)
{
OnStartImpl(args);

AppDomain.CurrentDomain.UnhandledException += OnCurrentDomainUnhandledException;
}

protected virtual void OnStartCommandLine()
{
Console.WriteLine("Service is running... Hit ENTER to break.");
Console.ReadLine();
}

protected abstract void OnStartImpl(string[] args);

void OnCurrentDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
// do something useful here, log it..
}

protected override void OnShutdown()
{
Stop();
}

protected override void OnStop()
{
OnStopImpl();
}

protected abstract void OnStopImpl();

protected virtual bool OnCustomCommandLine(string[] args)
{
// for extension
return false;
}

private void InstallService()
{
GetInstaller(".InstallLog").Install(new Hashtable());
InstallServiceCommandLine();
CreateUninstaller();
}

private void InstallServiceCommandLine()
{
string keyParent = @"SYSTEM\CurrentControlSet\Services\" + ServiceName;
const string VALUE_NAME = "ImagePath";

try
{
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(keyParent, true))
{
if (key == null)
{
throw new InvalidOperationException("Service not found in registry.");
}

var origPath = key.GetValue(VALUE_NAME) as string;
if (origPath == null)
{
throw new Exception("HKLM\\" + keyParent + "\\" + VALUE_NAME + " does not exist but was expected.");
}

key.SetValue(VALUE_NAME, origPath.Replace("\"\"", "\"") + " /service");
}
}
catch (Exception ex)
{
throw new Exception(
"Error updating service command line after installation. Unable to write to HKLM\\" + keyParent, ex);
}
}

private void CreateUninstaller()
{
using (RegistryKey parent = Registry.LocalMachine.OpenSubKey(UninstallRegKeyPath, true))
{
if (parent == null)
{
throw new Exception(String.Format("Uninstall registry key '{0}' not found.", UninstallRegKeyPath));
}
try
{
RegistryKey key = null;

try
{
string guidText = UninstallGuid.ToString("B");
key = parent.OpenSubKey(guidText, true) ??
parent.CreateSubKey(guidText);

if (key == null)
{
throw new Exception(String.Format("Unable to create uninstaller '{0}\\{1}'", UninstallRegKeyPath, guidText));
}

Assembly asm = GetType().Assembly;
Version v = asm.GetName().Version;
string exe = "\"" + asm.CodeBase.Substring(8).Replace("/", "\\\\") + "\"";

key.SetValue("DisplayName", DisplayName);
key.SetValue("ApplicationVersion", v.ToString());
key.SetValue("Publisher", "B-Line Medical");
key.SetValue("DisplayIcon", exe);
key.SetValue("DisplayVersion", v.ToString(2));
key.SetValue("URLInfoAbout", "http://www.blinemedical.com");
key.SetValue("Contact", "support@blinemedical.com");
key.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
key.SetValue("UninstallString", exe + " /uninstallprompt");
}
finally
{
if (key != null)
{
key.Close();
}
}
}
catch (Exception ex)
{
throw new Exception(
"An error occurred writing uninstall information to the registry. The service is fully installed but can only be uninstalled manually through the command line.",
ex);
}
}
}

private bool ConfirmUninstall()
{
string title = "Uninstall " + DisplayName;
string text = "Are you sure you want to remove " + DisplayName + " from your computer?";
return DialogResult.Yes ==
MessageBox.Show(text, title, MessageBoxButtons.YesNo, MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2);
}

private void InformUninstalled()
{
string title = "Uninstall " + DisplayName;
string text = DisplayName + " has been uninstalled.";
MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Information);
}

private void UninstallService()
{
GetInstaller(".UninstallLog").Uninstall(null);
RemoveUninstaller();
}

private TransactedInstaller GetInstaller(string logExtension)
{
var ti = new TransactedInstaller();

ti.Installers.Add(new ServiceProcessInstaller
{
Account = ServiceAccount.LocalSystem
});

ti.Installers.Add(new ServiceInstaller
{
DisplayName = DisplayName,
ServiceName = ServiceName,
StartType = ServiceStartMode.Automatic
});

string basePath = Assembly.GetEntryAssembly().Location;
String path = String.Format("/assemblypath=\"{0}\"", basePath);
ti.Context = new InstallContext(Path.ChangeExtension(basePath, logExtension), new[] { path });

return ti;
}

private void RemoveUninstaller()
{
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(UninstallRegKeyPath, true))
{
if (key == null)
{
return;
}
try
{
string guidText = UninstallGuid.ToString("B");
RegistryKey child = key.OpenSubKey(guidText);
if (child != null)
{
child.Close();
key.DeleteSubKey(guidText);
}
}
catch (Exception ex)
{
throw new Exception(
"An error occurred removing uninstall information from the registry. The service was uninstalled will still show up in the add/remove program list. To remove it manually delete the entry HKLM\\" +
UninstallRegKeyPath + "\\" + UninstallGuid, ex);
}
}
}
}

public enum ServiceExecutionMode
{
Unknown,
Service,
Console,
Install,
Uninstall,
Custom
}
}

Convert a C++ program to a Windows service?

There's a good example on how to set up a minimal service on MSDN. See the parts about writing the main function, entry point and also the example code.

Once you've got a windows service built and running, you'll discover the next major gotcha: it's a pain to debug. There's no terminal (and hence no stdout/stderr) and as soon as you try to run the executable it actually launches the service then returns to you.

One trick I've found very useful is to add a -foreground option to your app so that if you run with that flag then it bypasses the service starter code and instead runs like a regular console app, which makes it vastly easier to debug. In VS.Net set up the debugging options to invoke with that flag.

Converting my application in a Windows Service

This is a useful walkthrough http://msdn.microsoft.com/en-us/library/zt39148a(v=vs.100).aspx

You could either build UI into the service, or have another app that talks to the service e.g. using WCF.

This should also help, Making an existing exe with gui into windows service but as you've already broken your projects up your halfway there.

C# Converting Console App to Service

The MSDN documentation on Windows Services is really good and has everything you need to get started.

The problem you're having is because of your OnStart implementation, that's only supposed to be used to set up the service so it's ready to start, the method must return promptly. Usually you'd run the bulk of the code on another thread or in a timer. See the page for OnStart for confirmation.

Edit:
Without knowing what your windows service will do, it's hard to tell you how to implement it but let's say you wanted to run a method every 10 seconds while the service is running:

public partial class Service1 : ServiceBase
{
private System.Timers.Timer _timer;

public Service1()
{
InitializeComponent();
}

protected override void OnStart(string[] args)
{
#if DEBUG
System.Diagnostics.Debugger.Launch(); // This will automatically prompt to attach the debugger if you are in Debug configuration
#endif

_timer = new System.Timers.Timer(10 * 1000); //10 seconds
_timer.Elapsed += TimerOnElapsed;
_timer.Start();
}

private void TimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
{
// Call to run off to a database or do some processing
}

protected override void OnStop()
{
_timer.Stop();
_timer.Elapsed -= TimerOnElapsed;
}
}

Here, the OnStart method returns immediately after setting up the timer and TimerOnElapsed will be run on a worker thread. I also added a call to System.Diagnostics.Debugger.Launch(); which will make debugging alot easier.

If you have some other requirements, please edit your question or post a comment.

Convert console application to windows service

How are you running the installation? I'd wager a guess that you're registering a 32-bit compiled application with the 64 bit installer or vice versa

In other words, running:

%windir%\Microsoft.NET\Framework\v4.0.30319\installutil.exe <My x64 DLL/EXE>

or:

%windir%\Microsoft.NET\Framework64\v4.0.30319\installutil.exe <My x86 DLL/EXE>


Related Topics



Leave a reply



Submit