Tuesday, October 23, 2007

Automatic Updating of AssemblyFileVersion in C# Projects

So my good buddy and co-worker Stacy Draper and I put together a solution today to handle versioning of .NET assemblies used for SharePoint web part development. Maybe this has been done before, maybe even better than this, but a quick Google search didn't turn up squat for us, or at least anything we liked, so we created our own solution.

The general problem revolves around the fact that the AssemblyVersion attribute in .NET assemblies is used by other assemblies when referencing strong named/signed assemblies stored in the GAC. Now you don't have to put your SharePoint assemblies in the GAC, but in most all cases, it simply makes the most sense. If you change the AssemblyVersion of an already installed and used over and over and over again web part, you're in a world of hurt if you arbitrarily change the AssemblyVersion to something new. At that point, you're faced with having to update 10's to 100's to 1000's of web part pages that reference the original web part. That's not to say there aren't times when it makes sense to do so, but for day to day development and deployment scenarios where it really isn't a new version of the web part, but itis an updated version of the code, you still want to be able to have a very noticeable way to see what version is currently installed on your portal.

So that takes us to what Stacy and I whipped up today. I created a script that updates your AssemblyInfo.cs file, setting the AssemblyFileVersion attribute to a time-stamped version number, based off of the AssemblyVersion attribute's value, of when the assembly was built. Stacy created a script that updates the feature.xml file that is part of the SharePoint feature with the new version number of the AssemblyFileVersion to be displayed within the description of the feature. This is so you can also see that version info from within SharPoint, in addition to viewing the file properties. Both of these scripts are then configured to run in your pre-build events for your C# project. What follows is my pre-build commands and the content of my VBS script. I place my VBS file in a folder named UTIL inside my C# project. See Stacy's blog for his stuff.


CD "$(ProjectDir)UTIL"
cscript /nologo UpdateAssemblyFileVersion.vbs "$(ProjectDir)Properties\AssemblyInfo.cs"

'------------------------------------------------------------------------------
'--- UpdateAssemblyFileVersion.vbs
'------------------------------------------------------------------------------
'--- Author - Jim Sally
'--- Date - 2007.10.23
'------------------------------------------------------------------------------
'--- This updates the AssemblyFileVersion attribute in the AssemblyInfo.cs file
'--- of a given C# project. The AssemblyFileVersion is updated to contain the
'--- first two digits of the AssemblyVersion, with YMMDD.HHmm as the last two
'--- digits.
'------------------------------------------------------------------------------
Option Explicit

Dim fileHeader
fileHeader = "UpdateAssemblyFileVersion.vbs :: "

EnsureCommandLineArgumentExists
EnsureAssemblyInfoFileExists WScript.Arguments(0)
UpdateAssemblyFileVersion WScript.Arguments(0)

Private Sub EnsureCommandLineArgumentExists()
If WScript.Arguments.Count <> 1 Then
WScript.Echo fileHeader & "You must supply the path to the AssemblyInfo.cs file as the only argument on the command line"
WScript.Quit 1
End If
End Sub

Private Sub EnsureAssemblyInfoFileExists(AssemblyInfoFileName)
Dim fso

Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FileExists(AssemblyInfoFileName) Then
WScript.Echo fileHeader & "AssemblyInfo.cs does not exist at the path provided -> " & AssemblyInfoFileName
WScript.Quit 1
End If

Set fso = Nothing
End Sub

Private Sub UpdateAssemblyFileVersion(AssemblyInfoFileName)
Dim assemblyInfo, newAssemblyInfo

assemblyInfo = GetContentsOfAssemblyInfoFile(AssemblyInfoFileName)
newAssemblyInfo = UpdateAssemblyInfoText(assemblyInfo)

If newAssemblyInfo <> assemblyInfo Then
SaveContentsOfAssemblyInfoFile AssemblyInfoFileName, newAssemblyInfo
End If
End Sub

Private Function GetContentsOfAssemblyInfoFile(AssemblyInfoFileName)
Dim fso, file

Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.OpenTextFile(AssemblyInfoFileName)
GetContentsOfAssemblyInfoFile = file.ReadAll()
file.Close

Set file = Nothing
Set fso = Nothing
End Function

Private Sub SaveContentsOfAssemblyInfoFile(AssemblyInfoFileName, infoText)
Dim fso, file

Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.CreateTextFile(AssemblyInfoFileName, true)
file.Write(infoText)
file.Close

Set file = Nothing
Set fso = Nothing
End Sub

Private Function UpdateAssemblyInfoText(AssemblyInfoText)
Dim posAV, posAFV, posEolAV, posEolAFV, aNAV
Dim originalAssemblyVersionText, originalAssemblyFileVersionText, newAssemblyFileVersionText

posAV = InStr(AssemblyInfoText, "AssemblyVersion")
posAFV = InStr(AssemblyInfoText, "AssemblyFileVersion")

If Not IsNull(posAV) And Not IsNull(posAFV) And posAV > 0 And posAFV > 0 Then
posEolAV = InStr(posAV, AssemblyInfoText, "]")
posEolAFV = InStr(posAFV, AssemblyInfoText, "]")

originalAssemblyVersionText = Mid(AssemblyInfoText, posAV, posEolAV - posAV)
originalAssemblyFileVersionText = Mid(AssemblyInfoText, posAFV, posEolAFV - posAFV)

aNAV = Split(Split(originalAssemblyVersionText, """")(1), ".")
newAssemblyFileVersionText = "AssemblyFileVersion(""" & GetNewFileVersionNumber(aNAV) & """)"

If originalAssemblyFileVersionText <> newAssemblyFileVersionText Then
UpdateAssemblyInfoText = Replace(AssemblyInfoText, originalAssemblyFileVersionText, newAssemblyFileVersionText)

WScript.Echo fileHeader & "Updated attribute from '" & originalAssemblyFileVersionText & "' to '" & newAssemblyFileVersionText & "'"
WScript.Echo
Else
WScript.Echo fileHeader & "AssemblyFileVersion attribute already '" & newAssemblyFileVersionText & "'"
WScript.Echo
WScript.Quit
End If
Else
WScript.Echo fileHeader & "AssemblyVersion or AssemblyFileVersion attribute not found"
WScript.Echo
WScript.Quit 1
End If
End Function

Private Function GetNewFileVersionNumber(versionArray)
GetNewFileVersionNumber = versionArray(0) & "." & versionArray(1) & "." & GetVersionYMMDD() & "." & GetVersionHHmm()
End Function

Private Function GetVersionYMMDD()
Dim y, m, d

y = right(year(now()),1)
If y = 0 Then
y = 10
ElseIf y > 6 Then
m = (y - 6) * 12
y = 6
End If

m = Right("00" & (m + Month(now)), 2)
d = Right("00" & Day(now), 2)

GetVersionYMMDD = y & m & d
End Function

Private Function GetVersionHHmm()
GetVersionHHmm = Right("00" & Hour(Now()), 2) & Right("00" & Minute(Now()), 2)
End Function

2 comments:

Stacy said...

Here's my contribution
http://www.wildwires.com/Blog/PermaLink,guid,2b6de457-bc27-42f9-94cc-6d48b89d25fb.aspx
This was certainly a fun!

BigJimInDC said...

Just as a quick follow up comment, I wanted to document our decision making process to use VBS files, as I'm sure everyone will have an opinion on this.

The list of options Stacy and I came up with included custom MSBuild Actions and PowerShell scripts. Our decision was based on VBS scripts requiring zero additional installations, along with extreme ease of changing the numbering scheme inside the script (for those of you that don't like what we came up with).

PowerShell would require a download and install if you don't already have it installed. And if you wanted to modify the custom Action, you'd have to rebuild and redeploy the assembly.