Microsoft Word documents can have Quick Parts connected to SharePoint fields, so that the value of the field is displayed inside the document. But Word Online (still) does not fully support this. It will display Quick Parts with a a placeholder text instead of the actual value. If the SharePoint list value changes, the document must be opened in the desktop client for the placeholder text to update and show correctly in Word Online.
This caused trouble for me when I was migrating thousands of files into SharePoint Online. I had a script that uploaded documents and tagged them with important metadata. This metadata was supposed to be displayed in the document, but in Word Online the field name was shown instead (as a placeholder). This was not acceptable, especially since Word Online is default when opening a Word document in SharePoint. I tried to use the Word Automation COM API to make a script that updates these values, but it does not allow editing these fields. What to do?
Here I present a solution to update the placeholder values from PowerShell. It works by unzipping the DocX files to directly modify the XML files. Hacky, but it works :-)
This is not a generic script. It was written for my own needs. Modify and adapt the script to suit your needs before running. For example, it does not show how to get the field values to replace the placeholders with. In my case I had a separate CSV file with migration data. Below I just use a simple key/value structure to map placeholders (e.g. [Author]) to a value. A colleague told me he actually found the “real” values elsewhere in the DocX file. I have not verified this, but perhaps it is possible to read these values instead.
Note: Due to a bug in Compress-Archive make sure to first update it or else Word Online will think the documents are corrupt and only open them in view mode: Install-Module Microsoft.PowerShell.Archive -MinimumVersion 1.2.3.0 -Repository PSGallery -Force -AllowClobber
Update: Forgot to use HtmlEncode() to avoid corrupting the XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
[System.Reflection.Assembly]::LoadWithPartialName("System.web") $docIn = "d:\temp\quickparts.docx" $docOut = "d:\temp\quickparts-fixed.docx" $quickparts = @{ "[Author]" = "John Doe"; "[Company]" = "Contoso"; "[Area]" = "Sales"; } Write-Host "Start processing quickparts in $docIn" $workDir = "$($env:TEMP)\quickparts" if([System.IO.Directory]::Exists($workDir) -eq $false) { New-Item -Path $workDir -ItemType Directory | out-null } else { Remove-Item -Path "$workDir\*" -Recurse -Force } Copy-Item -Path $docIn -Destination "$workDir\in.zip" Expand-Archive -Path "$workDir\in.zip" -DestinationPath "$workDir\unzipped" # This list of xml files to process is based where placeholders were located in my files. There may be other locations too. $xmlFiles = @( "$workDir\unzipped\word\header1.xml" "$workDir\unzipped\word\header2.xml" "$workDir\unzipped\word\header3.xml" "$workDir\unzipped\word\glossary\document.xml" "$workDir\unzipped\word\footer1.xml" "$workDir\unzipped\word\footer2.xml" "$workDir\unzipped\word\footer3.xml" ) $anyModified = $false foreach($file in $xmlFiles) { Write-Host "Processing XML file: $([System.IO.Path]::GetFileName($file))..." if([System.IO.File]::Exists($file) -eq $true) { $file_content = [System.IO.File]::ReadAllLines( $file, [System.Text.Encoding]::UTF8 ) $modified = $false foreach($quickpart in $quickparts.GetEnumerator()) { $find = "<w:t>$($quickpart.Key)</w:t>" # Found by examining XML files. There may be other tags. if([system.String]::Join(" ",$file_content).Contains($find) -eq $true) { $replace = "$($quickpart.Value)`n" $replace = $replace.TrimEnd("`n").Replace("`n", ", ") $replace = [System.Web.HttpUtility]::HtmlEncode($replace) Write-Host "`tReplaced $($quickpart.Key) in $([System.IO.Path]::GetFileName($file)) with $replace" $replace = "<w:t>$replace</w:t>" $file_content = $file_content.Replace($find, $replace) $modified = $true } } if($modified -eq $true) { $anyModified = $true [System.IO.File]::WriteAllLines( $file, $file_content, [System.Text.Encoding]::UTF8 ) } } } if($anyModified -eq $true) { Write-Host "Compress and save processed document as $docOut" Compress-Archive -Path "$workDir\unzipped\*" -DestinationPath "$workDir\out.zip" Copy-Item -Path "$workDir\out.zip" -Destination $docOut } else { Write-Host "No quick part placeholders found in $docIn" } Remove-Item -Path $workDir -Recurse -Force Write-Host "Ready!" |