One of the more annoying quirks with dealing with SharePoint is that once you develop a solution that requires custom code, all dependencies are then tied to the Strong Name of the compiled DLL. That strong name also includes the version number, usually 1.0.0.0, and cannot be changed without breaking all existing references. This is a good thing from a compatibility perspective but it does make it rather complicated to figure out exactly which version of a DLL is in use.
You can open up the WSP and then use the timestamps and take a guess at the version but it is not always reliable. One popular option is to manually set the AssemblyFileVersion attribute in the AssemblyInfo.cs file which then lets you simply right-click and view the DLL properties to see the information. Changing the AssemblyFileVersion does not impact the strong name of a library. This works well but requires that you remember to actually do it and it becomes very tedious if you want to do this for each build.
Instead, I created a solution that uses PowerShell combined with Visual Studio’s Pre-Build event command line to automatically update the AssemblyFileVersion for each and every build. It even will check the file out of TFS for you. The original concepts came from this post but I have made extensive modifications and added functionality.
param($PathToProjectRoot) | |
#used to make it easier to spot the comments from the script in the Build Output window | |
$msgPrefix=" | " | |
#useful when testing the script - simply point this to the root of a folder that contains a project | |
#and the script can be run via PowerShell or ISE without having to constantly build via VS | |
$defaultProjectRoot="C:\projects\dwise\Samples\PowerShell\Project.Folder" | |
#the default location for the TFS files. You may need to update this for your specific installation | |
set-alias tfs "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\tf.exe" | |
write-output "$msgPrefix Beginning IncrementAssemblyFileVersion.ps1" | |
function AssignVersionValue([string]$oldValue, [string]$newValue) { | |
if ($newValue -eq $null -or $newValue -eq "") { | |
$oldValue | |
} else { | |
#placeholder for other functionality, like incrementing, dates, etc.. | |
if ($newValue -eq "increment") { | |
$newNum = 1 | |
try { | |
$newNum = [System.Convert]::ToInt64($oldValue) + 1 | |
} catch { | |
#do nothing | |
} | |
$newNum.ToString() | |
} else { | |
$newValue | |
} | |
} | |
} | |
function SetAssemblyFileVersion([string]$pathToFile, [string]$majorVer, [string]$minorVer, [string]$buildVer, [string]$revVer) { | |
#load the file and process the lines | |
$newFile = Get-Content $pathToFile -encoding "UTF8" | foreach-object { | |
if ($_.StartsWith("[assembly: AssemblyFileVersion")) { | |
$verStart = $_.IndexOf("(") | |
$verEnd = $_.IndexOf(")", $verStart) | |
$origVersion = $_.SubString($verStart+2, $verEnd-$verStart-3) | |
$segments=$origVersion.Split(".") | |
#default values for each segment | |
$v1="1" | |
$v2="0" | |
$v3="0" | |
$v4="0" | |
#assign them based on what was found | |
if ($segments.Length -gt 0) { $v1=$segments[0] } | |
if ($segments.Length -gt 1) { $v2=$segments[1] } | |
if ($segments.Length -gt 2) { $v3=$segments[2] } | |
if ($segments.Length -gt 3) { $v4=$segments[3] } | |
$v1 = AssignVersionValue $v1 $majorVer | |
$v2 = AssignVersionValue $v2 $minorVer | |
$v3 = AssignVersionValue $v3 $buildVer | |
$v4 = AssignVersionValue $v4 $revVer | |
if ($v1 -eq $null) { throw "Major version CANNOT be blank!" } | |
if ($v2 -eq $null) { throw "Minor version CANNOT be blank!" } | |
$newVersion = "$v1.$v2" | |
if ($v3 -ne $null) { | |
$newVersion = "$newVersion.$v3" | |
if ($v4 -ne $null) { | |
$newVersion = "$newVersion.$v4" | |
} | |
} | |
write-host "$msgPrefix Setting AssemblyFileVersion to $newVersion" | |
$_.Replace($origVersion, $newVersion) | |
} else { | |
$_ | |
} | |
} | |
$newfile | set-Content $assemblyInfoPath -encoding "UTF8" | |
} | |
function CheckOutFile([string]$pathToFile) { | |
#Make sure the file is writeable from TFS | |
$fileInfo =[System.IO.FileInfo]$pathToFile | |
# if it is readonly attempt to check it out | |
# this is a shortcut because in my environment, ReadOnly means that it is in TFS | |
# I could force the checkout all of the time but that adds about 5 seconds to the build | |
if ($fileInfo.Attributes -band 1) { | |
Write-Output "$msgPrefix Checking out AssemblyInfo.cs" | |
$coVal = tfs checkout "$pathToFile" | |
if ($coVal -eq $null) { | |
throw "Unable to check out the file: $pathToFile" | |
} | |
} | |
} | |
if ($PathToProjectRoot -eq "" -or $PathToProjectRoot -eq $null) { $PathToProjectRoot=$defaultProjectRoot } | |
$PathToProjectRoot = $PathToProjectRoot.Trim("\") | |
#if you use another .net language, you will need to change this to support that. | |
$assemblyInfoPath = "$PathToProjectRoot\Properties\AssemblyInfo.cs" | |
CheckOutFile $assemblyInfoPath | |
# the values here can be whatever your heart desires | |
$major=$null # $null indicates that whatever value is currently in the file should be used as-is | |
$minor=$null | |
$build=[System.DateTime]::Now.ToString("yyMM") | |
$rev="increment" # special token to increment whatever value it finds in that field | |
SetAssemblyFileVersion $assemblyInfoPath $major $minor $build $rev | |
write-output "$msgPrefix Ending IncrementAssemblyFileVersion.ps1" |
Once that is in place you will need to update the ‘Pre-build event command line’ in the Project Properties in Visual Studio to be what is below:
%WINDIR%\SysNative\WindowsPowerShell\v1.0\powershell.exe "D:\PSScripts\IncrementAssemblyFileVersion.ps1 '$(ProjectDir)'"
The path to the specific version of PowerShell is important because Visual Studio is still a 32-bit application and having it attempt to call the default 64-bit version of PowerShell does not work. Also, I keep all of my scripts under a common folder (/PSScripts) just for the sake of management so you’ll want to point that at your script instead.
What this script can do :
- Automatically check the file out of TFS (naturally, if you dont use TFS or use a different mechanism, you will need to fiddle with the CheckOutFile() function
- The version number can be any content that you can create in Powershell. Dates, times, ticks, whatever. If you want to add some code, you can even read/write the info from external files.
- Specifying the word ‘increment’ for one of the version values (major, minor, build, rev) will take whatever value it finds there and will simply increment it by 1
- Specifying $null for one of the version values tells the script to leave whatever value it finds there
- As it is current implemented, it will only modify the Build and Revision part of the version number, using the Year and Month for the Build and an increment for the Build. Using the Time instead of an increment would have been useful as well but just seemed clunky. However, this can be done easily by updating the $build and $rev variables to the values desired.
I use this approach to update the AssemblyFileVersion but the code could very easily be modified to update any other property in that file.
THIS IS FREAKING AWESOME 🙂
I wrote up a big spiel on Assembly Versioning Strategy about 6 months ago
http://trycatch.me/global-assembly-versioning-strategy-development-workflows-for-net-assemblies/#vs
But one thing that was kicking my butt still was “Remember to bumb the FileVersion revision number”. was just searching to see if there was an automated way to do it with Powershell and stumbled across your article. Excellent stuff.