Mr. Annoyed
Mr. Annoyed

Reputation: 571

Embed a text file as an object in Ms Word 2010

I am building a script to write word documents using PowerShell (Windows 7, PS4, .Net 4, Office 2010.

The script works but it embeds the text at the top of the document. I need to be able to specify the exact location like on page 2.

Here's what I got up to date:

# Opens an MsWord Doc, does a Search-and-Replace and embeds a text file as an object
# To make this work you need in the same folder as this script:
#   -A 2-page MsWord file called "Search_and_replace_Target_Template.doc" with:
#       -the string <package-name> on page ONE
#       -the string <TextFile-Placeholder> on page TWO
#   -A textfile called "TextFile.txt"
#
#The script will:
#   -replace <package-name> with something on page one
#   -embed the text file <package-name> at the top of page one (but I want it to replace <TextFile-Placeholder> on page 2)
#
# CAVEAT: Using MsWord 2010

[String]$MsWordDocTemplateName  = "Search_and_replace_Target_Template.doc"
[String]$TextFileName           = "TextFile.txt"

[Int]$wdReplaceAll      = 2
[Int]$wdFindContinue    = 1 #The find operation continues when the beginning or end of the search range is reached.
[Bool]$MatchCase        = $False 
[Bool]$MatchWholeWord   = $true
[Bool]$MatchWildcards   = $False 
[Bool]$MatchSoundsLike  = $False 
[Bool]$MatchAllWordForms = $False 
[Bool]$Forward          = $True 
[Int]$Wrap              = $wdFindContinue #The find operation continues when the beginning or end of the search range is reached.
[Bool]$Format           = $False 
[Int]$wdReplaceNone     = 0

$objWord = New-Object -ComObject Word.Application
$objWord.Visible        = $true #Makes the MsWord
[String]$ScriptDirectory = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.path)
[String]$WordDocTemplatePath = "$ScriptDirectory\$MsWordDocTemplateName"
[String]$TextFilePath   = "$ScriptDirectory\$TextFileName"
[String]$SaveAsPathNew  = Join-Path -Path $ScriptDirectory -ChildPath "${MsWordDocTemplateName}-NEW.doc"


#Open Template with MSWord
Try {
    $objDoc = $objWord.Documents.Open($WordDocTemplatePath)
} Catch {
    [string]$mainErrorMessage = "$($_.Exception.Message) $($_.ScriptStackTrace) $($_.Exception.InnerException)"
    Write-Host $mainErrorMessage -ForegroundColor Red
    Start-Sleep -Seconds 7
    $objDoc.Close()
    $objWord.Quit()
}

$objSelection               = $objWord.Selection
$objSelection.Find.Forward  = 'TRUE'
$objSelection.Find.MatchWholeWord = 'TRUE'


#Replace <package-name>
[String]$FindText = "<package-name>" 
[String]$ReplaceWith = "PackageName_v1"
write-host "replacing [$FindText] :" -NoNewline
$objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll) 


#Embed the text file as an object 
[System.IO.FileSystemInfo]$TextFileObj = Get-item $TextFilePath
If ( $(Try {Test-Path $($TextFileObj.FullName).trim() } Catch { $false }) ) {
    write-host "Embedding [$TextFileName] :" -NoNewline
    [String]$FindText = "<TextFile-Placeholder>" 
    [String]$ReplaceWith = ""
#   $objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll)


    #Need code to create a RANGE to the position of <TextFile-Placeholder>


    #Embed file into word doc as an object
    #$result = $objSelection.InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)
    $result = $objSelection.Range[0].InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true) #works too but does the same

    Write-host "Success"
} Else {
    Write-Host "[$TextFilePath] does not exist!" -ForegroundColor Red
    Start-Sleep -Seconds 5
}



Write-Host "Saving updated word doc to [${MsWordDocTemplateName}-NEW.doc] ***"
Start-Sleep -Seconds 2
#List of formats
$AllSaveFormat = [Enum]::GetNames([microsoft.office.interop.word.WdSaveFormat])
$SaveFormat = $AllSaveFormat

$objDoc.SaveAs([ref]$SaveAsPathNew,[ref]$SaveFormat::wdFormatDocument) #Overwrite if exists
$objDoc.Close()
$objWord.Quit()

$null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$objWord)
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Remove-Variable objWord

If you have a way to do this in Word 2016 I'll take it too as I'll have to redo it all for office 2016 later on.

UPDATE: Looks like the solution is to create a "Range" where the is located. By default it seems you have one range[0] that represents the whole document.

The closest I came to do this was:

$MyRange = $objSelection.Range[Start:= 0, End:= 7]

But this is not the syntax for PowerShell and it deals only with absolute character positions and not the results of a search for a string.

If I could create a 2nd range maybe this could work:

$objSelection.Range[1].InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true) 

Upvotes: 1

Views: 836

Answers (1)

Cindy Meister
Cindy Meister

Reputation: 25673

I can't write powershell script for you, but I can describe what you need. You are, indeed, on-track with the Range idea. To get the Range for an entire Word document:

$MyRange = $objDoc.Content

Note that you're free to declare and use as many Range objects as you need. Range is not limited like Selection, of which there can be only one. As long as you don't need the original Range again, go ahead and perform Find on it. If you do need the original Range again, then declare and instantiate a second, instantiating it either as above or, in order to use the original as the starting point:

$MyRange2 = $MyRange.Duplicate

Then use Find on a Range. Note that, when Find is successful the content of the Range is the found term, which puts the "focus" on the correct page. (But will not move the Selection!)

$MyRange.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll) 

If you want to test whether Find was successful the Execute method returns a Boolean value (true if Find was successful).

Use something like what follows to insert the file, although I'm not sure OLEObject is the best method for inserting a text file. I'd rather think InsertFile would be more appropriate. An OLEObject requires an OLE Server registered in Windows that supports editing text files; it will be slower and put a field code in the document that will try to update...

$result =$MyRange.InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)

Upvotes: 2

Related Questions