Publishing more than one Azure Cloud Service as part of a TFS build

Using the process in my previous post you can get a TFS build to create the .CSCFG and .CSPKG files needed to publish a Cloud Service. However, you hit a problem if your solution contains more that one Cloud Service project; as opposed to a single cloud service project with multiple roles, which is not a problem.

The method outlined in the previous post drops the two files into a Packages folder under the drops location. The .CSPKG files are fine, as they have unique names. However there is only one ServiceConfiguration.cscfg, whichever one was created last.

Looking in the cloud service projects I could find no way to rename the ServiceConfiguration file. It looks like it is like a app.config or web.config file i.e. it’s name is hard coded.

The only solution I could find was to add a custom target that is set to run after the publish target. This was added to the end of each .CCPROJ files using a text editor just before the closing </project>

 <Target Name="CustomPostPublishActions" AfterTargets="Publish">
    <Exec Command="IF '$(BuildingInsideVisualStudio)'=='true' exit 0
    echo Post-PUBLISH event: Active configuration is: $(ConfigurationName) renaming the .cscfg file to avoid name clashes
    echo Renaming the .CSCFG file to match the project name $(ProjectName).cscfg
    ren $(OutDir)PackagesServiceConfiguration.*.cscfg $(ProjectName).cscfg
    " />
  </Target>
   <PropertyGroup>
    <PostBuildEvent>echo NOTE: This project has a post publish event</PostBuildEvent>
  </PropertyGroup>

 


Using this I now get unique name for the .CSCFG files as well as for .CSPKG files in my drops location. All ready for Release Management to pickup


Notes:


  • I echo out a message in the post build event too just as a reminder that I have added a custom target that cannot be seen in Visual Studio, so is hard to discover
  • I use an if test to make sure the commands are only run on the TFS build box, not on a local build. The main reason for this is the path names are different for local builds as opposed to TFS build. If you do want a rename on a local build you need to change the $(OutDir)Packages path to $(OutDir)app.publish. However, it seemed more sensible to leave the default behaviour occur when running locally

Source: Rfennell

Deploying a Windows service with Release Management

I recently needed to deploy a Windows service as part of a Release Management pipeline. In the past, our internal systems I have only need to deploy DB (via SSDT Dacpacs) and Websites (via MSDeploy), so a new experience.

WIX Contents

The first step to to create an MSI installer for the service. This was done using WIX, with all the fun that usually entails. The key part was a component to do the actual registration and starting of the service

<Component Id ="ModuleHostInstall" Guid="{3DF13451-6A04-4B62-AFCB-731A572C12C9}" Win64="yes">
   <CreateFolder />
   <Util:User Id="ModuleHostServiceUser" CreateUser="no" Name="[SERVICEUSER]" Password="[PASSWORD]" LogonAsService="yes" />
   <File Id="CandyModuleHostService" Name ="DataFeed.ModuleHost.exe" Source="$(var.ModuleHost.TargetDir)ModuleHost.exe" KeyPath="yes" Vital="yes"/>
   <ServiceInstall Id="CandyModuleHostService" Name ="ModuleHost" DisplayName="Candy Module Host" Start="auto" ErrorControl="normal" Type="ownProcess"  Account="[SERVICEUSER]" Password="[PASSWORD]" Description="Manages the deployment of Candy modules" />
   <ServiceControl Id="CandyModuleHostServiceControl" Name="ModuleHost" Start="install" Stop="both" Wait="yes" Remove="uninstall"/>

So nothing that special here, but worth remembering if you miss out the ServiceControl block the service will not automatically start or be uninstalled with the MSI’s uninstall


You can see that we pass in the service account to be used to run the service as a property. This is an important technique for using WIX with Release Management, you will want to be able to pass in anything you may want to change as installation time as a parameter. This means we ended up with a good few properties such as

  <Property Id="DBSERVER" Value=".sqlexpress" />
  <Property Id="DBNAME" Value ="=CandyDB" />
  <Property Id="SERVICEUSER" Value="Domainserviceuser" />
  <Property Id="PASSWORD" Value="Password1" />

These tended to equate to app.config settings. In all cases I tried to set sensible default values so in most cases I could avoid passing in an override value.


These property values were then used to re-write the app.config file after the copying of the files from the MSI onto the target server. This was done using the XMLFile tools and some XPath e.g.

<Util:XmlFile Id="CacheDatabaseName" 
Action="setValue"
Permanent="yes"
File="[#ModuleHost.exe.config]"
ElementPath="/configuration/applicationSettings/DataFeed.Properties.Settings/setting[[]@name='CacheDatabaseName'[]]/value" Value="[CACHEDATABASENAME]" Sequence="1" />
 

Command Line Testing


Once the MSI was built it could be tested from the command line using the form

msiexec /i Installer.msi /Lv msi.log SERVICEUSER="domainsvc_acc" PASSWORD="Password1" DBSERVER="dbserver" DBSERVER="myDB" …..

I soon spotted a problem. As I was equating properties with app.config settings I was passing in connections strings and URLs, so the command line got long very quickly. It was really unwieldy to handle


A check of the log file I was creating, msi.log, showed the command line seemed to be truncated. This seemed to occur around 1000 characters. I am not sure if this was an artefact of the logging or the command line, but either way a good reason to try to shorten the property list.


I  therefore decided that I would not pass in whole connection strings, but just the properties that might change, especially effective for connection strings to things such as Entity Framework. This meant I did some string building in WIX during the transformation of the app.config file e.g.

<Util:XmlFile Id='CandyManagementEntities1'
   Action='setValue'
   ElementPath='/configuration/connectionStrings/add[[]@name="MyManagementEntities"[]]/@connectionString'
   File='[#ModuleHost.exe.config]' Value='metadata=res://*/MyEntities.csdl|res://*/MyEntities.ssdl|res://*/MyEntities.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=[DBSERVER];initial catalog=[DBNAME];integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;' />

This technique had another couple of advantages


  • It meant I did not need to worry over spaces in strings, I could therefore lose the “ in the command line – Turns out this is really important later.
  • As I was passing in just a ‘secret value’ as opposed to a whole URL I could use the encryption features of Release Management to hide certain values

It is at this point I was delayed for a long time. You have to be really careful when installing Windows services via an MSI that your service can actually start. If it cannot then you will get errors saying “… could not be installed. Verify that you have sufficient privileges to install system services”. This is probably not really a rights issue, just that some configuration setting is wrong so the service has failed to start. In my case it was down to an incorrect connection string, stray commas and quotes, and a missing DLL that should have been in the installer. You often end up working fairly blind at this point as Windows services don’t give too much information when they fail to load. Persistence, SysInternals Tools and comparing to the settings/files on a working development PC are the best options


Release Management Component


Once I had working command line I could create a component in Release Management. On the Configure Apps > Components page I already had a MDI Deployer, but this did not expose any properties. I therefore copied this component to create a MSI deployer specific to my new service installer and started to edit it.


All the edits were on the deployment tab, adding the extra properties that could be configured.


image


Note: Now it might be possible to do something with the pre/post deployment configuration variables as we do with MSDeploy, allowing the MSI to run then editing the app.config later. However, given that MSI service installers tends to fail they cannot start the new service I think passing in the correct properties into MSIEXEC is a better option. Also means it is consistent for anyone using the MSI via the command line.


On the Deployment tab I changed the Arguments to

-File ./msiexec.ps1 -MsiFileName "__Installer__"  -MsiCustomArgs ‘SERVICEUSER=”__SERVICEUSER__”  PASSWORD=”__PASSWORD__” DBSERVER=”__DBSERVER__”  DBNAME=”__DBNAME__” …. ’

I had initially assumed I needed the quotes around property values. Turns out I didn’t, and due to the way Release Management runs the component they made matters much, much worse. MSIEXEC kept failing instantly. if I ran the command line by hand on the target machine it was actually showing the Help dialog, so I knew the command line was invalid.


Turns out the issue is Release Management calls PowerShell.EXE to run the script passing in the Arguments. This in turn calls a PowerShell Script which does some argument processing before running a process to run MSIEXEC.EXE with some parameters. You can see there are loads of places where the escaping and quotes around parameters could get confused.


After much fiddling, swapping ‘ for “ I realised I could just forget most of the quotes. I had already edited my WIX package to build complex strings, so the actual values were simple with no spaces. Hence my command line became

-File ./msiexec.ps1 -MsiFileName "__Installer__"  -MsiCustomArgs “SERVICEUSER=__SERVICEUSER__  PASSWORD=__PASSWORD__ DBSERVER=__DBSERVER__  DBNAME=__DBNAME__ …. “

Once this was set my release pipeline worked resulting in a system with DBs, web services and window service all up and running.


As is often the case it took a while to get this first MSI running, but I am sure the next one will be much easier.


Source: Rfennell

PowerShell Summit Europe 2014

I find I am spending more time with PowerShell these days, as we aim to automated more of our releases and specifically with DSC in PowerShell 4, as I am sure many of us are

Give that fact, the PowerShell Summit Europe 2014 at the end of the month looks interesting. I only found out about it too late and I have diary clashes but might be of interest to some of you. Looks like a really good hands event.


Source: Rfennell

Got around to updating my Nokia 820 to WP81 Update 1

I had been suffering with the 0x80188308 error when I tried to update my Nokia 820 to the WP81 Update 1 because I had the developer preview installed. I had been putting off what appeared to be the only solution of doing a reset as discussed in the forums as it seem a bit drastic, thought I would wait for Microsoft to sort out the process. I got bored waiting..

Turns out as long as you do the backup first it is fairly painless, took about an hour of uploads and downloads over WiFi

  1. Created a manual backup of the phone: Settings>backup>apps+settings>backup now.
  2. Reset the phone to factory settings (DP 8.1), leaving any SD card alone: Settings>about>reset your phone.
  3. When prompted logged in with the same ID as used for the backup
  4. Restored the phone using the backup just created.  
  5. Reconnected to all of the other accounts and let the phone download all of the apps.
  6. Signed back into the Preview for Developers app – else you won’t see the updates!
  7. The updates comes down without a problem as one large package

Lets have a go with a UK aware version of Cortana….


Source: Rfennell

Got around to updating my Nokia 820 to WP81 Update 1

I had been suffering with the 0x80188308 error when I tried to update my Nokia 820 to the WP81 Update 1 because I had the developer preview installed. I had been putting off what appeared to be the only solution of doing a reset as discussed in the forums as it seem a bit drastic, thought I would wait for Microsoft to sort out the process. I got bored waiting..

Turns out as long as you do the backup first it is fairly painless, took about an hour of uploads and downloads over WiFi

  1. Created a manual backup of the phone: Settings>backup>apps+settings>backup now.
  2. Reset the phone to factory settings (DP 8.1), leaving any SD card alone: Settings>about>reset your phone.
  3. When prompted logged in with the same ID as used for the backup
  4. Restored the phone using the backup just created.  
  5. Reconnected to all of the other accounts and let the phone download all of the apps.
  6. Signed back into the Preview for Developers app – else you won’t see the updates!
  7. The updates comes down without a problem as one large package

Lets have a go with a UK aware version of Cortana….


Source: Rfennell

Getting ‘… is not a valid URL’ when using Git TF Clone

I have been attempting to use the Git TF technique to migrate some content between TFS servers. I needed to move a folder structure that contains spaces in folder names from a TPC that also contains spaces in its name. So I thought my command line would be

git tf clone “http://tfsserver1:8080/tfs/My Tpc” “$/My Folder”’ oldrepo --deep

But this gave the error

git-tf: “http://tfsserver1:8080/tfs/My Tpc” is not a valid URL

At first I suspected it was the quotes I was using, as I had had problems here before, but swapping from ‘ to “ made no difference.


The answer was to use the ASCII code %20 for the space, so this version of the command worked

git tf clone http://tfsserver1:8080/tfs/My%20Tpc “$/My Folder”’ oldrepo --deep

Interestingly you don’t need to use %20 for the folder name


Source: Rfennell

Getting ‘… is not a valid URL’ when using Git TF Clone

I have been attempting to use the Git TF technique to migrate some content between TFS servers. I needed to move a folder structure that contains spaces in folder names from a TPC that also contains spaces in its name. So I thought my command line would be

git tf clone “http://tfsserver1:8080/tfs/My Tpc” “$/My Folder”’ oldrepo --deep

But this gave the error

git-tf: “http://tfsserver1:8080/tfs/My Tpc” is not a valid URL

At first I suspected it was the quotes I was using, as I had had problems here before, but swapping from ‘ to “ made no difference.


The answer was to use the ASCII code %20 for the space, so this version of the command worked

git tf clone http://tfsserver1:8080/tfs/My%20Tpc “$/My Folder”’ oldrepo --deep

Interestingly you don’t need to use %20 for the folder name


Source: Rfennell

Build failing post TFS 2013.3 upgrade with ‘Stack empty. (type InvalidOperationException)’

Just started seeing build error on a build that was working until we upgraded the build agent to TFS 2013.3

Exception Message: Stack empty. (type InvalidOperationException)
Exception Stack Trace:    at Microsoft.VisualStudio.TestImpact.Analysis.LanguageSignatureParser.NotifyEndType()
   at Microsoft.VisualStudio.TestImpact.Analysis.SigParser.ParseType()
   at Microsoft.VisualStudio.TestImpact.Analysis.SigParser.ParseRetType()
   at Microsoft.VisualStudio.TestImpact.Analysis.SigParser.ParseMethod(Byte num1)
   at Microsoft.VisualStudio.TestImpact.Analysis.SigParser.Parse(Byte* blob, UInt32 len)
   at Microsoft.VisualStudio.TestImpact.Analysis.LanguageSignatureParser.ParseMethodName(MethodProps methodProps, String& typeName, String& fullName)
   at Microsoft.VisualStudio.TestImpact.Analysis.AssemblyMethodComparer.AddChangeToList(DateTime now, List`1 changes, CodeChangeReason reason, MethodInfo methodInfo, MetadataReader metadataReader, Guid assemblyIdentifier, SymbolReader symbolsReader, UInt32 sourceToken, LanguageSignatureParser& languageParser)
   at Microsoft.VisualStudio.TestImpact.Analysis.AssemblyMethodComparer.CompareAssemblies(String firstPath, String secondPath, Boolean lookupSourceFiles)
   at Microsoft.TeamFoundation.TestImpact.BuildIntegration.BuildActivities.GetImpactedTests.CompareBinary(CodeActivityContext context, String sharePath, String assembly, IList`1 codeChanges)
   at Microsoft.TeamFoundation.TestImpact.BuildIntegration.BuildActivities.GetImpactedTests.CompareBuildBinaries(CodeActivityContext context, IBuildDefinition definition, IList`1 codeChanges)
   at Microsoft.TeamFoundation.TestImpact.BuildIntegration.BuildActivities.GetImpactedTests.Execute(CodeActivityContext context)
   at System.Activities.CodeActivity.InternalExecute(ActivityInstance instance, ActivityExecutor executor, BookmarkManager bookmarkManager)
   at System.Activities.Runtime.ActivityExecutor.ExecuteActivityWorkItem.ExecuteBody(ActivityExecutor executor, BookmarkManager bookmarkManager, Location resultLocation)

I assume the issue is a DLL mismatch between what is installed in as part of the build agent and something in the 2012 generation build process template in use.


As an immediate fix, until I get a chance to swap the template to a newer one, was to disable Test Impact Analysis, which I was not using for this project anyway.


image


Once I did this my build completed OK with the tests ran OK


Source: Rfennell

Build failing post TFS 2013.3 upgrade with ‘Stack empty. (type InvalidOperationException)’

Just started seeing build error on a build that was working until we upgraded the build agent to TFS 2013.3

Exception Message: Stack empty. (type InvalidOperationException)
Exception Stack Trace:    at Microsoft.VisualStudio.TestImpact.Analysis.LanguageSignatureParser.NotifyEndType()
   at Microsoft.VisualStudio.TestImpact.Analysis.SigParser.ParseType()
   at Microsoft.VisualStudio.TestImpact.Analysis.SigParser.ParseRetType()
   at Microsoft.VisualStudio.TestImpact.Analysis.SigParser.ParseMethod(Byte num1)
   at Microsoft.VisualStudio.TestImpact.Analysis.SigParser.Parse(Byte* blob, UInt32 len)
   at Microsoft.VisualStudio.TestImpact.Analysis.LanguageSignatureParser.ParseMethodName(MethodProps methodProps, String& typeName, String& fullName)
   at Microsoft.VisualStudio.TestImpact.Analysis.AssemblyMethodComparer.AddChangeToList(DateTime now, List`1 changes, CodeChangeReason reason, MethodInfo methodInfo, MetadataReader metadataReader, Guid assemblyIdentifier, SymbolReader symbolsReader, UInt32 sourceToken, LanguageSignatureParser& languageParser)
   at Microsoft.VisualStudio.TestImpact.Analysis.AssemblyMethodComparer.CompareAssemblies(String firstPath, String secondPath, Boolean lookupSourceFiles)
   at Microsoft.TeamFoundation.TestImpact.BuildIntegration.BuildActivities.GetImpactedTests.CompareBinary(CodeActivityContext context, String sharePath, String assembly, IList`1 codeChanges)
   at Microsoft.TeamFoundation.TestImpact.BuildIntegration.BuildActivities.GetImpactedTests.CompareBuildBinaries(CodeActivityContext context, IBuildDefinition definition, IList`1 codeChanges)
   at Microsoft.TeamFoundation.TestImpact.BuildIntegration.BuildActivities.GetImpactedTests.Execute(CodeActivityContext context)
   at System.Activities.CodeActivity.InternalExecute(ActivityInstance instance, ActivityExecutor executor, BookmarkManager bookmarkManager)
   at System.Activities.Runtime.ActivityExecutor.ExecuteActivityWorkItem.ExecuteBody(ActivityExecutor executor, BookmarkManager bookmarkManager, Location resultLocation)

I assume the issue is a DLL mismatch between what is installed in as part of the build agent and something in the 2012 generation build process template in use.


As an immediate fix, until I get a chance to swap the template to a newer one, was to disable Test Impact Analysis, which I was not using for this project anyway.


image


Once I did this my build completed OK with the tests ran OK


Source: Rfennell

The random thoughts of Richard Fennell on technology and software development'