Internet Architect by day, environmentalist by night: Jim Schwartz @ imason.
Home » What We're Thinking » Jim @ imason
We’ve now surpassed 50 InfoPath forms on the InfoPath 2007 / SharePoint 2007 project I’m currently engaged on. If you’ve done InfoPath/SharePoint development before, you’ll know that it can be really painful to publish forms and then deploy them to SharePoint.
In Part 1 of this 2 blog series, I showed you how to automate the deployment of InfoPath forms to SharePoint using NAnt. This time around I’ll show you how to automate the publishing of an InfoPath form.
If you’ve published an InfoPath form to an Administrator-approved form template before, you’ll be all too familiar with the process of clicking next a whole bunch of times in order to publish the form. Why should you have to go through all these manual steps every time you make a change to a form?
To automate this, I first found some VB code on this blog article to publish a form. I then converted it to C#, and built it as a custom NAnt task. So now I can publish a form by simply calling a custom NAnt task like this:
<publishform outputfolder="c:\mypublishedforms" formname="MyFormName.xsn" cabsdkpath="..\tools\CabSDK\BIN" xsffilename="manifest.xsf" formsourcepath="..\trunk\src\SourceForms\MyFormName\InfoPath Form Template\" />
In order to use this approach, you’ll need to either build your InfoPath form using the Visual Studio template, or you’ll need to extract the form source files from the XSN. To extract the source files from the XSN, choose “File” -> “Save As Source Files”. Then you can work with your form template by right clicking on “manifest.xsf” and choosing “Design”.
Here is the code to build the custom NAnt task:
using System; using System.Diagnostics; using System.IO; using System.Xml; using NAnt.Core; using NAnt.Core.Attributes; namespace My.Build.Tasks { [TaskName("publishform")] public class PublishFormTask : Task { [TaskAttribute("outputfolder", Required = true)] public string OutputFolder { get; set; } [TaskAttribute("formname", Required = true)] public string FormName { get; set; } [TaskAttribute("cabsdkpath", Required = true)] public string CabSdkPath { get; set; } [TaskAttribute("xsffilename", Required = true)] public string XsfFileName { get; set; } [TaskAttribute("formsourcepath", Required = true)] public string FormSourcePath { get; set; } protected override void ExecuteTask() { string makeCabPath = CabSdkPath + "\\MAKECAB.EXE"; BuildCabFile(makeCabPath); } private void BuildCabFile(string makeCabExe) { try { if (!File.Exists(makeCabExe)) { throw new Exception(string.Format("CABSDK not found. Please check your CABSDK property value. Couldn't find makecab.exe @ '{0}'", makeCabExe)); } var tempName = Path.GetFileNameWithoutExtension(Path.GetTempFileName()); //var tempName = Path.GetTempFileName(); var tempFolder = Path.GetTempPath(); var tempPath = PathCombine(tempFolder, tempName); var ddfString = string.Empty; ddfString += ".Set DiskDirectoryTemplate='" + tempFolder + "'" + Environment.NewLine; ddfString += ".Set CabinetNameTemplate='" + tempName + "'" + Environment.NewLine; var xsfDom = new XmlDocument(); xsfDom.Load(PathCombine(FormSourcePath, XsfFileName)); var nm = InitNamespaceManager(xsfDom); ddfString += QuoteString(PathCombine(FormSourcePath, XsfFileName)) + Environment.NewLine; var fileNodes = xsfDom.SelectNodes("/xsf:xDocumentClass/xsf:package/xsf:files/xsf:file/@name", nm); for (int i = 0; i <= fileNodes.Count - 1; i++) { ddfString += QuoteString(PathCombine(FormSourcePath, fileNodes[i].InnerText)) + Environment.NewLine; } var ddfPath = PathCombine(tempFolder, "makecab.ddf"); SaveToFile(ddfString, ddfPath); ShellExecute(QuoteString(makeCabExe), "/V1 /F " + QuoteString(ddfPath)); File.Delete(ddfPath); if (File.Exists(PathCombine(OutputFolder, FormName))) { File.Delete(PathCombine(OutputFolder, FormName)); } File.Move(tempPath, PathCombine(OutputFolder, FormName)); string[] oScratchFiles = new string[] { "setup.inf", "setup.rpt" }; foreach (string strScratchFile in oScratchFiles) { if (File.Exists(strScratchFile)) { File.Delete(strScratchFile); } } } catch (Exception ex) { throw new Exception("Could not create XSN file from files.", ex); } } private static void SaveToFile(string data, string filePath) { var fs = File.Create(filePath); fs.Close(); var TextStream = new StreamWriter(filePath); TextStream.Write(data); TextStream.Flush(); TextStream.Close(); } private static void ShellExecute(string command, string args) { var info = new ProcessStartInfo(command, args); info.UseShellExecute = false; var process = Process.Start(info); process.WaitForExit(60000); } private static string QuoteString(string s) { return "\"" + s + "\""; } private static string PathCombine(string folder, string fileName) { return Path.Combine(folder, fileName); } public static XmlNamespaceManager InitNamespaceManager(XmlDocument xmlDOMDoc) { XmlNamespaceManager xnmMan; xnmMan = new XmlNamespaceManager(xmlDOMDoc.NameTable); foreach (XmlAttribute nsAttr in xmlDOMDoc.DocumentElement.Attributes) { if (nsAttr.Prefix == "xmlns") xnmMan.AddNamespace(nsAttr.LocalName, nsAttr.Value); } return xnmMan; } public void RunTask() { ExecuteTask(); } } }
using System;
using System.Diagnostics;
using System.IO;
using System.Xml;
using NAnt.Core;
using NAnt.Core.Attributes;
namespace My.Build.Tasks
{
[TaskName("publishform")]
public class PublishFormTask : Task
[TaskAttribute("outputfolder", Required = true)]
public string OutputFolder { get; set; }
[TaskAttribute("formname", Required = true)]
public string FormName { get; set; }
[TaskAttribute("cabsdkpath", Required = true)]
public string CabSdkPath { get; set; }
[TaskAttribute("xsffilename", Required = true)]
public string XsfFileName { get; set; }
[TaskAttribute("formsourcepath", Required = true)]
public string FormSourcePath { get; set; }
protected override void ExecuteTask()
string makeCabPath = CabSdkPath + "\\MAKECAB.EXE";
BuildCabFile(makeCabPath);
}
private void BuildCabFile(string makeCabExe)
try
if (!File.Exists(makeCabExe))
throw new Exception(string.Format("CABSDK not found. Please check your CABSDK property value. Couldn't find makecab.exe @ '{0}'", makeCabExe));
var tempName = Path.GetFileNameWithoutExtension(Path.GetTempFileName());
//var tempName = Path.GetTempFileName();
var tempFolder = Path.GetTempPath();
var tempPath = PathCombine(tempFolder, tempName);
var ddfString = string.Empty;
ddfString += ".Set DiskDirectoryTemplate='" + tempFolder + "'" + Environment.NewLine;
ddfString += ".Set CabinetNameTemplate='" + tempName + "'" + Environment.NewLine;
var xsfDom = new XmlDocument();
xsfDom.Load(PathCombine(FormSourcePath, XsfFileName));
var nm = InitNamespaceManager(xsfDom);
ddfString += QuoteString(PathCombine(FormSourcePath, XsfFileName)) + Environment.NewLine;
var fileNodes = xsfDom.SelectNodes("/xsf:xDocumentClass/xsf:package/xsf:files/xsf:file/@name", nm);
for (int i = 0; i <= fileNodes.Count - 1; i++)
ddfString += QuoteString(PathCombine(FormSourcePath, fileNodes[i].InnerText)) + Environment.NewLine;
var ddfPath = PathCombine(tempFolder, "makecab.ddf");
SaveToFile(ddfString, ddfPath);
ShellExecute(QuoteString(makeCabExe), "/V1 /F " + QuoteString(ddfPath));
File.Delete(ddfPath);
if (File.Exists(PathCombine(OutputFolder, FormName)))
File.Delete(PathCombine(OutputFolder, FormName));
File.Move(tempPath, PathCombine(OutputFolder, FormName));
string[] oScratchFiles = new string[] { "setup.inf", "setup.rpt" };
foreach (string strScratchFile in oScratchFiles)
if (File.Exists(strScratchFile))
File.Delete(strScratchFile);
catch (Exception ex)
throw new Exception("Could not create XSN file from files.", ex);
private static void SaveToFile(string data, string filePath)
var fs = File.Create(filePath);
fs.Close();
var TextStream = new StreamWriter(filePath);
TextStream.Write(data);
TextStream.Flush();
TextStream.Close();
private static void ShellExecute(string command, string args)
var info = new ProcessStartInfo(command, args);
info.UseShellExecute = false;
var process = Process.Start(info);
process.WaitForExit(60000);
private static string QuoteString(string s)
return "\"" + s + "\"";
private static string PathCombine(string folder, string fileName)
return Path.Combine(folder, fileName);
public static XmlNamespaceManager InitNamespaceManager(XmlDocument xmlDOMDoc)
XmlNamespaceManager xnmMan;
xnmMan = new XmlNamespaceManager(xmlDOMDoc.NameTable);
foreach (XmlAttribute nsAttr in xmlDOMDoc.DocumentElement.Attributes)
if (nsAttr.Prefix == "xmlns")
xnmMan.AddNamespace(nsAttr.LocalName, nsAttr.Value);
return xnmMan;
public void RunTask()
ExecuteTask();
Earlier this year I headed up a development team to build 2 Silverlight applications for The Weather Network. The first application to be released a few weeks ago is “WeatherEye Golf” (press release) which provides weather information for over 17,000 golf courses in Canada and the U.S. The second app that was just released last week is called “TrafficEye” (press release) which provides up-to-the-minute traffic information for the Greater Toronto Area (With plans to expand out to other areas). It includes traffic flow, highway conditions, traffic incidents, and traffic cameras. You can save up to 10 of your favourite highway cameras (If you download it now, you will be entered to win 1 of 2 Xbox 360 Game Consoles!!).
These applications were quite unique because they needed to be downloadable and run on the desktop. Silverlight has several benefits over the other options we had explored. First of all, Silverlight runs as a web application, so it’s very easy to update in the future; you don’t need to push out updates to the desktop, and the user doesn’t need to re-install anything. One of the issues with the flagship WeatherEye product (Which is purely a desktop application) is that they need to push out updates to users which can be very time consuming since there are approximately 2 million active users of the WeatherEye product. With Silverlight, the IT Staff simply needs to update the Web Application and the next time the user runs the app, they will have the latest version.
Another benefit of Silverlight is the fact that you can easily port the application over to any web site, by simply adding an <object> tag to the HTML. Whether it’s a PHP page, ASPX, JSP, HTML, you can display the Silverlight application anywhere.
The third benefit of Silverlight is the platform independence. We have a Mac version of the TrafficEye application that we are planning to release in the near future. It’s pretty neat to see a Microsoft solution running on a Mac desktop.
To display the Silverlight application on the desktop we built a Visual Basic 6 application that displays a browser frame, (while in Mac we are using a Cocoa application developed using the XCode tools). The desktop shim is very basic and allows us to consolidate the application functionality in Silverlight. Silverlight uses Isolated Storage to persist user settings. We chose VB6 so that it will run on various desktop configurations without requiring the .NET framework, and also to keep the installation as small as possible.
Overall I’m happy with the result of the applications. There are a couple kinks on certain desktop configurations, but overall the response from the public has been positive so far.