Monday, July 21, 2014

Auto-starting a Windows service at build time

My coworker Frank just posted a how-to on setting up a Windows service, which inspired this post.

If you have a Windows service as a C# project, you can set it up to start the service automatically anytime you build. You could even have it work only on your computer so others don't do it by accident, etc.

First go in your solution configuration (that little dropdown at the top that usually says "Debug" or "Release") and for your service project, add a new project configuration called "InstallServiceLocally." To make it even more explicit, if you leave the existing solution configuration alone, you can add a new solution configuration and then have that point to a new project configuration "InstallServiceLocally." Then you can change the dropdown from "Debug" to "InstallServiceLocally" and it will run a debug build plus install the service when that value is set in the solution configuration dropdown.

After you add the project configuration, you will need to edit your csproj file (every C# project has one, or if VB.NET use vbproj) and add the post-build event with the installation logic. Paste this in toward the at the end of your csproj, before the closing </Project> tag:

  <PropertyGroup Condition="'$(COMPUTERNAME)' == 'MyComputerNameGoesHere' and '$(Configuration)' == 'InstallServiceLocally'">    <PreBuildEventDependsOn>SetLatestNetFrameworkPath;$(PreBuildEventDependsOn)</PreBuildEventDependsOn>    <PostBuildEventDependsOn>SetLatestNetFrameworkPath;$(PostBuildEventDependsOn)</PostBuildEventDependsOn>    <CleanDependsOn>UninstallDealerOnCmsGoogleAnalyticsImportService</CleanDependsOn>    <LatestNetFrameworkPath>$(WinDir)\Microsoft.NET\Framework\v4.0.30319\</LatestNetFrameworkPath>    <InstallUtilPath>$(LatestNetFrameworkPath)InstallUtil.exe</InstallUtilPath>    <PostBuildEventWithDeployment>      "$(InstallUtilPath)" "$(TargetPath)"
      net start "$(TargetName)"
    </PostBuildEventWithDeployment>    <PreBuildEventWithDeployment>      net stop "$(TargetName)"
      "$(InstallUtilPath)" /u "$(TargetPath)"
      Exit /b 0
    </PreBuildEventWithDeployment>    <PostBuildEvent>$(PostBuildEventWithDeployment)</PostBuildEvent>    <PreBuildEvent>$(PreBuildEventWithDeployment)</PreBuildEvent>  </PropertyGroup>  <Target Name="SetLatestNetFrameworkPath">    <GetFrameworkPath>      <Output TaskParameter="Path" PropertyName="LatestNetFrameworkPath" />    </GetFrameworkPath>  </Target>  <Target Name="UninstallDealerOnCmsGoogleAnalyticsImportService">    <Exec WorkingDirectory="$(OutDir)" Command="$(PreBuildEvent)" />  </Target>

Whenever the solution configuration is set which builds the project in "InstallServiceLocally" configuration, if the computer name you're building on matches what's hardcoded in the project file (or you can remove this check, or do whatever other condition you want or no condition), then at the end of each build, it will stop, reinstall, and restart the service.

After that, if you want to debug, you'll need to attach the debugger to the service exe... So if you really want to debug the service as it's running, you'll probably want to put a sleep or timer of some sort at startup so it doesn't start running before your debugger is attached. Or you can always debug by creating a unit test wrapper around it, and debugging unit-test methods that call your service's internals. (Remember, you don't have to make things public for a unit test project to access them -- just use the [assembly: InternalsVisibleTo("Name.Of.Friend.Assembly")] attribute in the AssemblyInfo.cs file of the project having the members you want to expose. This makes it similar to a friend assembly in Java.

No comments:

Post a Comment