I have been building and releasing software for over 20 years now.  One thing I have learned over the years is that if you have a lot of manual steps in your release process then you will end up making mistakes.  There are many tools available that can help reduce those mistakes.  StudioShell is one of those tools and it has some unique characteristics that make it stand out over the rest of the tools I have used for automating release processes.

I have been maintaining software that manages the FIRST Robotics Competition for the last 5 years.  The Team Foundation Server build process for the software has been modified to package up the bits into an MSI installer.  The installer and a manifest file is deployed to a webserver.  This MSI and manifest make up an auto update process for all the events that are scattered across the US.  The problem I have had over the years is that the process of maintaining the Wix files, manifest, and assembly info meant I had to edit the version number for the next release in multiple places.  As you can imagine this was a recipe for easy mistakes that just do not need to exist.

Well I could solve this problem by modifying the build process to checkout the files that needed to have the new version number.  This would certainly remove the manual process of doing this by hand before the build executes.  However I am not a fan of having the build process modifying source files (Wix, manifest and assembly info).  So I was left with automating the manual process outside the build.  This is where StudioShell shines.

First I created a StudioShell Solution Module only because I wanted to be able to automatically change to the directory that the solution is located once the StudioShell view is opened up inside of Visual Studio.  This will allow me to easily launch other powershell scripts and do some relative paths within these scripts. 

Here is what is in the Solution Module:

function Set-Folder
{
    $file = (get-item(get-item dte:\solution).FullName);
    cd $file.DirectoryName
}

"Loading Solution Module" | out-outputpane;

$m = $MyInvocation.MyCommand.ScriptBlock.Module;
$m.OnRemove = {"Unloading Solution Module" | out-outputpane;}

Set-Folder

As you can see in the script it uses the DTE drive to get the full path to the solution and it simply changes to that directory.  Now any scripts executed in the StudioShell host can use relative paths.

Next I needed a new powershell script that did all the manual operations as follows:

  • Prompt for the version number
  • Change the AssemblyFileVersion in the VersionInfo.cs file to have the new version
  • Change the manifest files to have the new version
  • Change the Wix files to have the new version number

Here is what is in the SetVersion.ps1 file:

function Set-Version
{
    Param ([string]$newVersion)
    $tmp = '"' + $newVersion + '.0"'
    (get-item dte:\solution\projects\fms.util.shell\versioninfo.cs\codemodel\assemblyfileversion) | set-itemproperty -name value -value $tmp
    $file = get-item dte:\solution\projects\fms.util.shell\versioninfo.cs
    $file.Save($file.FileName)

    Set-ManifestVersion $newVersion "Full"
    Set-ManifestVersion $newVersion "Delta"
    Set-Wix $newVersion "Full" "Server"
    Set-Wix $newVersion "Full" "App"
    Set-Wix $newVersion "Delta" "Server"
    Set-Wix $newVersion "Delta" "App"
    Set-Wix $newVersion "" "Light"
}
function Get-Version
{
    return (get-item dte:\solution\projects\fms.util.shell\versioninfo.cs\codemodel\assemblyfileversion).Value
}
function Set-ManifestVersion
{
    Param ([string]$newVersion,[string]$installType)
    $tmp = '"' + $newVersion + '"'
    $fileName = ((get-item dte:\solution\projects\fms.util.shell\$installType\manifest.xml).FileName)
    $xml = [xml](get-content $fileName)
    $appsNode = $xml.Manifest.SelectSingleNode("./Versions/ManifestItem").Clone()
    $serverNode = $xml.Manifest.SelectSingleNode("./Versions/ManifestItem").Clone()
    $nodes = $xml.Manifest.SelectNodes("./Versions/ManifestItem")

    $node = $xml.Manifest.SelectNodes("./Versions/ManifestItem[Version='$newVersion']")
    $node | ForEach-Object  {$xml.Manifest.Versions.RemoveChild($_)}

    $appsNode.Version = "$newVersion"
    $appsNode.FileName = "FMSApps$installType.msi"
    $appsNode.InstallerType = "Apps"
    $xml.Manifest.Versions.AppendChild($appsNode)
    
    $serverNode.Version = "$newVersion"
    $serverNode.FileName = "FMSServer$installType.msi"
    $serverNode.InstallerType = "Server"
    $xml.Manifest.Versions.AppendChild($serverNode)
    $xml.Save($fileName)
}
function Set-Wix
{
    Param ([string]$newVersion,[string]$installType,[string]$targetType)
    $tmp = '"' + $newVersion + '"'
    $location = get-location
    $fileName = $location.Path + "\Source\fms.Installer\$targetType\Setup$installType.wxs"
    $xml = [xml](get-content $fileName)
    $node = $xml.Wix.Product
    $node.Version = "$newVersion"
    $nodes = $xml.Wix.Product.Upgrade.UpgradeVersion
    foreach ($node in $nodes)
    {
      if ($node.Property -eq "NEWPRODUCTFOUND")
      {
        $node.Minimum = "$newVersion"
      }
      if ($node.Property -eq "UPGRADEFOUND")
      {
        $node.Maximum = "$newVersion"
      }
    }
    $xml.Save($fileName)
}

$current = Get-Version
Write-Host "The current version is:" $current
$newVersion = read-host "What is the new version you want"
Set-Version $newVersion

As you can see the script is not all that complex.  The DTE drive makes finding resources in the solution and accessing properties of the resource such as file name very easy.  Since the solution is bound to source control, modifying the VersionInfo.cs file automatically checks it out of source control.  I was even able to use the codemodel to easily find the AssemblyFileVersion and set its value to the next version number.

To release the next version of my software I simply launch the StudioShell view and type .\SetVersion in the shell and the script will read the current version from the VersionInfo.cs file and prompt me for the next version number.  After I enter the new version number and hit return the necessary files will be updated and saved.  All I have to do is check in the changes and kick off the build.

In conclusion I was able to use the powerful features of StudioShell to take a very manual multi step process and reduce it down to only a couple steps.     

Comments

beefarino

Nice post Mike! One of the things I tend to do is use the assemblyversion attribute for a specific project to drive the version number across the build. E.g., when I build StudioShell, the version specified in the CodeOwls.StudioShell assembly is used to update the remaining assembly versions as well as the PowerShell module version number, the NuGet package version, and the MSI version variables. You should also look at PSake (psake.github.com) as a way to drive your build. It's just powershell, but helps you define tasks and dependencies in a super-easy way. Thanks again for the StudioShell love! jim

beefarino
mlinnen

Thanks Jim for making such a cool product! I have used PSake before on a project and I liked it very much. I need to revisit it again and start using it on future projects. Thanks for the feedback

mlinnen

Comments are closed