We needed to deploy a WindowsService in our nightly build. We deploy to a Development, a Test and Acceptance environment. That for the moment all these ‘environments’ are on the same physical server does not really matter. We are going to change this, but for now this is how it works.

This situation creates the requirement to deploy different instances of the same WindowsService on the same server (a Development, Test and Acceptance instance).
To install a WindowsService through our Build we created a WindowsService that is capable of installing itself when calling it with some arguments via the Command Line. 
Maybe I will blog about how we did this in the future, but for now, I think the TeamBuild Workflow is more interesting.
In the main sequence of the build workflow some extra elements exist, we added these elements because of the special build functionalities we need.

The main sequence used in this workflow looks like image 1:

01_BuildSequence
Img 1: Complete Build Sequence

The Deploy of the services is taking place in the fifth element of the workflow, another nested sequence I call ‘Deploy WindowsServices’ as shown on image 2:

02_If_tests_succes
Img 2: Deploy WindowsServices (Sub)Sequence

Inside the sequence an If then activity is placed that checks if compilation succeeded and if the tests have run succesfully (image 3):

03_If_tests_succesfull
Img 3: If compilation and Tests are successful activity

The Condition has the syntax as is shown in listing 1

BuildDetail.CompilationStatus = BuildPhaseStatus.Succeeded And (BuildDetail.TestStatus = BuildPhaseStatus.Succeeded Or BuildDetail.TestStatus = BuildPhaseStatus.Unknown)

Listing 1

Arguments
If you need variables that you want to influence in the Builddefinition the best option is to use Arguments, this are variables that are accessible in the Builddefinition. The Arguments can be added or changed by clicking the tab ‘Arguments’ in the bottom-left of the workflow editor (image 4):

03A_Arguments
Img 4: Arguments Build (can be set in builddefinition)

In the Arguments I added the Argument: WindowsServicesToDeploy the datatype (or argumenttype) is a string array. The WindowsServicesToDeploy argument holds the projectnames of windowsservices that have to be deployed and that are part of the solution that has to be build.

Argument WindowsServicesToDeploy:

The Argument of type string[] that is called WindowsServicesToDeploy has the following value:

New String() {"WebSocketServerService"}

Listing 2

The string array, has only one value for now, but when I add more windowsservices to the solution, all I have to do is add the name of the project in the array of the values in the Builddefinition and we're done.

In the If Then activity shown on image 3, in the Then section I added a ForEach activity. Image 5 shows the properties of this Activity:

04_for_each_Service_properties
Img 5: For each Service in Argument (List<string>)

Listing 2 and 3 show the syntax and value for the TypeArgument and the Values properties of the System.Activities.ForEach<System.String> activity:

TypeArgument:

Microsoft.TeamFoundation.Build.Workflow.Activities.PlatformConfiguration

Listing 3

Values:

BuildSettings.PlatformConfigurations

Listing 4

SolutionConfigurations
As I mentioned our Development, Test and Acceptance environments are all on one box (for now), to differentiate between these environments, we created Solution Configurations for all these environments in our solution. By using pre processing directives we then change configuration of connectionstrings, or url’s etc. in our code and our automated tests. So we have to do a different deployment for all Solution Configurations that are defined in the Builddefinition.

The ForEach activity that loops through all strings in the string array that is defined in the argument WindowsServicesToDeploy (that holds the projectnames of windowsservices that have to be deployed) contains yet another ForEach activity shown on image 6. In this ForEach I loop through all SolutionConfigurations.

05_for_each_Service_ForEach
Img 6: For each Solution Configuration in BuildDefinition

When we double-click to view the contents of this Foreach activity, we see the contents of image 7:

06_for_each_SolutionConfiguration
Img 7: Try Catch in For Each

The ForEach that loops through all SolutionConfigurations contains a TryCatch activity, as shown on image 8 an InvalidOperationException and Exceptions are caught in this TryCatch activity:


07_TryCatch
Img 8: Deploy WindowsService Sequence inside try Catch

In the Catches, BuildErrorMessages are written. In the Try block another Sequence is added that is called ‘Deploy windowsService’.

Variables
To hold state variables can be used, variables can be added or changed by clicking the tab ‘Arguments’ in the bottom-left of the workflow editor (image 9):

 

07a_Variables
Img 9: Variables

The variables, psExecOutput (Int32) and matchingFileResult (IEnumerable<String>) are added.

The ‘Deploy WindowsService’ activity
As we can see on image 8, the sequence Deploy WindowsService is executed inside the Try block.
In the sequence I start off with a FindMatchingFile activity, this is a standard build workflow activity that is available in team build 2010.

08_Deploy_WS_Sequence
Img 10: Deploy WindowsService Sequence

The FindMatchingFile activity has the properties that are shown on image 11, where the MatchPattern is the most important:

09_Find_Matching_Files
Img 11: FindMatchingFile Activity

The MatchPattern describes the pattern where the files you need are going to be found by.

MatchPattern:

 

String.Format("\\\\\{0}_{1}\{0}.exe", windowsService, solutionConfiguration.Configuration)

Listing 5

The next activity is another ‘If Then’, that checks if the FindMatchingFile activity found one file, as shown on image 12:

10If_a_Matching_File
Img 12: If then containing a Invoke Process Activity

PSExec.exe
If one file is found, than an ‘Invoke Process Activity’ is executed, when not a buildmessage is written.   Image 13 shows all properties of the InvokeProcess Activity, used to  invoke PsExec to execute the uninstallation of the WindowsService, because if the file is found on the location where the matchpattern says the file could be present, an older version of the windowsservice is installed. So we need to uninstall it.

12_Invoke_Uninstall_properties
Img 13: Invoke Process Activity's properties

We renamed the psexec.exe, to prevent security breach. 
Listing 6,7 and 8 show the values of the properties for the uninstall action for our WindowsService:

Arguments:

String.Format("\\ -d C:\Services\\{1}_{0}\{1}.exe -uninstall -name ""{0}"" /accepteula", solutionConfiguration.Configuration, windowsService)

Listing 6

Listing 6 shows the value for Arguments property, the arguments that you pass to the process you invoke. In our case we invoke psexec.exe that calls our windowsservice on another machine, so the arguments are arguments that we pass to psexec. The arguments used are:

  • –d which instructs psexec not to wait for the application to terminate, so the build will can continue in case something takes a very long time;
  • /accepteula which takes care of the dialog that will popup the first time a user calls psexec and will fail the build if it pops up, because the dialog will never be closed
  • C:\Services\\{1}_{0}\{1}.exe -uninstall -name ""{0}"" will result in something like: ‘C:\Services\\servicename_Development\servicename.exe -uninstall -name "servicename"’ this is the exe that psexec will call, we pass the parameters for the service right in there (-uninstall -name "servicename").

DisplayName:

Invoke PSExec Process Uninstall

Listing 7

 

FileName:

"D:\Tools\SysInternals\PSTools\PsExecT.exe"

Listing 8

Listing 8 shows the Local Path to PSExec.exe on the buildserver.

Result:

psExecOutput (Int32)

Listing 9

 

WorkingDirectory:

"D:\Tools\SysInternals\PSTools" 

Listing 10

Listing 10 shows the working directory for PSExec on the buildserver)

After the deinstallation, or if the service is not installed on the server the CopyDirectory activity is executed


13_CopyDirectory
Img 14: CopyDirectory Activity

The CopyDirectory Activity uses the following properties:

Destination

String.Format("\\\Services\{0}_{1}", windowsService, solutionConfiguration.Configuration)

Listing 11

Source

String.Format("{0}\{1}\bin\{2}", BuildDetail.DropLocation, windowsService, solutionConfiguration.Configuration)

Listing 12

When the copy action is ready another Invoke Process Activity is executed that calls psexec to install the windowsservice.

img_15_InvokeInstall
Img 15: Invoke Process (Install Service)

The details of this activity looks like image 16:

img_16_InvokeInstall_Process
Img 16: Invoke Process details

The properties are shown on image 17:

img_17_InvokeInstall_Properties
Img 17: Properties Invoke Install Process

All properties have almost the same values as the uninstall invoke process in image 13, only the Arguments are sligthly different.

Arguments:

String.Format("\\<servername> -d C:\Services\TX.Communication.Prototype\{1}_{0}\{1}.exe -install -name ""{0}"" /accepteula", solutionConfiguration.Configuration, windowsService)

Listing 12

These steps take care of installing our WindowsServices in our build, so we can deploy in an automated fashion. After the depoloyment, in our nigthly build we also run automated UI Tests. Another topic that is quite interesting…

Henry Cordes
My thoughts exactly…