Create InfoPath forms as another user

A customer had an InfoPath form library with hundreds of forms already posted. He encountered an issue where forms were no longer displaying the data stored in the list item. This probably occurred because of a form template change some time ago. Re-linking the forms, re-publishing the template, and other common approaches didn’t fix the problem.

Ultimately we had to open the form library in explorer view and copy all the documents to the local drive, delete the contents of the form library and post the forms back. This accomplished the goal but the side effect is that all items had my account as the Created By value. This was an issue because item-level read security was set to allow users to view only their forms and not the forms of others. With my account as the Created By value no one would be able to see their forms. Time for PowerShell.

I pieced together a couple of examples from the web to come up with the solution below. I iterate through a folder on the local drive that contains all of the xml files that I downloaded from the library and store it in the $file var. The HashID element of the xml file happened to contain the user’s SAMAccountName.  I passed that value to the SPWeb.EnsureUser() method to get the user token for that user and then created a new SPSite object with that token as a parameter to the SPSite constructor. As long as I reference that SPSite and corresponding SPWeb object I will perform actions in the context of the given user.

The script ran fine and the customer were able to see all of their data again without compromising the read security setting.

 <#
 This script copies files from the hard drive to a document or form library using a given user's credentials.
 The effect is that the documents are created with the given user's name in the "Created By" and "Modified By" fields.
  
 Note that overriding the above fields is only possible when creating a new item, not when editing an existing item. 
 #>
  
 param(
        [string]$siteUrl = $(throw "Site Url is required"),
        [string]$localFolderPath = $(throw "Local Folder path is required"),
        [string]$libName = $(throw "Library Name is required")
 )
  
 #Add-PSSnapin Microsoft.SharePoint.PowerShell
  
 $files = ([System.IO.DirectoryInfo] (Get-Item $localFolderPath)).GetFiles() 
 ForEach($file in $files)
 {
     $fileStream = ([System.IO.FileInfo] (Get-Item $file.FullName)).OpenRead()
     [xml]$x = gc $file.FullName
  
     $w = Get-SPWeb $siteUrl
     $user = $w.EnsureUser($x.myFields.HashID);
     $userToken = $w.AllUsers[$user].UserToken
  
     #Impersonate
     $tokenSite = New-Object Microsoft.Sharepoint.SpSite($siteUrl, $userToken)
     $tokenWeb = $tokenSite.OpenWeb()
    $lib = $tokenWeb.GetFolder($libName)
     [void]$lib.Files.Add($x.myFields.HashID +".xml", $fileStream)
  
     #Cleanup
     $tokenWeb.Update()
     $tokenWeb.Dispose()
     $tokenSite.Dispose()
     $w.Dispose()
 }