TFS TeamBuild and Sharepoint WSP deployment (and any post build events for that matter)

We use the SharePoint Visual Studio Project Template on CodePlex to create WSP deployment packages for our SharePoint features. I tend to think of this WSP creation project in the same way as a MSI installer; so we don’t put SharePoint components into the WSP solution itself, it is an extra project in the solution that assembles the components from a variety of other solutions (e.g. web parts, workflows, event receivers, shared libraries for the GAC etc) and builds a single deployable WSP file.

Running locally on a developers PC inside Visual Studio this template has worked well, the only change I make from the default is to alter the WSP projects pre-build event script to xcopy all the files into the correct directories to allow the VBScript files to create the WSP.

In our drive to automation and automatic testing I have been looking at getting the WSP created as part of our TFS Team Build process. It turns out you get a few problems because Visual Studio and Team Build do macro expansion differently.

So my Pre-build event becomes

echo PREBUILD STARTED

rem Check if we running in VS or Teambuild
if not exist "..\..\..\CLIENTLIBRARY\SharedLibProject\bin\$(ConfigurationName)\SharedLibProject.dll" goto tfsbuild

echo Copy from VS locations, in this sample we assume a shared library, a webpart and some javascript
xcopy "..\..\..\CLIENTLIBRARY\SharedLibProject\bin\$(ConfigurationName)\SharedLibProject.dll"  "$(ProjectDir)DLLS\GAC\"  /F /R /Y
xcopy "..\..\..\Web Part\bin\$(ConfigurationName)\*.dll"  "$(ProjectDir)DLLS\GAC\"  /F /R /Y
xcopy "$(SolutionDir)HOST\bin\HOST.dll"  "$(ProjectDir)DLLS\GAC\"  /F /R /Y
xcopy "$(SolutionDir)HOST\json*"  "$(ProjectDir)TEMPLATE\LAYOUTS"  /F /R /Y
xcopy "$(SolutionDir)HOST\*.js"  "$(ProjectDir)TEMPLATE\LAYOUTS"  /F /R /Y

goto end

:tfsbuild
echo Copy from TFS build locations

xcopy "$(outdir)\SharedLibproject.dll"  "$(ProjectDir)DLLS\GAC\"  /F /R /Y

xcopy "$(outdir)\WebPart.Core.dll"  "$(ProjectDir)DLLS\GAC\"   /F /R /Y
xcopy "$(outdir)\WebPart.UI.dll"  "$(ProjectDir)DLLS\GAC\"   /F /R /Y
xcopy "$(outdir)\Host.dll"  "$(ProjectDir)DLLS\GAC\"   /F /R /Y
xcopy "$(SolutionDir)HOST\json*"  "$(ProjectDir)TEMPLATE\LAYOUTS\json*"  /F /R /Y
xcopy "$(SolutionDir)HOST\*.js"  "$(ProjectDir)TEMPLATE\LAYOUTS\*.js"   /F /R /Y

:end

echo PREBUILD COMPLETE




Key points to note here are



  • For Visual Studio you can use Xcopy /s it makes no difference as there are no sub-directories (so you might ask why use it it all, I guess in some cases a generic copy all is easier than specifying a fixed file and directory). This is not the case for Team Build, if you use /s you can get multiple copies of DLLs in sub-directories created. This is because of the way Team Build structures it’s directories. The $(outdir)  is not a subdirectory of the $(solutiondir) as it is in Visual Studio, it is an absolute path defined for the build agents settings where all the outputs for all the projects in the build are assembled. So, depending on the project type, you seem to get sub directories. So it is best to be very specific as to what to copy, avoid wildcards and recursion.
  • When doing a wildcard xcopy as with json* files on Team Build you must specify the copy to file name i.e. json*, if you don’t you get the question ‘is the target a file or a directory’ message which obviously kills the build. This does not occur within Visual Studio.


It is also worth altering the post build event, by default the WSP is created in the project root, but if it is copied to the $(outdir) it ends up in the Team build drop location, so can be picked up by anyone, just like a DLL.



echo POSTBUILD STARTED
rem commented out as the build box does not have SharePoint installed
rem this could be wrappered in the chheck to see if the directory is present if we are on tema build or not
rem XCOPY "$(ProjectDir)TEMPLATE\*" "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE" /S /F /R /Y

echo Run the VBscripts to create the XML files
"$(ProjectDir)CreateManifest.vbs" "$(ProjectDir)" "$(ProjectName)"
"$(ProjectDir)CreateCabDDF.vbs" "$(ProjectDir)" "$(ProjectName)"

echo Build the WSP
cd "$(ProjectDir)"
makecab.exe /F cab.ddf

echo Copy it to the out directory
xcopy *.wsp "$(TargetDir)\*.wsp" /y

echo POSTBUILD COMPLETE


However your problems do not end here. If you build this WSP project locally on a development PC all is fine. However (depending upon you project) it may fail on Team Build, well actually not fail just pause forever. This due to the way that Team Build checks out folders. The WSP project has a folder structure you drop files in that the VBScript files scan to create the manifest and then the WSP. If one of these directories is empty then it is not created on the build box and the VBScript stalls.



The solution is simply just add an extra folder exists check in the CreateCabDDF.vbs file’s EnumFolder method



sub EnumFolder(sFolder, sRelativePath)
    dim oFolder, oFolders, oSub, oFile
    
    rem this is the extra line
    If oFS.FolderExists(sFolder) Then

        set oFolder = oFS.GetFolder(sFolder)
    
        if (sRelativePath = "TEMPLATE") then sRelativePath = ""
        if (sRelativePath = "FEATURES") then sRelativePath = ""
    
        if (sRelativePath <> "") then sRelativePath = sRelativePath + "\"
    
        for each oFile in oFolder.Files
            oDDF.WriteLine """" + oFile.Path + """" + vbTab + """" + sRelativePath + oFile.Name + """"
        next

        if (sRelativePath <> "" and InStr(1, sFolder, "FEATURES") > 0) then
            sRelativePath = "FEATURES\" + sRelativePath
        end if

       for each oSub in oFolder.SubFolders
            EnumFolder oSub.Path, sRelativePath + oSub.Name
       next
    
      end if
    
end sub


Once this is all one the you can build the project in the Team Build