{"id":560,"date":"2011-09-29T12:42:52","date_gmt":"2011-09-29T10:42:52","guid":{"rendered":"http:\/\/hen.co.za\/blog\/?p=560"},"modified":"2012-12-07T15:06:55","modified_gmt":"2012-12-07T13:06:55","slug":"creating-a-self-installing-windows-service","status":"publish","type":"post","link":"https:\/\/hen.co.za\/blog\/2011\/09\/creating-a-self-installing-windows-service\/","title":{"rendered":"Creating a self installing Windows Service"},"content":{"rendered":"<p>Being in the business of creating several Windows Services myself (EventScavenger, Event Collector, QuickMon) I started thinking about how to make the whole install and set up process a bit easier and eliminating the manual steps to install a Windows Service.<\/p>\n<p>So what if you can simply run the service &#8216;exe&#8217; with a &#8220;-install&#8221; or &#8220;-uninstall&#8221; parameter and let it do the work for you? Additionally, what if it it can even prompt you for start up type and user account details during the process &#8211; all of this without even using installutil&#8230;?<\/p>\n<p>Well after some research, testing, trail and error, losing some more hair and eventually finding a way to make it work I now have a way to do all this. Nice thing is it can be used together with an MSI installer as well or even an external app or script.<\/p>\n<p><strong>ProjectInstaller<\/strong><\/p>\n<p>The trick is to create a custom ProjectInstaller class (not use the wizards that comes with VS to add installers). I&#8217;ve added some properties to this class to specify things like Service name, display name, start up type, account type, user name and password.<\/p>\n<blockquote><p>[<span style=\"color: #0000ff;\">RunInstaller<\/span>(<span style=\"color: #0000ff;\">true<\/span>)]<br \/>\n<span style=\"color: #0000ff;\">public class<\/span> ProjectInstaller : <span style=\"color: #0000ff;\">Installer<\/span><br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">private ServiceProcessInstaller<\/span> processInstaller;<br \/>\n<span style=\"color: #0000ff;\">private<\/span> System.ServiceProcess.<span style=\"color: #0000ff;\">ServiceInstaller<\/span> serviceInstaller;<\/p>\n<p style=\"padding-left: 30px;\">#region Added properties<br \/>\n<span style=\"color: #0000ff;\">public string<\/span> ServiceName<br \/>\n{<br \/>\n<span style=\"color: #0000ff;\">get<\/span> { <span style=\"color: #0000ff;\">return<\/span> serviceInstaller.ServiceName; }<br \/>\n<span style=\"color: #0000ff;\">set<\/span> { serviceInstaller.ServiceName = <span style=\"color: #0000ff;\">value<\/span>; }<br \/>\n}<br \/>\n<span style=\"color: #0000ff;\">public string<\/span> DisplayName<br \/>\n{<br \/>\n<span style=\"color: #0000ff;\">get<\/span> { <span style=\"color: #0000ff;\">return<\/span> serviceInstaller.DisplayName; }<br \/>\n<span style=\"color: #0000ff;\">set<\/span> { serviceInstaller.DisplayName = <span style=\"color: #0000ff;\">value<\/span>; }<br \/>\n}<br \/>\n<span style=\"color: #0000ff;\">public string<\/span> Description<br \/>\n{<br \/>\n<span style=\"color: #0000ff;\">get<\/span> { <span style=\"color: #0000ff;\">return<\/span> serviceInstaller.Description; }<br \/>\n<span style=\"color: #0000ff;\">set<\/span> { serviceInstaller.Description = <span style=\"color: #0000ff;\">value<\/span>; }<br \/>\n}<br \/>\n<span style=\"color: #0000ff;\">public ServiceStartMode<\/span> StartType<br \/>\n{<br \/>\n<span style=\"color: #0000ff;\">get<\/span> { <span style=\"color: #0000ff;\">return<\/span> serviceInstaller.StartType; }<br \/>\n<span style=\"color: #0000ff;\">set<\/span> { serviceInstaller.StartType = <span style=\"color: #0000ff;\">value<\/span>; }<br \/>\n}<br \/>\n<span style=\"color: #0000ff;\">public ServiceAccount<\/span> Account<br \/>\n{<br \/>\n<span style=\"color: #0000ff;\">get<\/span> { <span style=\"color: #0000ff;\">return<\/span> processInstaller.Account; }<br \/>\n<span style=\"color: #0000ff;\">set<\/span> { processInstaller.Account = <span style=\"color: #0000ff;\">value<\/span>; }<br \/>\n}<br \/>\n<span style=\"color: #0000ff;\">public string<\/span> ServiceUsername<br \/>\n{<br \/>\n<span style=\"color: #0000ff;\">get<\/span> { <span style=\"color: #0000ff;\">return<\/span> processInstaller.Username; }<br \/>\n<span style=\"color: #0000ff;\">set<\/span> { processInstaller.Username = <span style=\"color: #0000ff;\">value<\/span>; }<br \/>\n}<br \/>\n<span style=\"color: #0000ff;\">public string<\/span> ServicePassword<br \/>\n{<br \/>\n<span style=\"color: #0000ff;\">get<\/span> { <span style=\"color: #0000ff;\">return<\/span> processInstaller.Password; }<br \/>\n<span style=\"color: #0000ff;\">set<\/span> { processInstaller.Password = <span style=\"color: #0000ff;\">value<\/span>; }<br \/>\n}<br \/>\n#endregion<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">public<\/span> ProjectInstaller()<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\">processInstaller = new <span style=\"color: #0000ff;\">ServiceProcessInstaller<\/span>();<br \/>\nserviceInstaller = new System.ServiceProcess.<span style=\"color: #0000ff;\">ServiceInstaller<\/span>();<\/p>\n<p style=\"padding-left: 60px;\">Installers.AddRange(<span style=\"color: #0000ff;\">new<\/span> Installer[] {<br \/>\nprocessInstaller,<br \/>\nserviceInstaller});<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">protected override void<\/span> OnBeforeInstall(System.Collections.<span style=\"color: #0000ff;\">IDictionary<\/span> savedState)<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\">SetContextParameter(&#8220;name&#8221;, ServiceName);<br \/>\nSetContextParameter(&#8220;display&#8221;, DisplayName);<br \/>\nSetContextParameter(&#8220;desc&#8221;, Description);<br \/>\nSetContextParameter(&#8220;start&#8221;, StartType.ToString());<br \/>\nSetContextParameter(&#8220;account&#8221;, Account.ToString());<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">if<\/span> (Account == <span style=\"color: #0000ff;\">ServiceAccount<\/span>.User)<br \/>\n{<\/p>\n<p style=\"padding-left: 90px;\">SetContextParameter(&#8220;user&#8221;, ServiceUsername);<br \/>\nSetContextParameter(&#8220;password&#8221;, ServicePassword);<\/p>\n<p style=\"padding-left: 60px;\">}<br \/>\n<span style=\"color: #0000ff;\">base<\/span>.OnBeforeInstall(savedState);<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">public void<\/span> SetContextParameter(<span style=\"color: #0000ff;\">string<\/span> key, <span style=\"color: #0000ff;\">string<\/span> value)<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">if<\/span> (!<span style=\"color: #0000ff;\">this<\/span>.Context.Parameters.ContainsKey(key))<\/p>\n<p style=\"padding-left: 90px;\"><span style=\"color: #0000ff;\">this<\/span>.Context.Parameters.Add(key, value);<\/p>\n<p style=\"padding-left: 60px;\">else<\/p>\n<p style=\"padding-left: 90px;\"><span style=\"color: #0000ff;\">this<\/span>.Context.Parameters[key] = value;<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p>}<\/p><\/blockquote>\n<p><strong>Calling the Installer\/uninstaller<\/strong><\/p>\n<p>In order to get the service executable to handle the -&#8220;install&#8221; or &#8220;-uninstall&#8221; parameters I modified the Main method in Program.cs to accept a string parameter array and then call the appropriate method if either of those two parameters are passed.<\/p>\n<p>Example code:<\/p>\n<blockquote><p><span style=\"color: #0000ff;\">static class<\/span> Program<br \/>\n{<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">static void<\/span> Main(<span style=\"color: #0000ff;\">string<\/span>[] args)<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">if<\/span> (args.Length &gt; 0)<br \/>\n{<\/p>\n<p style=\"padding-left: 90px;\"><span style=\"color: #0000ff;\">if<\/span> (args[0].ToUpper() == &#8220;-INSTALL&#8221;)<br \/>\n{<\/p>\n<p style=\"padding-left: 120px;\">InstallService();<br \/>\n<span style=\"color: #0000ff;\">return<\/span>;<\/p>\n<p style=\"padding-left: 90px;\">}<br \/>\n<span style=\"color: #0000ff;\">else if<\/span> (args[0].ToUpper() == &#8220;-UNINSTALL&#8221;)<br \/>\n{<\/p>\n<p style=\"padding-left: 120px;\">UnInstallService();<br \/>\n<span style=\"color: #0000ff;\">return<\/span>;<\/p>\n<p style=\"padding-left: 90px;\">}<\/p>\n<p style=\"padding-left: 60px;\">}<br \/>\n<span style=\"color: #0000ff;\">ServiceBase<\/span>[] ServicesToRun;<br \/>\nServicesToRun = new <span style=\"color: #0000ff;\">ServiceBase<\/span>[] { new <span style=\"color: #0000ff;\">SelfInstallService<\/span>() };<br \/>\nServiceBase.Run(ServicesToRun);<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">private static bool<\/span> InstallService()<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">bool<\/span> success = <span style=\"color: #0000ff;\">false<\/span>;<br \/>\n<span style=\"color: #0000ff;\">try<\/span><br \/>\n{<\/p>\n<p style=\"padding-left: 90px;\"><span style=\"color: #0000ff;\">string<\/span> exeFullPath = System.Reflection.<span style=\"color: #0000ff;\">Assembly<\/span>.GetExecutingAssembly().Location;<br \/>\n<span style=\"color: #0000ff;\">string<\/span> workingPath = System.IO.<span style=\"color: #0000ff;\">Path<\/span>.GetDirectoryName(exeFullPath);<br \/>\n<span style=\"color: #0000ff;\">string<\/span> logPath = System.IO.<span style=\"color: #0000ff;\">Path<\/span>.Combine(workingPath, &#8220;Install.log&#8221;);<br \/>\n<span style=\"color: #0000ff;\">ServiceStartMode<\/span> startmode = <span style=\"color: #0000ff;\">ServiceStartMode<\/span>.Automatic;<br \/>\n<span style=\"color: #0000ff;\">ServiceAccount<\/span> account = <span style=\"color: #0000ff;\">ServiceAccount<\/span>.LocalService;<br \/>\n<span style=\"color: #0000ff;\">string<\/span> username = &#8220;&#8221;;<br \/>\n<span style=\"color: #0000ff;\">string<\/span> password = &#8220;&#8221;;<\/p>\n<p style=\"padding-left: 90px;\"><span style=\"color: #0000ff;\">InstallerForm<\/span> installerForm = new <span style=\"color: #0000ff;\">InstallerForm<\/span>();<br \/>\ninstallerForm.StartType = <span style=\"color: #0000ff;\">ServiceStartMode<\/span>.Automatic;<br \/>\ninstallerForm.AccountType = <span style=\"color: #0000ff;\">ServiceAccount<\/span>.User;<br \/>\n<span style=\"color: #0000ff;\">if<\/span> (installerForm.ShowDialog() == System.Windows.Forms.<span style=\"color: #0000ff;\">DialogResult<\/span>.OK)<br \/>\n{<\/p>\n<p style=\"padding-left: 120px;\">startmode = installerForm.StartType;<br \/>\naccount = installerForm.AccountType;<br \/>\n<span style=\"color: #0000ff;\">if<\/span> (installerForm.AccountType == <span style=\"color: #0000ff;\">ServiceAccount<\/span>.User)<br \/>\n{<\/p>\n<p style=\"padding-left: 150px;\">username = installerForm.UserName;<br \/>\npassword = installerForm.Password;<\/p>\n<p style=\"padding-left: 120px;\">}<\/p>\n<p style=\"padding-left: 90px;\">}<\/p>\n<p style=\"padding-left: 90px;\"><span style=\"color: #0000ff;\">Hashtable<\/span> savedState = <span style=\"color: #0000ff;\">new Hashtable<\/span>();<br \/>\n<span style=\"color: #0000ff;\">ProjectInstaller<\/span> myProjectInstaller = <span style=\"color: #0000ff;\">new ProjectInstaller<\/span>();<br \/>\n<span style=\"color: #0000ff;\">InstallContext<\/span> myInstallContext = <span style=\"color: #0000ff;\">new InstallContext<\/span>(logPath, <span style=\"color: #0000ff;\">new string<\/span>[] { });<br \/>\nmyProjectInstaller.Context = myInstallContext;<br \/>\nmyProjectInstaller.ServiceName = &#8220;SelfInstallService&#8221;;<br \/>\nmyProjectInstaller.DisplayName = &#8220;Self Install Service&#8221;;<br \/>\nmyProjectInstaller.Description = &#8220;Self Install Service test&#8221;;<br \/>\nmyProjectInstaller.StartType = startmode;<br \/>\nmyProjectInstaller.Account = account;<br \/>\n<span style=\"color: #0000ff;\">if<\/span> (account == <span style=\"color: #0000ff;\">ServiceAccount<\/span>.User)<br \/>\n{<\/p>\n<p style=\"padding-left: 120px;\">myProjectInstaller.ServiceUsername = username;<br \/>\nmyProjectInstaller.ServicePassword = password;<\/p>\n<p style=\"padding-left: 90px;\">}<br \/>\nmyProjectInstaller.Context.Parameters[&#8220;AssemblyPath&#8221;] = exeFullPath;<\/p>\n<p style=\"padding-left: 90px;\">myProjectInstaller.Install(savedState);<br \/>\nsuccess = <span style=\"color: #0000ff;\">true<\/span>;<\/p>\n<p style=\"padding-left: 60px;\">}<br \/>\n<span style=\"color: #0000ff;\">catch<\/span> (<span style=\"color: #0000ff;\">Exception<\/span> ex)<br \/>\n{<\/p>\n<p style=\"padding-left: 90px;\">System.Windows.Forms.<span style=\"color: #0000ff;\">MessageBox<\/span>.Show(ex.Message, &#8220;Install service&#8221;, System.Windows.Forms.<span style=\"color: #0000ff;\">MessageBoxButtons<\/span>.OK, System.Windows.Forms.<span style=\"color: #0000ff;\">MessageBoxIcon<\/span>.Error);<\/p>\n<p style=\"padding-left: 60px;\">}<br \/>\n<span style=\"color: #0000ff;\">return<\/span> success;<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p style=\"padding-left: 30px;\"><span style=\"color: #0000ff;\">private static bool<\/span> UnInstallService()<br \/>\n{<\/p>\n<p style=\"padding-left: 60px;\"><span style=\"color: #0000ff;\">bool<\/span> success = <span style=\"color: #0000ff;\">false<\/span>;<br \/>\n<span style=\"color: #0000ff;\">try<\/span><br \/>\n{<\/p>\n<p style=\"padding-left: 90px;\"><span style=\"color: #0000ff;\">string<\/span> exeFullPath = System.Reflection.<span style=\"color: #0000ff;\">Assembly<\/span>.GetExecutingAssembly().Location;<br \/>\n<span style=\"color: #0000ff;\">string<\/span> workingPath = System.IO.<span style=\"color: #0000ff;\">Path<\/span>.GetDirectoryName(exeFullPath);<br \/>\n<span style=\"color: #0000ff;\">string<\/span> logPath = System.IO.<span style=\"color: #0000ff;\">Path<\/span>.Combine(workingPath, &#8220;Install.log&#8221;);<\/p>\n<p style=\"padding-left: 90px;\"><span style=\"color: #0000ff;\">ServiceInstaller<\/span> myServiceInstaller = <span style=\"color: #0000ff;\">new ServiceInstaller<\/span>();<br \/>\n<span style=\"color: #0000ff;\">InstallContext<\/span> Context = <span style=\"color: #0000ff;\">new InstallContext<\/span>(logPath, null);<br \/>\nmyServiceInstaller.Context = Context;<br \/>\nmyServiceInstaller.ServiceName = &#8220;SelfInstallService&#8221;;<br \/>\nmyServiceInstaller.Uninstall(<span style=\"color: #0000ff;\">null<\/span>);<br \/>\nsuccess = <span style=\"color: #0000ff;\">true<\/span>;<\/p>\n<p style=\"padding-left: 60px;\">}<br \/>\n<span style=\"color: #0000ff;\">catch<\/span> (<span style=\"color: #0000ff;\">Exception<\/span> ex)<br \/>\n{<\/p>\n<p style=\"padding-left: 90px;\">System.Windows.Forms.<span style=\"color: #0000ff;\">MessageBox<\/span>.Show(ex.Message, &#8220;Uninstall service&#8221;, System.Windows.Forms.<span style=\"color: #0000ff;\">MessageBoxButtons<\/span>.OK, System.Windows.Forms.<span style=\"color: #0000ff;\">MessageBoxIcon<\/span>.Error);<\/p>\n<p style=\"padding-left: 60px;\">}<br \/>\n<span style=\"color: #0000ff;\">return<\/span> success;<\/p>\n<p style=\"padding-left: 30px;\">}<\/p>\n<p>}<\/p><\/blockquote>\n<p>You might notice I use a custom form &#8216;InstallerForm&#8217; here which I&#8217;m not showing the code for. All it is is a simple Windows Form that allows you to enter or choose some settings that this code is using. I&#8217;ll upload some code example a bit later.<\/p>\n<p>Now, you can simple go to the command line (or create a batch file) that calls the service exe with either the &#8220;-<strong>install<\/strong>&#8221; or &#8220;-<strong>uninstall<\/strong>&#8221; parameters.<\/p>\n<p>Update: I&#8217;ve added some sample code <a title=\"TestServiceInstall.zip\" href=\"http:\/\/www.hen.co.za\/downloads\/TestServiceInstall.zip\" target=\"_blank\">here<\/a>. There have been some improvements since the article. It turns out the overridden method OnBeforeInstall is not necessary at all.<\/p>\n<p>In a new blog post I&#8217;ve enhanced the functionality to include multi instance functionality. See <a title=\"creating-a-multi-instance-self-registering-service\" href=\"https:\/\/hen.co.za\/blog\/2012\/12\/creating-a-multi-instance-self-registering-service\/\" target=\"_blank\">this<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Being in the business of creating several Windows Services myself (EventScavenger, Event Collector, QuickMon) I started thinking about how to make the whole install and set up process a bit easier and eliminating the manual steps to install a Windows &hellip;<\/p>\n<p class=\"read-more\"><a href=\"https:\/\/hen.co.za\/blog\/2011\/09\/creating-a-self-installing-windows-service\/\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,14],"tags":[7,8,9,303,92,219,253,26,82],"class_list":["post-560","post","type-post","status-publish","format-standard","hentry","category-development","category-technology","tag-net","tag-c","tag-code","tag-development","tag-installer","tag-msi","tag-service","tag-services","tag-windows"],"_links":{"self":[{"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/posts\/560","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/comments?post=560"}],"version-history":[{"count":14,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/posts\/560\/revisions"}],"predecessor-version":[{"id":569,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/posts\/560\/revisions\/569"}],"wp:attachment":[{"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/media?parent=560"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/categories?post=560"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hen.co.za\/blog\/wp-json\/wp\/v2\/tags?post=560"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}