I've inherited and am running the Powershell script below, and I'm having one problem interpreting it and applying some of its functionality to other scripts I'm trying to write.
In particular, I'm looking at the following lines of code:
ForEach ($doc in $srcfiles) {
saveas-document -docs $doc
}
From the program functionality, I know that each instance of a document in $srcfiles is being assigned to the variable $doc, and that variable is being passed to the saveas-document function as the input value. However, I'm not sure where $doc is coming from. Does this statement declare the $doc variable on the fly? Or is it a Powershell reserved word the represents the document object in my source path? Also, does the -docs switch in dessence declare that $doc is equal to the $docs variable expected by the function? I need some help understanding HOW this works so I can apply that knowledge to other projects.
$global:word = new-object -comobject word.application
$word.Visible = $False
# PATHS
$backupPath = "\\Server\path\to\source\files\"
$srcfiles = Get-ChildItem $backupPath -filter "*htm.*"
$dPath = "\\Server\path\to\desitination\files\"
$htmPath = $dPath + "HT\" # Data path for HTML
$docPath = $dPath + "DO\" # Data path for *.DOC
$doxPath = $dPath + "DX\" # Data path for *.DOCX
$txtPath = $dPath + "TX\" # Data path for *.TXT
$rtfPath = $dPath + "RT\" # Data path for *.RTF
# SAVE FORMATS
$saveFormatDoc = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveFormat], 0);
$saveFormatTxt = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveFormat], 4);
$saveFormatRTF = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveFormat], 6);
$saveFormatDox = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveFormat], 16);
$saveFormatXML = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveFormat], 14);
# Convert Documents
function saveas-document ($docs) {
$savepath = "$docPath$($docs.BaseName)"
"Converting to $savepath.doc"
$opendoc.saveas([ref]"$savepath", [ref]$saveFormatDoc)
$opendoc.close()
"Success with conversions."
" "
}
#
ForEach ($doc in $srcfiles) {
saveas-document -docs $doc
}
#
$word.quit()
Run the follwing commands, and read through the output:
Get-Help about_foreach -ShowWindow
Get-Help about_functions -ShowWindow
Related
I am setting up a Powershell script that by passing the path to a directory, checks if any of the strings contained in an excel file appear in the doc or docx files in that directory. If so, the colour of that string has to be changed in the doc/docx file.
I've been messing around with Word's COM object for a while and haven't been able to get it to work.
Here is what I have so far
#Open a Folder Browser to select the folder to process
$fwd = New-Object System.Windows.Forms.FolderBrowserDialog
$null = $fwd.ShowDialog()
$path = $fwd.SelectedPath
$files = Get-ChildItem -Path $path| Where-Object -Property Extension -Match ".docx?"
#Load the excel file data to an arraylist object for late iteration
$forbiddenWordsFilePath = "<<Path_To_XLSX>>"
$forbiddenWords= Import-Excel -Path $forbiddenWordsFilePath
$WordDoc = New-Object -ComObject Word.Application
$WordDoc.visible=$false
#Iterate over the files contained on the folder
foreach ($file in $files) {
#Make a backup of the docx file previous to working with it
$fileBackupPath= $file.FullName+"_bck"
Copy-Item $file.FullName -Destination $fileBackupPath
#Opens the docx? file on background
$doc = $WordDoc.Documents.Open($file.FullName,[type]::Missing,$true)
$MatchCase = $false
$MatchWholeWorld = $true
$MatchWildcards = $false
$MatchSoundsLike = $false
$MatchAllWordForms = $false
$Forward = $false
$Wrap = 1
$Format = $true
$wdReplaceAll = 2
#Iterate over the words list and search for them on the docx file
foreach ($word in $forbiddenWords){
$Replace=$word.Search_Pattern
$doc.Content.Find.Execute($word.Search_Pattern, $MatchCase, $MatchWholeWorld, $MatchWildcards, $MatchSoundsLike, $MatchAllWordForms, $Forward, $Wrap, $Format, $Replace, $wdReplaceAll)
}
}
#Clears any opened Microsoft Word process
$null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$WordDoc)
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Remove-Variable WordDoc
It is clear to me that the colour change should be in the second foreach. But I am not able to find the code that allows me to do it.
I'm a bit of a novice in dealing with the Word COM object with Powershell.
The excel file has the following headers....
Control_Type Control Comments Search_Pattern
In case there are doubts about how to obtain the property "Search_word".
This question already has an answer here:
Issues with Target Path in powershell in creating a short cut
(1 answer)
Closed 3 years ago.
thanks for trying to help me^^
And sorry for my bad English(maybe?)
I'm trying to create a shortcut to a PowerShell-Script to execute it.
I'm using:
$WshShell = New-Object -comObject WScript.Shell
$link = $wshshell.CreateShortcut(”$home\Desktop\TimeStamp.lnk”)
$link.targetpath = ("%windir%\System32\WindowsPowerShell\v1.0\powershell.exe -noexit -File " + '"' + ($SaveDir + "\TimeStamp.ps1") + '"')
$link.save()
$SaveDir is the Path where the Script is.
It creates the shortcut, but then there is a error: (In German sorry) Der Wert liegt außerhalb des erwarteten Bereichs.
I tried to find a way to solve the problem. I now know whats the problem: It puts a " at the start and the end of the Target Location in the Properties of the Shortcut. Is there any way to prevent that?
To create a shortcut where you have arguments for the executable lik in your question, you will need to at least also use the Arguments property.
You can use this function which allows you to set all kinds of properties for a new shortcut, including the option to have the executable run as Administrator.
function New-Shortcut {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$TargetPath, # the path to the executable
# the rest is all optional
[string]$ShortcutPath = (Join-Path -Path ([Environment]::GetFolderPath("Desktop")) -ChildPath 'New Shortcut.lnk'),
[string[]]$Arguments = $null, # a string or string array holding the optional arguments.
[string[]]$HotKey = $null, # a string like "CTRL+SHIFT+F" or an array like 'CTRL','SHIFT','F'
[string]$WorkingDirectory = $null,
[string]$Description = $null,
[string]$IconLocation = $null, # a string like "notepad.exe, 0"
[ValidateSet('Default','Maximized','Minimized')]
[string]$WindowStyle = 'Default',
[switch]$RunAsAdmin
)
switch ($WindowStyle) {
'Default' { $style = 1; break }
'Maximized' { $style = 3; break }
'Minimized' { $style = 7 }
}
$WshShell = New-Object -ComObject WScript.Shell
# create a new shortcut
$shortcut = $WshShell.CreateShortcut($ShortcutPath)
$shortcut.TargetPath = $TargetPath
$shortcut.WindowStyle = $style
if ($Arguments) { $shortcut.Arguments = $Arguments -join ' ' }
if ($HotKey) { $shortcut.Hotkey = ($HotKey -join '+').ToUpperInvariant() }
if ($IconLocation) { $shortcut.IconLocation = $IconLocation }
if ($Description) { $shortcut.Description = $Description }
if ($WorkingDirectory) { $shortcut.WorkingDirectory = $WorkingDirectory }
# save the link file
$shortcut.Save()
if ($RunAsAdmin) {
# read the shortcut file we have just created as [byte[]]
[byte[]]$bytes = [System.IO.File]::ReadAllBytes($ShortcutPath)
$bytes[21] = 0x22 # set byte no. 21 to ASCII value 34
[System.IO.File]::WriteAllBytes($ShortcutPath, $bytes)
}
# clean up the COM objects
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($shortcut) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($WshShell) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
Using your example, use it like this:
$SaveDir = 'D:\' #'# the path to the .ps1 file
$props = #{
'ShortcutPath' = Join-Path -Path ([Environment]::GetFolderPath("Desktop")) -ChildPath 'TimeStamp.lnk'
'TargetPath' = '%windir%\System32\WindowsPowerShell\v1.0\powershell.exe'
'Arguments' = '-NoExit -NoLogo -File "{0}"' -f (Join-Path -Path $SaveDir -ChildPath 'TimeStamp.ps1')
# add more parameters here when needed
}
New-Shortcut #props
I have a Powershell script. My ultimate goal is to compare the two Excel files and highlight differences in both versions. Part of my "preparatory code" is this:
function DefineVars () {
Clear-Host
# Define some basic variables
$Directory = Split-Path -Parent $PSCommandPath
$FilePath = $Directory + "\xlsx\"
$FileName1 = $FilePath + "Firewallv2.xlsx"
$FileName2 = $FilePath + "Firewallv3.xlsx"
$OutFile1 = $FilePath + "file1_raw.csv"
$OutFile2 = $FilePath + "file2_raw.csv"
# Create an Object Excel.Application using Com interface
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Excel.DisplayAlerts = $false
# Generate the Workbook Objects
$WorkBook1 = $Excel.Workbooks.Open($FileName1)
$WorkBook2 = $Excel.Workbooks.Open($FileName2)
return $Directory, $FilePath, $FileName1, $FileName2, $OutFile1, $OutFile2, $Excel, $WorkBook1, $WorkBook2
}
function GenerateData ($WorkBook, $OutFile) {
$Results = #()
Write-Host $OutFile
foreach ($CurrentWorkSheet in $WorkBook.Worksheets) {
$CurrentWorkSheetName = $CurrentWorkSheet.Name
$CurrentWorkSheetRows = $CurrentWorkSheet.UsedRange.Rows.Count
$CurrentWorkSheetColumns = $CurrentWorkSheet.UsedRange.Columns.Count
$CurrentWorkSheet.Activate()
for ($CurrentColumn = 1; $CurrentColumn -le $CurrentWorkSheetColumns; $CurrentColumn++) {
for ($CurrentRow = 1; $CurrentRow -le $CurrentWorkSheetRows; $CurrentRow++) {
$CurrentCell = $CurrentWorksheet.Cells.Item($CurrentRow, $CurrentColumn)
$CurrentCellContent = $CurrentCell.Text
if ([System.IO.File]::Exists($OutFile)) {
Write-Host "true"
#","+$CurrentCellContent | Out-File $OutFile -Append
} else {
Write-Host "false"
#$CurrentCellContent | Out-File $OutFile
}
}
}
}
return $Results
}
function CloseExcel () {
$WorkBook1.Close($true)
$WorkBook2.Close($true)
$Excel.Quit()
spps -n Excel
}
$Directory, $FilePath, $FileName1, $FileName2, $OutFile1, $OutFile2, $Excel, $WorkBook1, $WorkBook2 = DefineVars
$ResultsFile1 = GenerateData($WorkBook1, $OutFile1)
$ResultsFile2 = GenerateData($WorkBook2, $OutFile2)
CloseExcel
My problem is that the parameter call to the GenerateData functions of the $OutFile variables won't work for some reason. All the other parameters appear to be passed successfully, e.g. the WorkBooks. But if I insert a Write-Host $OutFile at the beginning of the GenerateData function, the string is empty (which means it doesn't get passed, if I am not mistaken).
I am sure this is easily explained, but I just can't seem to figure this one out.
Thanks and best
Simon
I got it. My problem was the syntax in the main method. Being caught up in other languages, I thought I needed parentheses and commas to pass arguments. Yet, it's much simpler with Powershell:
$ResultsFile1 = GenerateData $WorkBook1 $OutFile1
$ResultsFile2 = GenerateData $WorkBook2 $OutFile2
CompareObjects $ResultsFile1 $ResultsFile2
CloseExcel
This did the trick! The only weird thing about it is that Powershell doesn't throw an error, if you stick to the parentheses-comma style of coding. The argument simply doesn't get passed.
I am sort of new to scripting and here's my task:
A folder with X files. Each file contains some Word documents, Excel sheets, etc. In these files, there is a client name and I need to assign an ID number.
This change will affect all the files in this folder that contain this client's name.
How can do this using Windows Power Shell?
$configFiles = Get-ChildItem . *.config -rec
foreach ($file in $configFiles)
{
(Get-Content $file.PSPath) |
Foreach-Object { $_ -replace " JOHN ", "123" } |
Set-Content $file.PSPath
}
Is this the right approach ?
As #lee_Daily pointed out you would need to have different code to perform a find and replace in different file types. Here is an example of how you could go about doing that:
$objWord = New-Object -comobject Word.Application
$objWord.Visible = $false
foreach ( $file in (Get-ChildItem . -r ) ) {
Switch ( $file.Extension ) {
".config" {
(Get-Content $file.FullName) |
Foreach-Object { $_ -replace " JOHN ", "123" } |
Set-Content $file.FullName
}
{('.doc') -or ('.docx')} {
### Replace in word document using $file.fullname as the target
}
{'.xlsx'} {
### Replace in spreadsheet using $file.fullname as the target
}
}
}
For the actual code to perform the find and replace, i would suggest com objects for both.
Example of word find and replace https://codereview.stackexchange.com/questions/174455/powershell-script-to-find-and-replace-in-word-document-including-header-footer
Example of excel find and replace Search & Replace in Excel without looping?
I would suggest learning the ImportExcel module too, it is a great tool which i use a lot.
For Word Document : This is what I'm using. Just can't figure out how this script could also change Header and Footer in a Word Document
$objWord = New-Object -comobject Word.Application
$objWord.Visible = $false
$list = Get-ChildItem "C:\Users\*.*" -Include *.doc*
foreach($item in $list){
$objDoc = $objWord.Documents.Open($item.FullName,$true)
$objSelection = $objWord.Selection
$wdFindContinue = 1
$FindText = " BLAH "
$MatchCase = $False
$MatchWholeWord = $true
$MatchWildcards = $False
$MatchSoundsLike = $False
$MatchAllWordForms = $False
$Forward = $True
$Wrap = $wdFindContinue
$Format = $False
$wdReplaceNone = 0
$ReplaceWith = "help "
$wdFindContinue = 1
$ReplaceAll = 2
$a = $objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord, `
$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,`
$Wrap,$Format,$ReplaceWith,$ReplaceAll)
$objDoc.Save()
$objDoc.Close()
}
$objWord.Quit()
What If I try to run on C# ? Is anything else missing?
}
string rootfolder = #"C:\Temp";
string[] files = Directory.GetFiles(rootfolder, "*.*",SearchOption.AllDirectories);
foreach (string file in files)
{ try
{ string contents = File.ReadAllText(file);
contents = contents.Replace(#"Text to find", #"Replacement text");
// Make files writable
File.SetAttributes(file, FileAttributes.Normal);
File.WriteAllText(file, contents);
}
catch (Exception ex)
{ Console.WriteLine(ex.Message);
}
}
I am trying to use PowerShell to do a batch conversion of Word Docx to PDF - using a script found on this site:
http://blogs.technet.com/b/heyscriptingguy/archive/2013/03/24/weekend-scripter-convert-word-documents-to-pdf-files-with-powershell.aspx
# Acquire a list of DOCX files in a folder
$Files=GET-CHILDITEM "C:\docx2pdf\*.DOCX"
$Word=NEW-OBJECT –COMOBJECT WORD.APPLICATION
Foreach ($File in $Files) {
# open a Word document, filename from the directory
$Doc=$Word.Documents.Open($File.fullname)
# Swap out DOCX with PDF in the Filename
$Name=($Doc.Fullname).replace("docx","pdf")
# Save this File as a PDF in Word 2010/2013
$Doc.saveas([ref] $Name, [ref] 17)
$Doc.close()
}
And I keep on getting this error and can't figure out why:
PS C:\docx2pdf> .\docx2pdf.ps1
Exception calling "SaveAs" with "16" argument(s): "Command failed"
At C:\docx2pdf\docx2pdf.ps1:13 char:13
+ $Doc.saveas <<<< ([ref] $Name, [ref] 17)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Any ideas?
Also - how would I need to change it to also convert doc (not docX) files, as well as use the local files (files in same location as the script location)?
Sorry - never done PowerShell scripting...
This will work for doc as well as docx files.
$documents_path = 'c:\doc2pdf'
$word_app = New-Object -ComObject Word.Application
# This filter will find .doc as well as .docx documents
Get-ChildItem -Path $documents_path -Filter *.doc? | ForEach-Object {
$document = $word_app.Documents.Open($_.FullName)
$pdf_filename = "$($_.DirectoryName)\$($_.BaseName).pdf"
$document.SaveAs([ref] $pdf_filename, [ref] 17)
$document.Close()
}
$word_app.Quit()
The above answers all fell short for me, as I was doing a batch job converting around 70,000 word documents this way. As it turns out, doing this repeatedly eventually leads to Word crashing, presumably due to memory issues (the error was some COMException that I didn't know how to parse). So, my hack to get it to proceed was to kill and restart word every 100 docs (arbitrarily chosen number).
Additionally, when it did crash occasionally, there would be resulting malformed pdfs, each of which were generally 1-2 kb in size. So, when skipping already generated pdfs, I make sure they are at least 3kb in size. If you don't want to skip already generated PDFs, you can delete that if statement.
Excuse me if my code doesn't look good, I don't generally use Windows and this was a one-off hack. So, here's the resulting code:
$Files=Get-ChildItem -path '.\path\to\docs' -recurse -include "*.doc*"
$counter = 0
$filesProcessed = 0
$Word = New-Object -ComObject Word.Application
Foreach ($File in $Files) {
$Name="$(($File.FullName).substring(0, $File.FullName.lastIndexOf("."))).pdf"
if ((Test-Path $Name) -And (Get-Item $Name).length -gt 3kb) {
echo "skipping $($Name), already exists"
continue
}
echo "$($filesProcessed): processing $($File.FullName)"
$Doc = $Word.Documents.Open($File.FullName)
$Doc.SaveAs($Name, 17)
$Doc.Close()
if ($counter -gt 100) {
$counter = 0
$Word.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Word)
$Word = New-Object -ComObject Word.Application
}
$counter = $counter + 1
$filesProcessed = $filesProcessed + 1
}
This works for me (Word 2007):
$wdFormatPDF = 17
$word = New-Object -ComObject Word.Application
$word.visible = $false
$folderpath = Split-Path -parent $MyInvocation.MyCommand.Path
Get-ChildItem -path $folderpath -recurse -include "*.doc" | % {
$path = ($_.fullname).substring(0,($_.FullName).lastindexOf("."))
$doc = $word.documents.open($_.fullname)
$doc.saveas($path, $wdFormatPDF)
$doc.close()
}
$word.Quit()
Neither of the solutions posted here worked for me on Windows 8.1 (btw. I'm using Office 365). My PowerShell somehow does not like the [ref] arguments (I don't know why, I use PowerShell very rarely).
This is the solution that worked for me:
$Files=Get-ChildItem 'C:\path\to\files\*.docx'
$Word = New-Object -ComObject Word.Application
Foreach ($File in $Files) {
$Doc = $Word.Documents.Open($File.FullName)
$Name=($Doc.FullName).replace('docx', 'pdf')
$Doc.SaveAs($Name, 17)
$Doc.Close()
}
I've updated this one to work on latest office :
# Get invocation path
$curr_path = Split-Path -parent $MyInvocation.MyCommand.Path
# Create a PowerPoint object
$ppt_app = New-Object -ComObject PowerPoint.Application
#$ppt.visible = $false
# Get all objects of type .ppt? in $curr_path and its subfolders
Get-ChildItem -Path $curr_path -Recurse -Filter *.ppt? | ForEach-Object {
Write-Host "Processing" $_.FullName "..."
# Open it in PowerPoint
$document = $ppt_app.Presentations.Open($_.FullName,0,0,0)
# Create a name for the PDF document; they are stored in the invocation folder!
# If you want them to be created locally in the folders containing the source PowerPoint file, replace $curr_path with $_.DirectoryName
$pdf_filename = "$($curr_path)\$($_.BaseName).pdf"
# Save as PDF -- 17 is the literal value of `wdFormatPDF`
#$opt= [Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType]::ppSaveAsPDF
$document.SaveAs($pdf_filename,32)
# Close PowerPoint file
$document.Close()
}
# Exit and release the PowerPoint object
$ppt_app.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ppt_app)