Microsoft InfoPath 2010
The official blog of the Microsoft InfoPath team

Sniffing Code in Form Templates

Sniffing Code in Form Templates

  • Comments 6

With the introduction of InfoPath Forms Services for MOSS 2007, clever management of form template deployment will probably become a must for most IT departments.  You'll want to be sure that form templates are not draining server resources.  You'll especially want to keep an eye on administrator-deployed form templates, as those can achieve fully trusted status and execute arbitrary code on the server.

With that in mind, it's probably a good idea to set up a code review process for InfoPath form templates that will be deployed by the administrator.  To facilitate the process, it might be nice to have a tool around that will tell you whether or not a form template uses custom code.  This is pretty easy to determine, and a tool can be coded up rather quickly, but we figured we'd facilitate the process and provide a sample to get you going.  And here it is…

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.IO;
using System.Diagnostics;

namespace CheckForCode
{
class Program
{
/// <summary>
/// The Main method entry point for the code-checking algorithm.
/// </summary>
/// <param name="args">The command-line arguments for the code-checking algorithm.</param>
public static void Main(string[] args)
{
if (args == null || args.Length == 0)
{
Console.WriteLine("No arguments specified.");
}
else
{
string filePath = args[0];
try
{
Uri fileUri = new Uri(filePath, UriKind.Absolute);
filePath = fileUri.AbsoluteUri;
 
// If the file is an http URL, download it to the local machine.
if (fileUri.Scheme.Equals(Uri.UriSchemeHttp))
{
filePath = CopyFormTemplateLocally(filePath);
}
 
if (File.Exists(filePath))
{
string extension = Path.GetExtension(filePath);
if (extension.Equals(".xsn", StringComparison.CurrentCultureIgnoreCase))
{
// Extract the xsf and check for the root assembly.
string pathToXSF = ExpandManifest(filePath);
CheckXSFForCode(pathToXSF);
}
else if (extension.Equals(".xsf", StringComparison.CurrentCultureIgnoreCase))
{
// Check for the root assembly.
CheckXSFForCode(filePath);
}
}
}
catch(ArgumentException exn)
{
Console.WriteLine("The file path argument is invalid.");
Console.WriteLine("Exception message: " + exn.Message);
}
catch(UriFormatException exn)
{
Console.WriteLine("The file path is not a valid Uri: '" + filePath + "'");
Console.WriteLine("Exception message: " + exn.Message);
}
}
 
Console.WriteLine("Hit any key to exit.");
Console.ReadKey();
}
 
/// <summary>
/// Get the path to the temp folder for the current user.
/// </summary>
private static string TempFolder
{
get
{
string tempFolder = Environment.ExpandEnvironmentVariables("%temp%");
return tempFolder;
}
}
 
/// <summary>
/// Copy a form template at an http URL to a local path.
/// </summary>
/// <param name="absoluteFileUri">The absolute Uri of the form template.</param>
/// <returns>The local path where the form template was downloaded.</returns>
private static string CopyFormTemplateLocally(string absoluteFileUri)
{
string fileName = Path.GetFileName(absoluteFileUri);
string tempFilePath = Path.Combine(TempFolder, fileName);
 
// Download the form template source from the server.
System.Net.WebClient client = new System.Net.WebClient();
client.UseDefaultCredentials = true;
client.Headers.Add("Translate:f");
client.DownloadFile(absoluteFileUri, tempFilePath);
 
return tempFilePath;
}
 
/// <summary>
/// Expand the manifest.xsf file from the specified xsn.
/// </summary>
/// <param name="pathToXSN">The absolute path to the xsn.</param>
/// <returns>The path to the expanded xsf.</returns>
private static string ExpandManifest(string pathToXSN)
{
string tempFolder = TempFolder;
 
Process expand = new Process();
expand.StartInfo.ErrorDialog = false;
expand.StartInfo.UseShellExecute = false;
expand.StartInfo.RedirectStandardOutput = true;
expand.StartInfo.CreateNoWindow = true;
expand.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
expand.StartInfo.FileName = "expand.exe";
expand.StartInfo.Arguments = pathToXSN + " -F:manifest.xsf " + tempFolder;
expand.Start();
 
// Note that the xsf may not be named "manifest" so this could fail if the user
// has extracted the form template files and changed the name of the file.
return Path.Combine(tempFolder, "manifest.xsf");
}
 
/// <summary>
/// Check the xsf at the specified path for a root assembly dll.
/// </summary>
/// <param name="pathToXSF">The absolute path to the xsf document.</param>
private static void CheckXSFForCode(string pathToXSF)
{
string rootAssemblyNameXPath = "/xsf:xDocumentClass/xsf:package/xsf:files/xsf:file[xsf:fileProperties/xsf:property/@value='rootAssembly']/@name";
 
// Load the xsf document.
XmlDocument xsfDocument = new XmlDocument();
xsfDocument.Load(pathToXSF);
 
// Load the xsf namespace.
XmlNamespaceManager nameSpaceManager = new XmlNamespaceManager(xsfDocument.NameTable);
nameSpaceManager.AddNamespace("xsf", "http://schemas.microsoft.com/office/infopath/2003/solutionDefinition");
 
// Navigate to the root assembly, if it exists.
XPathNavigator xsfRootNavigator = xsfDocument.CreateNavigator();
XPathNavigator assemblyFile = xsfRootNavigator.SelectSingleNode(rootAssemblyNameXPath, nameSpaceManager);
 
// Alert for code review.
if (null == assemblyFile)
{
Console.WriteLine("No custom code in form template.");
}
else
{
Console.WriteLine("Code review required! Root assembly name: " + assemblyFile.Value);
}
}
}
}

Just slap this into a console application in Visual Studio 2005, and you'll have a simple application that will accept the Uri of the form template or xsf as input and output "No code" or "Code found!"  With a little customization, something like this could be worked into a workflow that would govern the deployment of administrator-deployed form templates.

And taking it a bit further, you could automatically seek out the source code folder in the "manifest.xsf" file, in the "projectPath" attribute of the "xsf2:managedCode" element.  Note here, though, that it will only be accessible if the form designer stored the VSTA project in a shared location, so you may have to implement some administrator policies to guarantee that this location is accessible.

Forrest Dillaway
Software Design Engineer in Test

Leave a Comment
  • Please add 7 and 5 and type the answer here:
  • Post
  • Hi,

    I am designing a Browser based InfoPath 2007 form.

    Need to have a repeating table with a column generating the auto-numbers (as a serial number) whenever a new row is added.

    I have done this earlier in InfoPath 2003 client, but it does not work in InfoPath 2007 browser form.

    For this, I have added an expression box with function value set to "position()".

    But this throws an error:

    "use of unsupported XPath functions, position()".

    Any suggestions !!!

    Thanks,

    Zullu.

  • Use the following expression for the "ID" column's default value.  Make sure you've checked "Update this value when the result of the formula is recalculated" in the field properties.

    1 + count(../preceding-sibling::my:RepeatingParentNodeName)

  • Thanks for your prompt repley.

    I really appreciate your efforts !!!

    But I apologize, I am unable to completely get the meaning from your expression.

    I have a repeating table on my form which displays the below info as one row:

    - Part No

    - Type Color

    - Size

    Now, when I add a row, I need the Part No column to display the nos 1, 2, 3, and so on.....

    These colmuns are also attached to the underlying data source (sharepoint List).

    Now, as I understand, you want me to place the formula :

    "1 + count(../preceding-sibling::my:RepeatingParentNodeName)".

    But let me inform you that I have my "Part No" as an expression box and not a textbox tied up to any data source.

    Also, I can't seem to find the option "Update this value when the result of the formula is recalculated" as you mentioned. Where do I get that?

    Any further suggestions.

    Thanks for lending me your precious time.

    Zullu.

  • You cannot set default values for a secondary query data connection.  The secondary DOM nodes are always populated from the data retrieved by the query adapter.  Therefore, default values do not make sense in the context of secondary DOM nodes generated for a SharePoint list query data connection.

    My suggestion will only work main DOM nodes where there is a repeating group node (e.g., named "RepeatingParentNodeName").  Then, the formula I specified would be set as the default value for a field (element or attribute) child of the RepeatingParentNodeName node.

    If you can provide more detail regarding your scenario, it's possible there's a workaround.  For example, you could write custom code to copy the secondary DOM node values to an equivalent structure in the main DOM.  Then, my auto-numbering scheme would work...

    Thanks,

    Forrest

  • Thanks Forrest!!!

    Your help is really appreciated.

    It worked when I had my PartNo linked to the Main Data source and used the function provided by you.

    This is cool !

    Thanks again,

    Zullu

  • Back again.

    I thought you may point me in some direction, as I have cracked my head lot since a couple of days now.

    My form is a wizard kind of layout, where I have 3 views presented to the user one after the other, on click of next button.

    The 2nd and 3rd views contain all the controls (textboxes, repeating tables, etc). The first view has just a drop down displaying all the FormIds from an existing sharepoint list named "SubmittedForms".

    Now when a user selects a FormID, all I need to do is pull the record from the "SubmittedForms" list and display them on the form. I guess I have to write code in the drop down "changed" evenet, but how to do this a big question mark for me now.

    Any help regarding this, I am already thankful to you :-)

    Thanks,

    Zullu.

Page 1 of 1 (6 items)