Nice one. In response to your question 'a better approach to get to the Headers TextBoxes'... this is what we are using .. .less nested loops
# In Word Doc - Loop through each StoryRange and perform a find and replace
# This includes text and shapes in the body but only text in the headers & footers
ForEach ($storyRng in $newWordDoc.StoryRanges) {
$success = $storyRng.Find.Execute($findText,$matchCase,$matchWholeWord,$matchWildcards,$matchSoundsLike,$matchAllWordForms,$forward,$wrap,$format,$replaceWith,$replace)
}
# In Word Doc - Loop through all Shapes within all Headers and Footers and perform a find and replace
$shapes = $newWordDoc.Sections.Item(1).Headers.Item(1).Shapes
If ($shapes.Count) {
ForEach ($shape in $shapes | Where {[bool]$_.TextFrame.HasText}) {
$success = $shape.TextFrame.TextRange.Find.Execute($findText,$matchCase,$matchWholeWord,$matchWildcards,$matchSoundsLike,$matchAllWordForms,$forward,$wrap,$format,$replaceWith,$replace)
}
}
Description of below full code:
- Copies a word template (well *.docx with mail merge looking text, eg: '[full_name], not a *.dotx)
- You provide it a hashtable of search and replace string pairs (see main body at the bottom)
- For each search and replace string pair, it replaces ALL occurances of the search string (eg: '[full_name]') in the doc with the replace string (eg: 'Jane Doe') in ALL sections of the doc, incl body, header, footer, shapes
- saves the word doc copy
- Saves as PDF
- Deletes the word doc copy
# -------------------------------------------------------------
# FUNCTION: Create-PdfFromWordTemplate (*.docx -> *.pdf)
# -------------------------------------------------------------
# Create a PDF file based on a copy of a Word Template
# after the equivalent of a mail merge (search and replace).
function Create-PdfFromWordTemplate {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[ValidateScript({Test-Path $_})]
[string]$FilePath,
[Parameter(Mandatory=$True)]
[string]$FileNameExclExt,
[Parameter(Mandatory=$True)]
[ValidateScript({Test-Path $_})]
[string]$TemplateFilePath,
[Parameter(Mandatory=$True)]
[string]$TemplateFileName,
[Parameter(Mandatory=$True)]
[hashtable]$SearchAndReplacePairs
)
Begin {
# Create a Microsoft Word Application object
try {
$wordApp = New-Object -ComObject Word.Application
$wordApp.Visible = $false
}
catch {
Write-Warning "$(Get-Date): error in Create-PdfFromWordTemplate create com object: $($_)"
return
}
# Variables - Word Application Enumerations
# https://docs.microsoft.com/en-us/office/vba/api/word.find.execute
$wdFindContinue = 1 # https://docs.microsoft.com/en-us/office/vba/api/word.wdfindwrap
$wdReplaceAll = 2 # https://docs.microsoft.com/en-us/office/vba/api/word.wdreplace
$wdFormatPDF = 17 # https://docs.microsoft.com/en-us/office/vba/api/word.wdsaveformat
$matchCase = $false
$matchWholeWord = $false
$matchWildcards = $false
$matchSoundsLike = $false
$matchAllWordForms = $false
$forward = $true
$wrap = $wdFindContinue
$format = $false
$replace = $wdReplaceAll
}
Process {
# Filenames
$fullTemplFileName = Join-Path -Path $TemplateFilePath -ChildPath "$($TemplateFileName)"
$fullWordFileName = Join-Path -Path $TemplateFilePath -ChildPath "$($FileNameExclExt).docx"
$fullPdfFileName = Join-Path -Path $FilePath -ChildPath "$($FileNameExclExt).pdf"
# Copy Word Template to a New Temporary Word doc
try {
Copy-Item -Path "$($fullTemplFileName)" "$($fullWordFileName)"
}
catch {
Write-Warning "$(Get-Date): error in Create-PdfFromWordTemplate Copy-Item: $($_)"
return
}
# Open Word Doc
try {
$newWordDoc = $wordApp.Documents.Open( $fullWordFileName )
# Loop through each Search and Replace Pair
# Search and replace in the temp word doc
ForEach ($key in $SearchAndReplacePairs.keys) {
Write-Verbose "$(Get-DateTime): Replacing $($key) with $($SearchAndReplacePairs[$key]) in $($fullWordFileName)"
# Set Search & Replace values
$findText = $key
$replaceWith = $SearchAndReplacePairs[$key]
# In Word Doc - Loop through each StoryRange and perform a find and replace
# This includes text and shapes in the body but only text in the headers & footers
ForEach ($storyRng in $newWordDoc.StoryRanges) {
$success = $storyRng.Find.Execute($findText,$matchCase,$matchWholeWord,$matchWildcards,$matchSoundsLike,$matchAllWordForms,$forward,$wrap,$format,$replaceWith,$replace)
}
# In Word Doc - Loop through all Shapes within all Headers and Footers and perform a find and replace
$shapes = $newWordDoc.Sections.Item(1).Headers.Item(1).Shapes
If ($shapes.Count) {
ForEach ($shape in $shapes | Where {[bool]$_.TextFrame.HasText}) {
$success = $shape.TextFrame.TextRange.Find.Execute($findText,$matchCase,$matchWholeWord,$matchWildcards,$matchSoundsLike,$matchAllWordForms,$forward,$wrap,$format,$replaceWith,$replace)
}
}
}
# Save the word document
$newWordDoc.Save()
# Save the word document as a PDF document
Write-Verbose "$(Get-DateTime): Converting: $($fullWordFileName) to $($fullPdfFileName)"
$newWordDoc.SaveAs([ref] "$fullPdfFileName", [ref] $wdFormatPDF)
# Close the word document
$newWordDoc.Close()
# Remove the temporary word document
Remove-Item -Path $fullWordFileName
}
catch {
Write-Warning "$(Get-Date): error in Create-PdfFromWordTemplate: $($_)"
}
}
End {
# Exit the Word Application & Cleanup
try {
$wordApp.Quit()
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($wordApp)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
catch {
Write-Warning "$(Get-Date): error in Create-PdfFromWordTemplate end: $($_)"
}
}
}
# -------------------------------------------------------------
# MAIN BODY
# -------------------------------------------------------------
# Prepare the list of search and replace string/value pairs
$searchReplacePairs = @{
"[full_name]" = "Jane Doe"
"[dob]" = "01/01/2000"
}
Create-PdfFromWordTemplate -FilePath "C:\letters" -FileNameExclExt "Letter" -TemplateFilePath "C:\templates" -TemplateFileName "SampleTemplate.docx" -SearchAndReplacePairs $searchReplacePairs
I also came across this but haven't used it yet. It may help:
http://blog.beyondimpactllc.com/blog/garbage-collection-with-powershell
# Adding the garbage collection (GC) line at the start of each for/foreach loop helps improve memory intensive scripts
foreach ($server in $servers) {
[system.gc]::Collect()
$events = get-winevent | where filterItSomehowToReduceSizeOfStoredObject
$events | export-csv $outputFilePath
}
$Document.ActiveWindow.Selection.Findfor the sake of reducing horizontal scrolling in the question? Continuation in a new line in powershell usually requires a`backtick... \$\endgroup\$*.doc will take all .doc* filespuzzles me. \$\endgroup\$