Tag Archives: visual studio

Versioning Visual Studio Solutions The Easy Way

Versioning your assemblies on each test or release build is an essential best practice to ensure identifiable releases (for testing and support) and comply with Windows Installer standards if you produce MSIs (for example using WIX).

However many developers won’t have access to a fully setup and maintained “continuous integration” build environment, which typically provide versioning support built-in. And anyway there are the interim builds you may want to make quickly on a local developer PC, for example during WIX development. They probably won’t be done via a build sever for convenience and speed.

This brings a requirement for a simple local method to quickly increment the build across all projects and various configuration files. Here is a solution involving just a PowerShell script and text file, which you can add to your solution to achieve the same. You could also integrate this with a build server, providing the best of both solutions.

Firstly, here are the files you add to the root of your solution (in “Solution Items”):

  • Version.txt   – Stores the major and minor version parts which you will manually increment according to your target product and sprint/release versions, plus the build and revision parts which are automatically set by the script to the year, month, day and build number.
  • Version.ps1 – PowerShell script which reads the old Version.txt, generates a new version number then writes it back. Then it edits all necessary project files (e.g. AssemblyInfo.cs, Global.wxi, App.config, Web.config).
  • Version.cmd – Windows Command script file which calls the PowerShell script with the necessary parameters. Makes it easy for the developer or build script to invoke the PowerShell script.

So you start by creating a Version.txt file with your major and minor versions and any build and revision, for example:

2.5.0.0

Then create the Version.ps1 containing the following script, editing or extending the final subroutine calls to cover all of your relevant project files:

Write-Output "Version"
Write-Output "======="
Write-Output "Increments the version number stored in the Version.txt file,"
Write-Output "then applies it to all relevant source files in the solution."
Write-Output "Build is set to the UTC year and month in ""yyMM"" format."
Write-Output "Revision is set to the UTC day * 1000 plus a three digit incrementing number." 
Write-Output ""

trap
{
    Write-Error $_
    exit 1
}

function Update-Version ([Version]$Version)
{
    $date = (Get-Date).ToUniversalTime()
    $newBuild = $date.ToString("yyMM")
    $dayRevisionMin = $date.Day * 1000
    if (($Version.Build -lt $newBuild) -or ($Version.Revision -lt $dayRevisionMin)) { $newRevision = $dayRevisionMin + 1 } else { $newRevision = $Version.Revision + 1 }
    New-Object -TypeName System.Version -ArgumentList $Version.Major, $Version.Minor, $newBuild, $newRevision
}

function Get-VersionFile ([String]$File)
{
    Write-Host ("Reading version file " + $File)
    $versionString = [System.IO.File]::ReadAllText($File).Trim()
    New-Object -TypeName System.Version -ArgumentList $versionString
}

function Set-VersionFile ([String]$File, [Version]$Version)
{
    Write-Host ("Writing version file " + $File)
    [System.IO.File]::WriteAllText($File, $Version.ToString())
}

function Set-VersionInAssemblyInfo ([String]$File, [Version]$Version)
{
    Write-Host ("Setting version in assembly info file " + $File)
    $contents = [System.IO.File]::ReadAllText($File)
    $contents = [RegEx]::Replace($contents, "(AssemblyVersion\("")(?:\d+\.\d+\.\d+\.\d+)(""\))", ("`${1}" + $Version.ToString() + "`${2}"))
    $contents = [RegEx]::Replace($contents, "(AssemblyFileVersion\("")(?:\d+\.\d+\.\d+\.\d+)(""\))", ("`${1}" + $Version.ToString() + "`${2}"))
    [System.IO.File]::WriteAllText($File, $contents)
}

function Set-VersionInWixGlobal ([String]$File, [Version]$Version)
{
    Write-Host ("Setting version in WIX global file " + $File)
    $contents = [System.IO.File]::ReadAllText($File)
    $contents = [RegEx]::Replace($contents, "(\<\?define\s*ProductVersion\s*=\s*"")(?:\d+\.\d+\.\d+\.\d+)(""\s*\?\>)", ("`${1}" + $Version.ToString() + "`${2}"))
    [System.IO.File]::WriteAllText($File, $contents)
}

function Set-VersionInAssemblyReference ([String]$File, [String]$AssemblyName, [Version]$Version)
{
    Write-Host ("Setting version in assembly references of " + $File)
    $contents = [System.IO.File]::ReadAllText($File)
    $contents = [RegEx]::Replace($contents, "(["">](?:\S+,\s+){0,1}" + $AssemblyName + ",\s+Version=)(?:\d+\.\d+\.\d+\.\d+)([,""<])", ("`${1}" + $Version.ToString() + "`${2}"))
    [System.IO.File]::WriteAllText($File, $contents)
}

function Set-VersionInBindingRedirect ([String]$File, [String]$AssemblyName, [Version]$Version)
{
    Write-Host ("Setting version in binding redirects of " + $File)
    $contents = [System.IO.File]::ReadAllText($File)
    $oldVersionMax = New-Object -TypeName "System.Version" -ArgumentList $Version.Major, $Version.Minor, $Version.Build, ($Version.Revision - 1)
    $pattern = "([\s\S]*?<assemblyIdentity\s+name=""" + $AssemblyName + """[\s\S]+?/>[\s\S]*?<bindingRedirect\s+oldVersion=""\d+\.\d+\.\d+\.\d+-)(?:\d+\.\d+\.\d+\.\d+)(""\s+newVersion="")(?:\d+\.\d+\.\d+\.\d+)(""[\s\S]*?/>)"
    $contents = [RegEx]::Replace($contents, $pattern, ("`${1}" + $oldVersionMax.ToString() + "`${2}" + $Version.ToString() + "`${3}"))
    [System.IO.File]::WriteAllText($File, $contents)
}

$scriptDirectory =  [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition)

$versionFilePath = $scriptDirectory + "\Version.txt"
$version = Get-VersionFile -File $versionFilePath
Write-Host ("Old Version: " + $version.ToString())

$newVersion = Update-Version -Version $version
Write-Host ("New Version: " + $newVersion.ToString())
Set-VersionFile -File $versionFilePath -Version $newVersion

$otherVersionFilePath = $scriptDirectory + "\Dependencies\Other.Component.Version.txt"
$otherVersion = Get-VersionFile -File $otherVersionFilePath
Write-Host ("Other Component Reference Version: " + $otherVersion.ToString())

Set-VersionInAssemblyInfo -File ($scriptDirectory + "\MyProduct.Assembly1\Properties\AssemblyInfo.cs") -Version $newVersion
Set-VersionInAssemblyInfo -File ($scriptDirectory + "\MyProduct.Assembly2\Properties\AssemblyInfo.cs") -Version $newVersion
Set-VersionInAssemblyInfo -File ($scriptDirectory + "\MyProduct.Assembly3\Properties\AssemblyInfo.cs") -Version $newVersion
Set-VersionInAssemblyReference -File ($scriptDirectory + "\MyProduct.Assembly3\App.config") -AssemblyName "MyProduct.Assembly2" -Version $newVersion
Set-VersionInAssemblyInfo -File ($scriptDirectory + "\MyProduct.Assembly4\Properties\AssemblyInfo.cs") -Version $newVersion
Set-VersionInBindingRedirect -File ($scriptDirectory + "\MyProduct.Assembly4\Web.config") -AssemblyName "Other.Component" -Version $otherVersion
Set-VersionInWixGlobal -File ($scriptDirectory + "\MyProduct.Setup\Global.wxi") -Version $versionString

exit 0

Lastly the Version.cmd invokes the PowerShell Version.ps1 correctly from the command line, e.g. I usually open a command prompt using the Visual Studio Power Tools toolbox menu item then run either my build script (which calls Version.cmd) or just Version.cmd to increment the version alone.

@powershell -File "%~dp0Version.ps1"

Most of this script remains static. The functions provided in this example will edit the “AssemblyVersion” attribute in an “AssemblyInfo.cs” or a WIX “ProductVersion” variable in a WIX file (typically stored in a shared include file named Global.wxi). Finally the last lines of the script are customized for your actual solution, calling the relevant functions to update the version number in files of your solution.

Now each time you build your solution you have an identifiable version which is exactly same across all assemblies and even the MSI. For example, the third build of the above solution on the 23rd April 2013 will produce the following version:

2.5.1303.23003

This is broken down as follows:

  • 2.5 comes from the static major and minor version you typed in the Version.txt.
  • 1303 comes from the year and month, 2013 and 03 for March.
  • 23003 is the day, the 23rd, multiplied by 1000 to give us three predictable digits for the build number, 003 is the third build on this day, with up to 999 possible before clashing with builds on the next day (should never occur then).

Following this pattern, you could add the version files to every solution you create and maintain it simply by adding a line or two each time you add projects and calling it from any build script or system you may have. If you have new types of files to edit add your own functions and call them at the end. The general solution of using regular expression replacements should suffice for most use cases. Other more complex requirements could use an XPath query to edit configuration files where it is not possible to identify the version by string patterns alone (when a structured XML path is more appropriate to accurately locate the version entries).

I like this solution because it allows you to use best practice versioning without the overhead. And even if you do have a proper continuous build system in place, using this in place of any proprietary versioning add-on or Visual Studio extension is much simpler, flexible (e.g. use offline or when build server is out of service)  and easy to maintain, reducing your 3rd party product dependencies.

Scorching Source Code

After much experience of the issues related to switching between different PCs with Visual Studio source bound solutions, I was pleased to find the “scorch” feature of Visual Studio Team Foundation Power Tools.

Previously Visual Studio just didn’t clean itself up properly. This would leave duplicate and extra files when deletes or renames occurred on other PCs or by other users. The next developer could then mistakenly open or edit the wrong file, potentially losing changes without knowing it. Knowledge of such issues discourages developers from making proactive refactoring, e.g. for readability, consistency and reduced maintenance costs.

Another major issue was the failure of Visual Studio to update local files even when the current copy was out of date or missing. The only solution to this was to “Get Specific Version” with the option to get files even when they should exist. An assumption is apparently programmed into Visual Studio to only use the last known source control status, not bothering to check the actual files.

The SCORCH command is a combination of the TREECLEAN command which deletes extra files, plus verification that all local files are the correct version. Deleting extra files will also eliminate any compiled object, binary, test results and other outputs. This significantly reduces the size of your local source copy in case you want to take a backup or have limited disk space. Removing intermediary files can help avoid issues when shared components are updated but a local cache of older versions still exist.

With all these benefits, I’d strongly recommend that “scorching your source” (as I like to call it) should be a best practice performed regularly by developers whether in a team or stand-alone switching between different PCs. It is especially important to do before going away to work offline, checking-in offline changes, coming back from holiday or any other situation where major changes to the dependencies or structure of the source may have occurred.

Here’s how you do it:

  1. Make sure you installed the Team Foundation Power Tools. Best place to do this is via the “Tools” – “Extension Manager” (NuGet) or directly here:
    http://visualstudiogallery.msdn.microsoft.com/c255a1e4-04ba-4f68-8f4e-cd473d6b971f
  2. Open a command prompt.
  3. Enter the command “tfpt scorch <path> /recursive /diff” where “<path>” is the root directory of your source collection.
  4. Wait for it to scan your source tree, review changes (but no extra effort is usually required), then confirm to clean-up and update your local files.
  5. Repeat the command for any other collections/source root paths you have from your Team Foundation Server, e.g. sometimes one for each customer or business subsidiary.
  6. Run “tfpt scorch /?” for further options. It can be automated, although you shouldn’t do that unless you also go online/shelve/check-in any unsaved changes first.

Fix for XML Serialization Assembly Generation in Visual Studio 2010

XML serialization assembly generation greatly improves start-up performance of web service proxies. The Framework already supports generation and use of serialization assemblies via the SGEN command or MSBuild task. Visual Studio also purportedly supports this “automatically”. However it doesn’t work because by default it decides whether or not to generate the assemblies based on a proxy type.

However there is a workaround, documented here:

http://stackoverflow.com/a/8798289/1080914

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
     <!-- Snip... -->
     <GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>
     <SGenUseProxyTypes>false</SGenUseProxyTypes>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
     <!-- Snip... -->
     <GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>
     <SGenUseProxyTypes>false</SGenUseProxyTypes>
   </PropertyGroup>

It is necessary to edit the MSBuild XML of the Visual Studio projects where you have XML serializable types to fix the issue with proxy types preventing SGEN execution. The correct answer is hence not the first one in the above forum thread, it is the last one from http://stackoverflow.com/users/94928/heavyd who correctly points out the most graceful way to restore original Visual Studio functionality.

If the “use proxy types” were available in the Visual Studio GUI developers could have fixed this without resorting to hidden project edits. This issue has still not been fixed in Visual Studio 2012.