Udostępnij za pośrednictwem


Sniffing Code in Form Templates

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

Comments

  • Anonymous
    January 11, 2008
    The comment has been removed

  • Anonymous
    January 11, 2008
    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)

  • Anonymous
    January 14, 2008
    The comment has been removed

  • Anonymous
    January 14, 2008
    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

  • Anonymous
    January 16, 2008
    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

  • Anonymous
    January 16, 2008
    The comment has been removed