Jim @ imason

Sharing tips and tricks with the development community.
Automated Publishing of InfoPath Forms (Part 2 of 2)

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();

        }

    }

}

 

 

Silverlight in Desktop Applications

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.

TrafficEye WeatherEye Golf

How to Deploy InfoPath 2007 Forms with NAnt (Part 1 of 2)

I've been using NAnt a lot lately for deploying components to development and integration machines. The common misconception that I've heard from reluctant SharePoint folks around here is that we should be packaging SharePoint solutions in WSP (SharePoint solution files) instead of deploying components with NAnt. That's all great, I'm totally pro-WSP and all, but the thing people don't realize is that whether or not you're using WSP files, NAnt still compliments SharePoint deployments very well.

Even if I'm simply running STSADM commands, I would still use NAnt instead of writing batch files. NAnt can do so much more than WSP files are capable of doing such as: Modifying data in Web.Config files (xmlpeek/xmlpoke), creating Virtual Directories on your IIS site (mkiisdir), copying files (copy), etc.

One use I've found for NAnt is to deploy an InfoPath 2007 form to Central Administration and activate to my site collections, and adding Data Connection Library (.udcx) files to Central Administration. It was always a hassle to do that manually, and batch files are so 1980's.

So here is some NAnt XML that I've written to deploy InfoPath forms and Data Connections:

 

<property name="stsadm.exe" value="C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN\STSADM.exe" />

 

<target name="DeployInfoPathForms" description="Deploys the InfoPath forms to SharePoint">

      <property name="formName" value="MyInfoPathForm.xsn" />

      <call target="DeployForm" />

      <exec program="IISRESET" />

</target>

 

<target name="DeployForm" description="Deploys an InfoPath form">

      <exec program="${stsadm.exe}" failonerror="false">

            <arg line="-o deactivateformtemplate" />

            <arg line="-url http://mysharepointsite" />

            <arg line="-filename &quot;.\PublishedForms\${formName}&quot;" />

      </exec>

      <exec program="${stsadm.exe}" failonerror="false">

            <arg line="-o removeformtemplate" />

            <arg line="-filename &quot;.\PublishedForms\${formName}&quot;" />

      </exec>

      <exec program="${stsadm.exe}" failonerror="false">

            <arg line="-o execadmsvcjobs" />

      </exec>

      <exec program="${stsadm.exe}">

            <arg line="-o uploadformtemplate" />

            <arg line="-filename &quot;.PublishedForms\${formName}&quot;" />

      </exec>

      <exec program="${stsadm.exe}">

            <arg line="-o execadmsvcjobs" />

      </exec>

      <exec program="${stsadm.exe}">

            <arg line="-o activateformtemplate" />

            <arg line="-url http://mysharepointsite "/>

            <arg line="-filename &quot;.PublishedForms\${formName}&quot;" />

      </exec>

</target>

 

<target name="DeployDataConnections" description="Deploys Forms Services Data Connection library files">

      <property name="dataConnectionPath" value=".\config\DataConnections" />

      <property name="dataConnectionFile" value="MyDataConnection.udcx" />

      <call target="DeployDataConnection" />

</target>

 

<target name="DeployDataConnection" description="Deploys a single data connection">

      <copy file="${dataConnectionPath}\${dataConnectionFile}" tofile=".\${dataConnectionFile}" />

      <attrib file="${rootPath}\${dataConnectionFile}" readonly="false" />

      <exec program="${stsadm.exe}">

            <arg line="-o adddataconnectionfile" />

            <arg line="-filename &quot;.\${dataConnectionFile}&quot;" />

            <arg line="-overwrite true"/>

      </exec>

      <delete file=".\${dataConnectionFile}" />

</target>

 

You’ll notice I had to make a copy of the data connection before I could deploy it. This is because the file was read only when it’s checked in source control, so I needed to make a copy and take off the read only attribute using the NAnt attrib task before I could upload the file (Otherwise STSADM will complain).

Now you can call NAnt to deploy all of your forms at once, or you could specify an individual form by calling: “nant.bat –DeployForm –D:formName=MyFormName.xsn” or “nant.bat –DeployDataConnection –D:dataConnectionFile=MyDataConnection.udcx”.

Happy NAnting!

Isolated Storage in Silverlight 2 Beta 2

In Silverlight 2 Beta 1, storing user settings in IsolatedStorage was almost as much of a pain as storing user settings in Vista Gadgets (Except you can write .NET code to stream the data to file instead of using JavaScript).

With the recent release of Silverlight 2 Beta 2, storing data in IsolatedStorage is as easy as storing and retrieving values from a web.config.

To store business objects into IsolatedStorage, I would recommend creating a Class in your Silverlight project to hold all of the user settings. You also need to set the “DataContract” attribute on any classes that you want to persist to IsolatedStorage as well as the “DataMember” attribute for the properties you’d like to persist. This will allow the class to be serialized into IsolatedStorage.

Here is an example of a simple entity class with some data that I’d like to persist:

using System.Runtime.Serialization;

 

namespace My.Silverlight.Application

{

    [DataContract]