The following program runs an Excel VBA macro named "Macro1" from Powershell on a group of files in a folder location "c:\mfolder". How can I replicate it for a Word VBA macro?
*****runexcel.ps1 ******
$excel = new-object -comobject excel.application
$excelFiles = Get-ChildItem -Path C:\mfolder -Include *.xls -Recurse
Foreach($file in $excelFiles)
{
$workbook = $excel.workbooks.open($file.fullname)
$worksheet = $workbook.worksheets.item(1)
$excel.Run("Macro1")
$workbook.save()
$workbook.close()
}
$excel.quit()
To open MS Word via Powershell, use the following command:
$word = new-object –comobject Word.Application
Within your loop, use this to open each file:
$doc = $word.documents.open($file.fullname)
You should be able to adapt the rest from the script you provided.
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".
I have added spell checking by reading contents of a file using powershell script?
This script does my job, but I want to check if there are any external packages or modules available for the same, since it would make the work easier.
$file = Get-ChildItem ./Code-Duplication/master.md
$Proofread_text = Get-Content $file.FullName
$Word = New-Object -COM Word.Application
$Document = $Word.Documents.Add()
$Textrange = $Document.Range(0)
#$english = FindLanguage("English (US)")
#$Textrange.LanguageID = $english.ID
$Textrange.InsertAfter($Proofread_text)
<#Handle misspelled words here#>
$file.Name
Write-Output "---------------"
foreach($spell_error in $textrange.SpellingErrors){
Write-Host $spell_error.Text
}
$Document.Close(0)
$Word.Quit()
This is my code, below:
# Specify the path to the Excel file and the WorkSheet Name
$FilePath = "C:\Downloads\Portalroom_CW30.xlsx"
$SheetName = "4_docexchange"
# Create an Object Excel.Application using Com interface
$objExcel = New-Object -ComObject Excel.Application
# Disable the 'visible' property so the document won't open in excel
$objExcel.Visible = $false
# Open the Excel file and save it in $WorkBook
$WorkBook = $objExcel.Workbooks.Open($FilePath)
# Load the WorkSheet 'BuildSpecs'
$WorkSheet = $WorkBook.sheets.item($SheetName)
$WorkBook.close($true)
$objExcel.Quit()
Please help me resolv, Thanks !!
You can call the Delete() method on your worksheet in order to do so. But before that you would want to set DisplayAlerts option as $false and would save the $Workbook after you are done deleting. Your code will look like this -
#Specify the path to the Excel file and the WorkSheet Name
$FilePath = "C:\Downloads\Portalroom_CW30.xlsx"
$SheetName = "4_docexchange"
#Create an Object Excel.Application using Com interface
$objExcel = New-Object -ComObject Excel.Application
#Disable the 'visible' property so the document won't open in excel
$objExcel.Visible = $false
#Set Display alerts as false
$objExcel.displayalerts = $False
#Open the Excel file and save it in $WorkBook
$WorkBook = $objExcel.Workbooks.Open($FilePath)
#Load the WorkSheet 'BuildSpecs'
$WorkSheet = $WorkBook.sheets.item($SheetName)
#Deleting the worksheet
$WorkSheet.Delete()
#Saving the worksheet
$WorkBook.Save()
$WorkBook.close($true)
$objExcel.Quit()
I've got an application that opens a winform and asks the user to input a PDF file. Because I can't read strings in PDF files easily, I need to convert it to a .txt. When the user clicks OK, the application does this.
The problem I'm having is now using the .txt file object and passing it to another command without knowing the name of it. When I try to pipe it to another command, it won't work because I don't have the path. I think this is because the output of conversion is the string "OK" and not the actual .txt file.
How can I convert the PDFs to text (I'm using Xpdf) and pass the converted file down the pipeline for further processing?
If the means I'm using is the problem, how can I accomplish this task another way?
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.StartPosition = 'CenterScreen'
$button = New-Object System.Windows.Forms.Button
$form.Controls.Add($button)
$button.Text = 'Get file'
$button.Location = '10,10'
$button.Add_Click({
$ofd = New-Object system.windows.forms.Openfiledialog
$ofd.Filter = 'PDFs (*.pdf)|*.pdf'
$script:filename = 'Not found'
if ($ofd.ShowDialog() -eq 'Ok') {
$script:filename = $textbox.Text = $ofd.FileName
}
})
$buttonOK = New-Object System.Windows.Forms.Button
$form.Controls.Add($buttonOK)
$buttonOK.Text = 'Ok'
$buttonOK.Location = '10,40'
$buttonOK.DialogResult = 'OK'
$textbox = New-Object System.Windows.Forms.TextBox
$form.Controls.Add($textbox)
$textbox.Location = '100,10'
$textbox.Width += 50
$form.ShowDialog()
$output = & "C:\Users\eakinsa\Desktop\Style Guide Report\Includes\bin32\pdftotext" $filename
$output |
Get-Location -OutVariable textFile |
Select-String -Path $textFile -Pattern ed
Per Ansgar:
I amended the lines last few lines to, for now, maintain the default functionality of pdftotext where it creates the file in the same directory with the same name, as with his suggestion, I could easily replace .pdf with .txt on the end of the file path, thereby having the flexibility to pass the correct file path to subsequent functions. That made it so I was able to search the text file.
& "C:\users\eakinsa\Desktop\Style Guide Report\Includes\bin32\pdftotext" $filename
$pdf = Get-Item $filename
$textfile = $filename -replace '\.pdf$', '.txt'
Select-String -Path $textfile -Pattern ed
When you run pdftotext with just the input PDF as argument it creates the output text file in the same directory with the same basename and the extension txt.
& pdftotext C:\temp\foo.pdf # creates C:\temp\foo.txt
So you can build the text file path like this:
$pdf = Get-Item $filename
$textfile = Join-Path $pdf.DirectoryName ($pdf.BaseName + '.txt')
or like this:
$textfile = $filename -replace '\.pdf$', '.txt'
Alternatively you can tell pdftotext where to create the output file:
$textfile = 'C:\some\where\bar.txt'
& pdftotext $filename $textfile # creates C:\some\where\bar.txt
Doing a search on the above Com operations yields links dating to '09 and even earlier. Perhaps it hasn't changed but I find myself bumping up against errors where 'it is being used by another process.' - even though no Excel app is open on my desktop. I have to reboot to resume.
To be clear - I'm trying to open an existing file; immediately SaveAs() (that much works), add a sheet, Save(); Close() - and then, importantly, repeat that cycle. In effect, I'm creating a few dozen new sheets within a loop that executes the above 'Open Master; SaveAs(); Edit Stuff; Save; Close;
From the examples I've seen this is not a typical workflow for PowerShell. Pasted at the very bottom is my provisional script - pretty rough and incomplete but things are opening what they need to open and adding sheet also works - until I know I have the right way to cleanly close stuff out I'm not worried about the iterations.
I've found a couple examples that address closing:
From http://theolddogscriptingblog.wordpress.com/2012/06/07/get-rid-of-the-excel-com-object-once-and-for-all/
$x = New-Object -com Excel.Application
$x.Visible = $True
Start-Sleep 5
$x.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($x)
Remove-Variable x
And from http://social.technet.microsoft.com/Forums/windowsserver/en-US/24e57b61-e792-40c1-8aff-b0a8205f48ab/updated-opened-excel-using-powershell?forum=winserverpowershell
Set-ItemProperty $path -name IsReadOnly -value $false
$Excel.ActiveWorkBook.Save()
$openfile.Close()
$openfile = $null
$Excel.Quit()
$Excel = $null
[GC]::Collect()
<>
function MakeNewBook($theWeek, $AffID){
$ExcelFile = "C:\csv\InvoiceTemplate.xlsm"
$Excel = New-Object -Com Excel.Application
$Excel.Visible = $True
$Workbook = $Excel.Workbooks.Open($ExcelFile)
$theWeek = $theWeek -replace "C:\\csv\\", ""
$theWeek = $theWeek -replace "\.csv", ""
$theWeek = "c:\csv\Invoices\" +$AffID +"_" + $theWeek + ".xlsm"
$SummaryWorksheet = $Workbook.worksheets.Item(1)
$Workbook.SaveAs($theWeek)
return $Excel
}
function MakeNewSheet($myBook, $ClassID){
$SheetName = "w"+$ClassID
#$Excel = New-Object -Com Excel.Application
#$Excel.Visible = $True
$wSheet = $myBook.WorkSheets.Add()
}
function SaveSheet ($myExcel)
{
#$WorkBook.EntireColumn.AutoFit()
#Set-ItemProperty $path -name IsReadOnly -value $false
$myExcel.ActiveWorkBook.Save()
$openfile= $myExcel.ActiveWorkBook
$openfile.Close()
$openfile = $null
$myExcel.Quit()
$myExcel = $null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($myExcel)
Remove-Variable $myExcel
[GC]::Collect()
}
$theWeek = "C:\csv\wkStart2013-11-04.csv"
$x = Import-Csv $theWeek
foreach ($xLine in $x){
if ($x[0]){
$AffID = $x[1].idAffiliate
$myExcel = MakeNewBook $theWeek $x[1].idAffiliate
$ClassID = $x[1].idClass
MakeNewSheet $myExcel $ClassID
continue
}
SaveSheet($myExcel)
$AffID = $_.$AffID
$wID = $xLine.idClass
#MakeNewSheet($wID)
Echo "$wID"
}
As a follow up after playing around with this issue myself. I geared my solution around Ron Thompson's comment minus the function calls:
# collect excel process ids before and after new excel background process is started
$priorExcelProcesses = Get-Process -name "*Excel*" | % { $_.Id }
$Excel = New-Object -ComObject Excel.Application
$postExcelProcesses = Get-Process -name "*Excel*" | % { $_.Id }
# your code here (probably looping through the Excel document in some way
# try to gently quit
$Excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
# otherwise allow stop-process to clean up
$postExcelProcesses | ? { $priorExcelProcesses -eq $null -or $priorExcelProcesses -notcontains $_ } | % { Stop-Process -Id $_ }
My experience has been that the Quit method doesn't work well, especially when looping. When you get the error, instead of rebooting, open up Task Manager and look at the Processes tab. I'm willing to bet you'll see Excel still open -- maybe even multiple instances of it. I solved this problem by using Get-Process to find all instances of Excel and piping them to Stop-Process. Doesn't seem like that should be necessary, but it did the trick for me.
You should not have to keep track of processes and kill them off.
My experience has been that to properly and completely close Excel (including in loops), you also need to release COM references. In my own testing have found removing the variable for Excel also ensures no remaining references exist which will keep Excel.exe open (like if you are debugging in the ISE).
Without performing the above, if you look in Task Manager, you may see Excel still running...in some cases, many copies.
This has to do with how the COM object is wrapped in a “runtime callable wrapper".
Here is the skeleton code that should be used:
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$workbook = $excel.Workbooks.Add()
# or $workbook = $excel.Workbooks.Open($xlsxPath)
# do work with Excel...
$workbook.SaveAs($xlsxPath)
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
# no $ needed on variable name in Remove-Variable call
Remove-Variable excel
Try this
$filePath = "E:\TSMBackup\TSMDATAPull\ODCLXTSM01_VM.xlsx"
$excelObj = New-Object -ComObject Excel.Application
$excelObj.Visible = $true
$workBook = $excelObj.Workbooks.Open($filePath)
$workSheet = $workBook.Sheets.Item("Sheet1")
$workSheet.Select()
$workBook.RefreshAll()
$workBook.Save()
$excelObj.Quit()