Replace Multiple words in MSWord using Powershell - powershell

I am trying to use powershell to replace multiple lines in a word document without having to save and close after every change. right now I have:
$objWord = New-Object -comobject Word.Application
function findAndReplace
{
$objDoc = $objWord.Documents.Open("C:\temp\test.docx")
$objWord.Visible = $false
$objSelection = $objWord.Selection
$a = $objSelection.Find.Execute($FindText, $false, $true, $False, $False, $False, $true, 1, $False, $ReplaceWith)
$objDoc.Save()
$objDoc.close()
}
############DEVICE DETAILS#############
$FindText = "USERID"
$ReplaceWith = $AssociateIDnum
findAndReplace
$FindText = "CONTACTNUMBER"
$ReplaceWith = $Phone
findAndReplace
Using this, I made the function that runs those commands, and I change the $findtext and $replacetext on each instance of a new word, then run the function each time.
With this method, the Word Doc opens and closes nearly 25 times to write each new word replacement.
Is there a way I can make a function or loop to make it change the $findtext
and $replacewith variable each time?
I am relatively new to powershell but I have been learning some of it on my own.

Try using a list of objects as a parameter to the function, something like that :
$objWord = New-Object -comobject Word.Application
function findAndReplace ($todoObjs)
{
$objDoc = $objWord.Documents.Open("C:\temp\test.docx")
$objWord.Visible = $false
$objSelection = $objWord.Selection
foreach ($todoObj in $todoObjs)
{
$a = $objSelection.Find.Execute($($todoObj.FIND), $false, $true, $False, $False, $False, $true, 1, $False, $($todoObj.REPLACE))
}
$objDoc.Save()
$objDoc.close()
}
############DEVICE DETAILS#############
$todo = #"
FIND,REPLACE
toto,titi
tutu,tata
"#
$todoObjs = ConvertFrom-Csv $todo
findAndReplace $todoObjs

Related

How to make a textbox function in a form?

I have an application I am making with multiple text boxes and I am trying to clean it up by making a text box function. However the name parameter itself needs to return a variable name and I am just not quite sure how to do that. I tried giving the parameter the
[psvariable] type but that does not seem to be working.
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.StartPosition = "CenterScreen"
$Form.Size = New-Object System.Drawing.Point(415,838)
$Form.text = "Test Form"
$Form.TopMost = $false
$Form.MaximumSize = $Form.Size
$Form.MinimumSize = $Form.Size
function textBox {
[CmdletBinding()]
param (
[Parameter (Mandatory = $True)]
[psvariable]$name,
[Parameter (Mandatory = $True)]
[string]$ml,
[Parameter (Mandatory = $True)]
[int]$lx,
[Parameter (Mandatory = $True)]
[int]$ly,
[Parameter (Mandatory = $True)]
[string]$text
)
$name = New-Object system.Windows.Forms.TextBox
$name.multiline = $ml
$name.Size = New-Object System.Drawing.Size(300,60)
$name.location = New-Object System.Drawing.Point($lx,$ly)
$name.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
$name.Text = $text
}
textBox -name '$source' -ml $True -lx 15 -ly 100 -text "test it"
$Form.controls.AddRange(#($source))
[void]$Form.ShowDialog()
exit
It's unclear why is the $name parameter there in your function to begin with, I don't see any use for it. The other problem is that your function is not returning anything and your function invocation is not capturing anything either.
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
# Form code here
function textBox {
[CmdletBinding()]
param (
[Parameter (Mandatory = $True)]
[bool] $ml,
[Parameter (Mandatory = $True)]
[int] $lx,
[Parameter (Mandatory = $True)]
[int] $ly,
[Parameter (Mandatory = $True)]
[string] $text
)
[Windows.Forms.TextBox]#{
Multiline = $ml
Size = [Drawing.Size]::new(300,60)
Location = [Drawing.Point]::new($lx, $ly)
Font = [Drawing.Font]::new('Microsoft Sans Serif', 10)
Text = $text
}
}
# Capture here
$txtBox = textBox -ml $True -lx 15 -ly 100 -text "test it"
$Form.Controls.AddRange(#($txtBox))
[void] $Form.ShowDialog()
To clarify further, the $Name Parameter was constraint to be a PSVariable:
[psvariable] $Name = $null
Hence when, in your function's body, you try to assign an instance different than a PSVariable:
$name = New-Object System.Windows.Forms.TextBox
You would receive this error, which is basically telling you that PowerShell cannot convert an instance of TextBox to the constraint type:
Cannot convert the "System.Windows.Forms.TextBox, Text: " value of type "System.Windows.Forms.TextBox" to type "System.Management.Automation.PSVariable".

How to set parameters for SaveAs() dialog in Word.Application?

I want to save a Word document as PDF from a PowerShell script.
The following code works for me.
$Word = New-Object -ComObject Word.Application
$Doc = $Word.Documents.Open("C:\TEMP\WORD.DOCX")
$Name = ($Doc.Fullname).Replace("DOCX", "PDF")
$result = $Doc.SaveAs([ref] $Name, [ref] 17)
$Doc.Close()
echo "Saved to $Name"
The produced PDF is a PDF/A though.
When I save the document manually then I can set the option "PDF/A compliant" in a dialog which pops up.
How can I change this format specific option via PowerShell?
The pictures explain perhaps better what I'm trying.
The only way I know of is by using the ExportAsFixedFormat function instead of SaveAs.
$Word = New-Object -ComObject Word.Application
$Doc = $Word.Documents.Open("C:\TEMP\WORD.DOCX")
$Name = [System.IO.Path]::ChangeExtension($Doc.Fullname, "PDF")
# Use ExportAsFixedFormat function.
# See: https://learn.microsoft.com/en-us/office/vba/api/word.document.exportasfixedformat
# Parameters:
# OutputFileName, ExportFormat, OpenAfterExport, OptimizeFor, Range, From
# To, Item, IncludeDocProps, KeepIRM, CreateBookmarks, DocStructureTags
# BitmapMissingFonts, UseISO19005_1
# The last parameter 'UseISO19005_1' saves as PDF/A Compliant
$result = $Doc.ExportAsFixedFormat(
$Name,
[Microsoft.Office.Interop.Word.WdExportFormat]::wdExportFormatPDF,
$false,
[Microsoft.Office.Interop.Word.WdExportOptimizeFor]::wdExportOptimizeForOnScreen,
[Microsoft.Office.Interop.Word.WdExportRange]::wdExportAllDocument,
0,
0,
[Microsoft.Office.Interop.Word.WdExportItem]::wdExportDocumentContent,
$true,
$true,
[Microsoft.Office.Interop.Word.WdExportCreateBookmarks]::wdExportCreateWordBookmarks,
$true,
$false,
$true
)
$Doc.Close()
# clean up Com object after use
$Word.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Word) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

Foreach Loop to Create Multiple Shortcuts Not Working as Expected

I'm trying to make a script that creates multiple shortcuts to executables on the desktop. Because the code that's responsible for creating the shortcuts will be used multiple times and in other scripts I decided to put it into a function.
The logic is pretty straightforward:
Define function
Define target files of shortcuts in separate arrays (I'm using notepad.exe and cmd.exe in my example)
Define intended path for shortcuts
I'm trying to use nested foreach loops to iterate through the target file and shortcut path arrays but it's not producing the shortcuts correctly. Maybe there's a better way to iterate through the programs that I'm not seeing (quite possible as I'm sick and have bad brain fog).
The script can handle one shortcut at least.
I've tried running the function code outside of the function. When I remove Command Prompt from the arrays, the shortcut to Notepad is properly created.
function CreateShortcuts {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true, Position = 0)]
[System.String]$ShortcutPath,
[Parameter(Mandatory = $true, Position = 1)]
[System.String]$TargetFile,
[Parameter(Mandatory = $false, Position = 2)]
[System.String]$ShortcutArgs
)
$objShell = New-Object -ComObject WScript.Shell
$objShortcut = $objShell.CreateShortcut($ShortcutPath)
$objShortcut.TargetPath = $TargetFile
$objShortcut.Save()
}
$TargetFiles = "$env:SystemRoot\System32\notepad.exe", "$env:SystemRoot\System32\cmd.exe"
$ShortcutPaths = "$env:Public\Desktop\Notepad.lnk", "$env:Public\Desktop\Command Prompt.lnk"
foreach ($ShortcutPath in $ShortcutPaths) {
foreach ($TargetFile in $TargetFiles) {
CreateShortcuts -ShortcutPath $ShortcutPath -TargetFile $TargetFile
}
}
Expected output is shortcuts to Notepad and Command Prompt appear on the desktop and link to the intended program. Instead what happens is both shortcuts link to cmd.exe.
You're doing your loops wrong. What you are doing is looping through each item in $ShortcutPaths, and for each of those looping through each item in $TargetFiles, so every item in $ShortcutPaths ends up with a shortcut created pointing at the last item in $TargetFiles. What you want to do instead is correlate each item in each array with the same indexed item in the other array. So item 1 in $ShortcutPaths with item 1 in $TargetFiles, and so on. To do that you use a For loop as such:
$TargetFiles = "$env:SystemRoot\System32\notepad.exe", "$env:SystemRoot\System32\cmd.exe"
$ShortcutPaths = "$env:Public\Desktop\Notepad.lnk", "$env:Public\Desktop\Command Prompt.lnk"
For($i=0;$i -lt $ShortcutPaths.count;$i++){
CreateShortcuts -ShortcutPath $ShortcutPaths[$i] -TargetFile $TargetFiles[$i]
}
I agreed with TheMadTechnician, simply add another variable $i to select from the strings array you provide. can be also written like that:
$i=0
Foreach ($TargetFile in $TargetFiles) {
CreateShortcuts -ShortcutPath $ShortcutPaths[$i] -TargetFile $TargetFile
$i=$i+1
}
I prefer to have this for loop inside the function section and you simply pass the string array to the function. Something like this.
function CreateShortcuts {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true, Position = 0)]
[system.String[]]$TargetFile,
[Parameter(Mandatory = $true, Position = 1)]
[system.String[]]$ShortcutPath,
[Parameter(Mandatory = $false, Position = 2)]
[System.String]$ShortcutArgs
)
$i=0
Foreach ($object in $TargetFile) {
$objShell = New-Object -ComObject WScript.Shell
$objShortcut = $objShell.CreateShortcut($ShortcutPath[$i])
$objShortcut.TargetPath = $object
$objShortcut.Save()
$i=$i+1
}
}
$TargetFile = "$env:SystemRoot\System32\notepad.exe", "$env:SystemRoot\System32\cmd.exe"
$ShortcutPath ="$env:Public\Desktop\Notepad.lnk" ,"$env:Public\Desktop\Command Prompt.lnk"
CreateShortcuts -TargetFile $TargetFile -ShortcutPath $ShortcutPath
Thanks for the input everyone. It was helpful and got me unstuck. My brain fog cleared the next day and the gears in my head finally started turning again. I ended up using hashtables for this task to ensure that the targets, shortcut paths, and shortcut arguments values all match up based on a key of the same name. I realized using arrays could be problematic if the values for each of the above were indexed out of order from one another, or some shortcuts needed arguments while others didn't.
Below is the updated code. The only thing left to do is add the help information.
function CreateShortcuts {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true,
Position = 0)]
[System.Collections.Hashtable]$TargetFiles,
[Parameter(Mandatory = $true,
Position = 1)]
[System.Collections.Hashtable]$ShortcutPaths,
[Parameter(Mandatory = $false,
Position = 2)]
[System.Collections.Hashtable]$ShortcutArgs
)
$objShell = New-Object -ComObject WScript.Shell
Foreach ($item in $TargetFiles.Keys) {
$objShortcut = $objShell.CreateShortcut($ShortcutPaths.Item($item))
$objShortcut.TargetPath = $TargetFiles.Item($item)
if ($ShortcutArgs) {
$objShortcut.Arguments = $ShortcutArgs.Item($item)
}
$objShortcut.Save()
}
}
$TargetFiles = #{
"Notepad" = "$env:SystemRoot\System32\notepad.exe"
"CmdPrompt" = "$env:SystemRoot\System32\cmd.exe"
}
$ShortcutPaths = #{
"Notepad" = "$env:Public\Desktop\Notepad.lnk"
"CmdPrompt" = "$env:Public\Desktop\Command Prompt.lnk"
}
$ShortcutArgs = #{
"CmdPrompt" = "/foo -bar"
"Notepad" = "/test"
}
CreateShortcuts -ShortcutPaths $ShortcutPaths -TargetFiles $TargetFiles -ShortcutArgs $ShortcutArgs

Powershell word header replace

I want to replace header in word document.
$pth = "d:\test\test.docx"
$objWord = New-Object -ComObject word.application
$objWord.Visible = $True
$objDoc = $objWord.Documents.Open($pth)
$objSelection = $objWord.Selection
$Section = $objDoc.Sections.Item(1)
$header = $Section.Headers.Item(1)
This return me a plain text:
Write-Host $header.Range.Text
But my header have an image and table. Can i replace string in header without destroying header? I replace strings in word document and works great. My only problem is header.
Link to example Word document header below.
http://zapodaj.net/223c522426648.png.html
Try this:
$replaceWith = "New Text !"
$replace = [Microsoft.Office.Interop.Word.WdReplace]::wdReplaceAll
$findWrap = [Microsoft.Office.Interop.Word.WdFindWrap]::wdFindContinue
$find = $header.Range.find
$find.Execute($header.Range.Text,
$false, #match case
$false, #match whole word
$false, #match wildcards
$false, #match soundslike
$false, #match all word forms
$true, #forward
$findWrap,
$null, #format
$replaceWith,
$replace)
The pictures and other tables, should remain untouched.
I don't know solution in powershell but I use VBA runed from powershell.
// code is changed so if it don't work let me know
$objWord = New-Object -ComObject word.application
$objWord.Visible = $True # don't have to be true
$pathToFile = "d:\Delivery_Templates\filename.docx" #path to your file
$objDoc = $objWord.Documents.Open(pathToFile )
$objSelection = $objWord.Selection
$objWord.Run('myReplace', [ref] $currentVersion); # myReplace - macro name, currentVersion - macro parameter

Can I make a parameter set depend on the value of another parameter?

Let's say I have a function like:
function Authenticate
{
param
(
[ValidateSet('WindowsAuthentication','UsernameAndPassword')][string] $AuthenticationType,
[Parameter(ParameterSetName='Set1')][string] $Username,
[Parameter(ParameterSetName='Set1')][string] $Password
)
..
}
And I would like to make the parameter set to be mandatory when $AuthenticationType = 'UsernameAndPassword' but also so that it cannot be used if $AuthenticationType = 'WindowsAuthentication'.
It this even possible in PowerShell?
Using the link from Tim Ferrill's answer, I created the following function to help create dynamic parameters:
function New-DynamicParameter
{
[CmdletBinding(DefaultParameterSetName = 'Core')]
param
(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)][string] $Name,
[Parameter(Mandatory = $true, ParameterSetName = 'Core')][Parameter(Mandatory = $true, ParameterSetName = 'ValidateSet')][type] $Type,
[Parameter(Mandatory = $false)][string] $ParameterSetName = '__AllParameterSets',
[Parameter(Mandatory = $false)][bool] $Mandatory = $false,
[Parameter(Mandatory = $false)][int] $Position,
[Parameter(Mandatory = $false)][bool] $ValueFromPipelineByPropertyName = $false,
[Parameter(Mandatory = $false)][string] $HelpMessage,
[Parameter(Mandatory = $true, ParameterSetName = 'ValidateSet')][string[]] $ValidateSet,
[Parameter(Mandatory = $false, ParameterSetName = 'ValidateSet')][bool] $IgnoreCase = $true
)
process
{
# Define Parameter Attributes
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.ParameterSetName = $ParameterSetName
$ParameterAttribute.Mandatory = $Mandatory
$ParameterAttribute.Position = $Position
$ParameterAttribute.ValueFromPipelineByPropertyName = $ValueFromPipelineByPropertyName
$ParameterAttribute.HelpMessage = $HelpMessage
# Define Parameter Validation Options if ValidateSet set was used
if ($PSCmdlet.ParameterSetName -eq 'ValidateSet')
{
$ParameterValidateSet = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $ValidateSet -Strict (!$IgnoreCase)
}
# Add Parameter Attributes and ValidateSet to an Attribute Collection
$AttributeCollection = New-Object Collections.ObjectModel.Collection[System.Attribute]
$AttributeCollection.Add($ParameterAttribute)
$AttributeCollection.Add($ParameterValidateSet)
# Add parameter to parameter list
$Parameter = New-Object System.Management.Automation.RuntimeDefinedParameter -ArgumentList #($Name, $Type, $AttributeCollection)
# Expose parameter to the namespace
$ParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$ParameterDictionary.Add($Name, $Parameter)
return $ParameterDictionary
}
}
And solved my particular problem in the following way:
function Authenticate
{
param
(
[ValidateSet('WindowsAuthentication','UsernameAndPassword')][string] $AuthenticationType,
)
DynamicParam
{
if ($AuthenticationType -eq 'UsernameAndPassword')
{
New-DynamicParameter Username [string] -Mandatory $true
New-DynamicParameter Password [string] -Mandatory $true
}
}
...
}
It became unneeded to have a parameter set when using Dynamic Parameter so I removed the parameter set.
You can do this using DynamicParam. I saw a decent post on this recently here.
DynamicParam {
if ($AuthenticationType -eq 'UsernameAndPassword') {
#create ParameterAttribute Objects for the username and password
$unAttribute = New-Object System.Management.Automation.ParameterAttribute
$unAttribute.Mandatory = $true
$unAttribute.HelpMessage = "Please enter your username:"
$pwAttribute = New-Object System.Management.Automation.ParameterAttribute
$pwAttribute.Mandatory = $true
$pwAttribute.HelpMessage = "Please enter a password:"
#create an attributecollection object for the attributes we just created.
$attributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute]
#add our custom attributes
$attributeCollection.Add($unAttribute)
$attributeCollection.Add($pwAttribute)
#add our paramater specifying the attribute collection
$unParam = New-Object System.Management.Automation.RuntimeDefinedParameter('username', [string], $attributeCollection)
$pwParam = New-Object System.Management.Automation.RuntimeDefinedParameter('password', [string], $attributeCollection)
#expose the name of our parameter
$paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add('username', $unParam)
$paramDictionary.Add('password', $pwParam)
return $paramDictionary
}
}
Process {
$PSBoundParameters.username
$PSBoundParameters.password
}