在整个 Word 文档中查找和替换时出现空白页眉/页脚错误的 PowerShell 解决方法

问题描述:

我正在尝试组合一个 PowerShell 脚本来在整个 Word 文档中进行多次查找和替换,包括 Headers页脚和任何可能显示文本的Shape.
周围有很多 VBA 示例,所以它不是太难,但是有一个已知错误在 VBA 中被绕过,解决方案称为Peter Hewett 的 VBA 技巧".请参阅此示例以及这个.
我已经尝试在 PowerShell 中以类似的方式解决这个错误,但它没有按预期工作.HeaderFooter 中的某些 TextBoxes 仍然被忽略.
然而,我注意到,运行我的脚本两次实际上最终会起作用.

I am trying to put together a PowerShell script to do multiple find and replace throughout a whole Word Document, that is including Headers, Footers and any Shape potentially displaying text.
There are plenty of VBA examples around so it's not too difficult, but there is a know bug that is circumvented in VBA with a solution dubbed as "Peter Hewett 's VBA trickery". See this example and also this one.
I have tried to address this bug in a similar fashion in PowerShell but it is not working as expected. Some TextBoxes in Header or Footer are still being ignored.
I noticed however, that runnning my script twice will actually end up working.

任何有关解决此问题的想法将不胜感激.

Any idea as to a solution to this problem would be greatly appreciated.

$folderPath = "C:\Users\user\folder\*" # multi-folders: "C:\fso1*", "C:\fso2*"
$fileType = "*.doc"                    # *.doc will take all .doc* files

$textToReplace = @{
# "TextToFind" = "TextToReplaceWith"
"This1" = "That1"
"This2" = "That2"
"This3" = "That3"
}

$word = New-Object -ComObject Word.Application
$word.Visible = $false

$storyTypes = [Microsoft.Office.Interop.Word.WdStoryType]
#Val, Name
#  1, wdMainTextStory
#  2, wdFootnotesStory
#  3, wdEndnotesStory
#  4, wdCommentsStory
#  5, wdTextFrameStory
#  6, wdEvenPagesHeaderStory
#  7, wdPrimaryHeaderStory
#  8, wdEvenPagesFooterStory
#  9, wdPrimaryFooterStory
# 10, wdFirstPageHeaderStory
# 11, wdFirstPageFooterStory
# 12, wdFootnoteSeparatorStory
# 13, wdFootnoteContinuationSeparatorStory
# 14, wdFootnoteContinuationNoticeStory
# 15, wdEndnoteSeparatorStory
# 16, wdEndnoteContinuationSeparatorStory
# 17, wdEndnoteContinuationNoticeStory

Function findAndReplace($objFind, $FindText, $ReplaceWith) {
  #simple Find and Replace to execute on a Find object
  $matchCase = $true
  $matchWholeWord = $true
  $matchWildcards = $false
  $matchSoundsLike = $false
  $matchAllWordForms = $false
  $forward = $true
  $findWrap = [Microsoft.Office.Interop.Word.WdReplace]::wdReplaceAll
  $format = $false
  $replace = [Microsoft.Office.Interop.Word.WdFindWrap]::wdFindContinue

  $objFind.Execute($FindText, $matchCase, $matchWholeWord, $matchWildCards, $matchSoundsLike, $matchAllWordForms, \`
           $forward, $findWrap, $format, $ReplaceWith, $replace) > $null
}

Function findAndReplaceAll($objFind, $FindText, $ReplaceWith) {
    findAndReplace $objFind $FindText $ReplaceWith
    While ($objFind.Found) {
        findAndReplace $objFind $FindText $ReplaceWith
    }
}

Function findAndReplaceMultiple($objFind, $lookupTable) {
  #apply multiple Find and Replace on the same Find object
  $lookupTable.GetEnumerator() | ForEach-Object {
    findAndReplaceAll $objFind $_.Key $_.Value
  }
}

Function findAndReplaceMultipleWholeDoc($Document, $lookupTable) {
  ForEach ($storyRge in $Document.StoryRanges) {
    #Loop through each StoryRange
    Do {
      findAndReplaceMultiple $storyRge.Find $lookupTable
      #check if the StoryRange has shapes (we check only StoryTypes 6 to 11, basically Headers and Footers)
      # as the Shapes inside the wdMainTextStory will be checked
      # see http://wordmvp.com/FAQs/Customization/ReplaceAnywhere.htm
      # and http://gregmaxey.com/using_a_macro_to_replace_text_wherever_it_appears_in_a_document.html
      If (($storyRge.StoryType -ge $storyTypes::wdEvenPagesHeaderStory) -and \`
        ($storyRge.StoryType -le $storyTypes::wdFirstPageFooterStory)) {
        If ($storyRge.ShapeRange.Count) { #non-zero is True
          ForEach ($shp in $storyRge.ShapeRange) {
            If ($shp.TextFrame.HasText) { #non-zero is True, in case of text .HasText = -1
              findAndReplaceMultiple $shp.TextFrame.TextRange.Find $lookupTable
            }
          }
        }
      }
      #check for linked Ranges
      $storyRge = $storyRge.NextStoryRange
    } Until (!$storyRge) #non-null is True

  }
}

Function processDoc {
  $doc = $word.Documents.Open($_.FullName)
  # The "VBA trickey" translated to PowerShell...
  $junk = $doc.Sections.Item(1).Headers.Item(1).Range.StoryType
  #... but not working
  findAndReplaceMultipleWholeDoc $doc $textToReplace
  $doc.Close([ref]$true)
}

$sw = [Diagnostics.Stopwatch]::StartNew()
$countf = 0
Get-ChildItem -Path $folderPath -Recurse -Filter $fileType | ForEach-Object { 
  Write-Host "Processing \`"$($_.Name)\`"..."
  processDoc
  $countf++
}
$sw.Stop()
$elapsed = $sw.Elapsed.toString()
Write-Host "Done. $countf files processed in $elapsed"

$word.Quit()
$word = $null
[gc]::collect() 
[gc]::WaitForPendingFinalizers()

我查看了 Microsoft 文档 文档在这里 然后我认为下面的代码可以做到.

I checked out Microsoft documentation documentation here and then I think the below code can do it.

$word = New-Object -ComObject Word.Application
$word.visible=$false
$files = Get-ChildItem "C:\Users\Ali\Desktop\Test" -Filter *.docx

$find="Hello"
$replace="Bye"
$wdHeaderFooterPrimary = 1


$ReplaceAll = 2
$FindContinue = 1
$MatchCase = $false
$MatchWholeWord = $false
$MatchWildcards = $false
$MatchSoundsLike = $false
$MatchAllWordForms = $false
$Forward = $true
$Wrap = $findContinue
$Format = $false


for ($i=0; $i -lt $files.Count; $i++) {
$filename = $files[$i].FullName 
$doc = $word.Documents.Open($filename)


ForEach ($StoryRange In $doc.StoryRanges){

$StoryRange.Find.Execute($find,$MatchCase,
                        $MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
                        $MatchAllWordForms,$Forward,$Wrap,$Format,
                        $replace,$ReplaceAll)

 While ($StoryRange.find.Found){
        $StoryRange.Find.Execute($find,$MatchCase,
                        $MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
                        $MatchAllWordForms,$Forward,$Wrap,$Format,
                        $replace,$ReplaceAll)
 }



  While (-Not($StoryRange.NextStoryRange -eq $null)){
   $StoryRange = $StoryRange.NextStoryRange


           $StoryRange.Find.Execute($find,$MatchCase,
                        $MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
                        $MatchAllWordForms,$Forward,$Wrap,$Format,
                        $replace,$ReplaceAll)


    While ($StoryRange.find.Found){
          $StoryRange.Find.Execute($find,$MatchCase,
                        $MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
                        $MatchAllWordForms,$Forward,$Wrap,$Format,
                        $replace,$ReplaceAll)
    }
    } 

}

  #shapes in footers and headers
for ($j=1; $j -le $doc.Sections.Count; $j++) {

    $FooterShapesCount = $doc.Sections($j).Footers($wdHeaderFooterPrimary).Shapes.Count
    $HeaderShapesCount = $doc.Sections($j).Headers($wdHeaderFooterPrimary).Shapes.Count

        for ($i=1; $i -le $FooterShapesCount; $i++) {
            $TextRange = $doc.Sections($j).Footers($wdHeaderFooterPrimary).Shapes($i).TextFrame.TextRange
            $TextRange.Find.Execute($find,$MatchCase,
                        $MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
                        $MatchAllWordForms,$Forward,$Wrap,$Format,
                        $replace,$ReplaceAll)
        }
        for ($i=1; $i -le $HeaderShapesCount; $i++) {
            $TextRange = $doc.Sections($j).Headers($wdHeaderFooterPrimary).Shapes($i).TextFrame.TextRange
            $TextRange.Find.Execute($find,$MatchCase,
                        $MatchWholeWord,$MatchWildcards,$MatchSoundsLike,
                        $MatchAllWordForms,$Forward,$Wrap,$Format,
                        $replace,$ReplaceAll)
        }
    }
$doc.Save()
$doc.close()
}

$word.quit()