Thursday, September 25, 2014

Automating ClickOnce deployment and WCF hosting

I'm at the point where I need to deploy my new WPF application and it's WCF services as part of an automated build. This involves learning a little about MSBuild, a complex application that Microsoft has somehow failed to document in any meaningful way. You can find it in the framework folder (Windows\Microsoft.Net\Framework\v<frameworkversion>\)

MSBuild is used by Visual Studio to perform build and publish functions but can be run from the command line. It uses the project file (vbproj or csproj) together with some parameters from the command line to build and/or publish specific configurations. For example...

msbuild Purchasing.vbproj /t:Publish /p:Configuration=Release /p:PublishDir="\\<production server>\Purchasing\PurchasingClient\\" /v:normal > Log.txt

This command instructs msbuild to perform a release build (using the PropertyGroup with a configuration parameter = Release) and then publish the application to the specified shared folder on the production server. The output is written to a log file. I could not get it to publish to a URL, but that might just be a matter of configuring publish to URL in visual studio, looking at the project file, and looking for the PublishURL task. Then you could override it with a /p:PublishURL parameter.

Note the double slash at the end of PurchasingClient\\. This gets around an odd bug. It seems as though a single slash following by a quote is being interpreted as an attempt to embed the quote into the parameter value. A single slash throws an invalid character error. A double slash does what we want.

The big problem is that MSBuild cannot increment the publish version which upsets the clients when they try to download the application in online-only mode. The client reports an error saying that there is already an application with the same version number installed. Although Visual Studio can auto increment publish version, this is not done via MSBuild so we need to find another way.

All we need to do is increment the content of an element with an XPath of \Project\PropertyGroup\ApplicationRevision. There are several ways we can do this. In the end I found it easier to write a very simple app that takes an XML file name and one or more XPaths. If the content of the elements matching those XPaths is numeric, I increment it and save the file. This is only needed for the WPF applications. I called the application ProjectVersionIncrementer. There's probably a better way to handle the namespace issue, but I was in a hurry.

Module ProjectVendorIncrementer Sub Main(Args() As String) Dim XMLFileName As String Dim XMLFile As New System.Xml.XmlDocument Dim XPath As String Dim sValue As String Dim IsIncremented As Boolean = False Dim mgr As System.Xml.XmlNamespaceManager Try XMLFileName = Args(0) XMLFile.Load(XMLFileName) mgr = New System.Xml.XmlNamespaceManager(XMLFile.NameTable) mgr.AddNamespace("m", XMLFile.DocumentElement.NamespaceURI) Console.WriteLine("Loaded " & XMLFileName) For i As Integer = 1 To Args.Count - 1 Console.WriteLine("Searching for XPath " & Args(i)) XPath = Args(i).Replace("/", "/m:").Replace("/m:/m:", "//m:") For Each XMLNode As System.Xml.XmlNode In XMLFile.DocumentElement.SelectNodes(XPath, mgr) sValue = XMLNode.InnerText If IsNumeric(sValue) Then sValue = (CDec(sValue) + 1).ToString() Console.WriteLine(" Incremented " & XMLNode.Name & " to " & sValue) XMLNode.InnerText = sValue IsIncremented = True End If Next Next If IsIncremented Then XMLFile.Save(XMLFileName) Console.WriteLine("Saved Changes") End If Catch ex As Exception Console.WriteLine("ERROR:" & ex.Message) Finally Console.WriteLine("Done") End Try End Sub End Module

So to increment the version numbers of all configurations of a local file called Purchasing.vbproj you would add the following line at the beginning of your publish bat file.

ProjectVersionIncrementer.exe Purchasing.vbproj //Project/PropertyGroup/ApplicationRevision

Follow this with the msbuild line from the top of the blog post and you've got a bat file that will increment the publish number and publish to the server of your choice.

Automating the publication of WCF services is similar, but different. You don't have to worry about versioning because you're not using ClickOnce. On the other hand msbuild won't handle the deployment. There's a utility called MSDeploy that's supposed to do that but it kept complaining about ACLs so, as it's basically just copying files to a known location I used xcopy instead.

The bat file looks like this and is run from the vbproj folder,,,

C:\Windows\Microsoft.Net\Framework\v4.0.30319\msbuild.exe WCFDatabase.vbproj /t:Package /p:Configuration=Release /v:normal > Log.txt

xcopy obj\Release\Package\PackageTmp \\<Production Server>\Purchasing\WCFDatabase /i /s /y

Although our development boxes are all running Windows 7, our test and production servers are still on Windows Server 2003 SP 2 which only runs IIS 6.0 and framework 4.0. Configuring IIS 6.0 to host WCF services requires some manual labor.


  1. Register the framework by browsing to the framework version and running aspnet_regiis.exe /i /enable
  2. Find ServiceModelReg.exe and run it with /r /x /v /y
  3. In IIS management studio create a new virtual directory that points to the directory you copied the WCF service svc file to. Make sure the virtual directory is configured for the correct version of the framework.
  4. Restart IIS using iisreset.exe
I created a single shared directory under Inetpub/wwwroot/Purchasing and then a new subfolder to host each WCF service. The xcopy for the service copies the files via the shared directory to the subfolder. I also created a new web application for each service that points to the service's subfolder. The web applications are configured for read/write/execute. Each one must be in a classic app pool.

If, after doing this, you still get 405 Not Allowed errors it might mean your .svc extensions didn't get registered, Don't panic - it's not difficult but it is tedious.

Right-click on each of the WCF service virtual directories and select Properties. Then click on the [Configuration...] button. If your Application Extensions list does not include .svc you will need to add it. Click [Add...]

Type "c:\windows\microsoft.net\framework\v4.0.30319\aspnet_isapi.dll" into the Executable: textbox and ".svc" into the Extension: textbox. Leave the defaults in place. Be sure to use the correct version of the framework in the executable path. Repeat for each WCF service.

No comments:

Post a Comment