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

Friday, October 5, 2007

Missing MOSS 2007 Functionality

So having been using MOSS 2007 for the past 3 months now, I figured I'd put my own stake in the ground as to some things that would be quite handy to be built into the product. Yes, there are probably ways to do some custom webparts to handle some of these, but IMHO, these are pieces of functionality that should be part of the base product. Hopefully they make it into the next version...
  1. List View Security
    • This follows some of the same logic behind the use of a database VIEW. An administrator should be able to create a SharePoint list view with a row-level filter and then secure the list of views to the appropriate folks (to the point of hiding views a user doesn't have access to use).
    • This would allow various business scenarios to be simply handled via view security. For instance
      • hiding items based on the state/status code of an item to augment workflows
      • storing heterogeneous content types, but securing which are visible to whom
  2. List Item Column Security
    • This is very similar to the view security above, but at a column-level, rather than row-level (vertical security versus horizontal security). In fact, this could work very similarly to how views currently work, but could be called item views, instead of list views.
    • Yes, you could write custom ASPX pages to handle this, but the point of this functionality is to give a non-developer the ability to create this kind of functionality.
  3. Custom Attributes On Lists
    • This is more or less a property bag (key/value pair bucket) that can be populated via the GUI by an end user, or via the API.
    • This would be primarily useful to a developer who wants to track/manipulate additional details about a list, but enable end-users to make changes to those values when necessary.