I have the following code and it works perfectly except it's not closing Excel properly. It's leaving an Excel process running.
Is there a way to close Excel properly without killing the process?
Since i'm using other Excel files while running this script i can not kill all active Excel processes.
I think i tried everything i found online.
$WorkDir = "D:\Test\QR_ES\RG_Temp"
$BGDir = "D:\Test\QR_ES\3_BG"
$File = "D:\Test\QR_ES\4_Adr_Excel\KD_eMail.xlsx"
$SentDir = "D:\Test\QR_ES\RG_Temp\Sent\Dunning"
chdir $WorkDir
$firstPageList = Get-ChildItem "$WorkDir\1*.pdf" -File -Name
ForEach ($firstPage in $firstPageList)
{
$secondPage = "$BGDir\BG_RG.pdf"
$output = "Dunn-$firstPage"
invoke-command {pdftk $firstPage background $secondPage output $output}}
del 1*.pdf
gci $WorkDir\Dunn-*.pdf | rename-item -newname {$_.Name.Substring(5)} -Force
$Excel = New-Object -ComObject Excel.Application
$Excel.visible = $false
$Workbook = $Excel.workbooks.open($file)
$DunnList = Get-ChildItem "$WorkDir\1*.pdf" -File -Name
ForEach ($Dunn in $DunnList)
{
$Worksheets = $Workbooks.worksheets
$Worksheet = $Workbook.Worksheets.Item("KD_eMail")
$Range = $Worksheet.Range("A1").EntireColumn
$DunnSearch = $Dunn.Substring(0,5)
$SearchString = $DunnSearch
$Search = $Range.find($SearchString)
$Recipient = $Worksheet.Cells.Item($Search.Row, $Search.Column + 1)
$Msg = "<span style='font-family:Calibri;font-size:12pt;'>Test</span>"
$Outlook = New-Object -ComObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
$namespace.Logon($null, $null, $false, $true)
$EmailFrom = ('test#test.com')
$account = $outlook.Session.Accounts.Item($EmailFrom)
$Mail = $Outlook.CreateItem(0)
$Mail.HTMLBody = $Msg
$Mail.Subject = "OP - $SearchString"
$Mail.To = $Recipient
function Invoke-SetProperty {
param(
[__ComObject] $Object,
[String] $Property,
$Value
)
[Void] $Object.GetType().InvokeMember($Property,"SetProperty",$NULL,$Object,$Value)
}
Invoke-SetProperty -Object $mail -Property "SendUsingAccount" -Value $account
$Mail.Attachments.Add("$WorkDir\$Dunn")
$Mail.Save()
$Mail.close(1)
$Mail.Send()}}
$workbook.close($false)
$Excel.Quit()
chdir $WorkDir
del 1*.pdf
See this post:
https://stackoverflow.com/a/35955339/5329137
which is not accepted as an answer, but I believe is the full, correct way to close Excel.
This is what did it for me:
$FilePID = (Get-Process -name Excel | Where-Object { $_.MainWindowTitle -like 'FileName.xlsx*' }).Id
$Workbook.Save()
$Workbook.close($false)
Stop-Process $FilePID
Elaborating on #ASD's answer, since the MainWindowTitle doesn't (always) include the file suffix (.xlsx) you may have to strip that when comparing it to the filename. I'm using -replace to use a Regex match of everything before the last dot.
$excelPID = (Get-Process -name Excel | Where-Object { $_.MainWindowTitle -eq $fileName -replace '\.[^.]*$', '' }).Id
$workbook.Close()
Stop-Process $excelPID
Related
I have multiple Excel files that have similar data, and I want a PowerShell script that searches and replaces a string of text in all Excel files in a specified folder.
So far, I have the following PowerShell script that opens all my Excel files and attempts to replace matching strings in the second Worksheet, but the expected changes are not made:
$Path = "C:\Users\mabrant\Downloads\Workstations (21)\Workstations\"
$files = Get-ChildItem "C:\Users\mabrant\Downloads\Workstations (21)\Workstations" -Filter *.xlsx
ForEach ($item in $files) {
$Excel = New-Object -ComObject Excel.Application
$Excel.visible = $true
$Workbook = $Excel.workbooks.open($Path + [System.IO.Path]::GetFileName("$item"))
$Worksheets = $Workbooks.worksheets
$Worksheet = $Workbook.Worksheets.Item(2)
$SearchString = "NEW" #String to Find
$Range = $Worksheet.Range("S4:Y4").EntireColumn #Range of Cells to look at
$Search = $Range.find($SearchString) }
$Search = $Range.find($SearchString)
if ($search -ne $null) {
$FirstAddress = $search.Address
do {
$Search.value() = "Installed" # Replacement Value
$search = $Range.FindNext($search)
} while ( $search -ne $null -and $search.Address -ne $FirstAddress)
}
$WorkBook.Save()
$WorkBook.Close()
[void]$excel.quit() `
Seems like you were really close to having a working script. I believe the main problem is that your ForEach block should include everything except the $excel.quit() so that you save and close each workbook as you go.
I reformatted your code to make it easier to see the entire ForEach block, I removed the duplicate $Search = $Range.find($SearchString) statement, and I set some Excel.Application properties to $false to make it work better.
Here is the updated code:
$Path = "C:\Users\mabrant\Downloads\Workstations (21)\Workstations\"
$files = Get-ChildItem $Path -Filter *.xlsx
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Excel.EnableEvents = $false
$Excel.DisplayAlerts = $false
ForEach ($item in $files) {
$Workbook = $Excel.Workbooks.Open($Path + [System.IO.Path]::GetFileName("$item"))
$Worksheet = $Workbook.Worksheets.Item(2)
$SearchString = "NEW" #String to Find
$Range = $Worksheet.Range("S4:Y4").EntireColumn #Range of Cells to look at
$Search = $Range.find($SearchString)
if ($Search -ne $null) {
$FirstAddress = $Search.Address
do {
$Search.Value() = "Installed" # Replacement Value
$Search = $Range.FindNext($Search)
} while ( $Search -ne $null -and $Search.Address -ne $FirstAddress)
}
$WorkBook.Save()
$WorkBook.Close()
}
$Excel.Quit()
I use below script to convert bunch of xls files to xlsx.
$folderpath = %tempPath%
$filetype ="*xls"
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlWorkbookDefault
write-host $xlFixedFormat
$excel = New-Object -ComObject excel.application
$excel.visible = $true
Get-ChildItem -Path $folderpath -Include $filetype -recurse |
ForEach-Object `
{
$path = ($_.fullname).substring(0, ($_.FullName).lastindexOf("."))
"Converting $path"
$workbook = $excel.workbooks.open($_.fullname)
$path += ".xlsx"
$workbook.saveas($path, $xlFixedFormat)
$workbook.close()
}
$excel.Quit()
$excel = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
It used to work perfectly running on VM.
Unfortunately with changing folder path I realised there are popup windows to confirm saving that didn't come up before and the script gets stuck on that.
Any simple corrections that could prevent that error?
"scriptError": {
"localizedName": "Error",
"value": "Unable to get the SaveAs property of the Workbook class\r\nAt C:\\Users\\~
"variableName": "ScriptError"
}
Here's an example of how I set the path when saving an Excel file using PowerShell. I set the path using a combination of the Get-Location cmdlet, Get-Date cmdlet and the file name, which is stored in a string variable for use when saving the script.
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlWorkbookDefault
$htFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlHtml
$Date = get-date -format R
$CurrentLocation = Get-Location
$CurrentDir = Get-location
$Timestamp = get-date -format d
$xlsx = [String] $CurrentLocation + "\MyNewExcelStuff-" + $Timestamp + ".xlsx"
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$excel.DisplayAlerts = $False
$workbook = $excel.Workbooks.add()
$sheet1 = $workbook.worksheets.Item(1)
$sheet1.name = "Stuff"
$Sheet1.Cells.Item(1,1) = "Reporting Stack Stuff"
$title = $Sheet1.Range("A1:K1")
$title.Select()
$title.MergeCells = $true
$title.VerticalAlignment = -4108 # Centre (vertically) heading
$title.HorizontalAlignment = -4108 # Centre (horizontally) heading
$Title.Interior.ColorIndex = 0
$Excel.ActiveWorkbook.SaveAs($xlsx, $xlFixedFormat)
Start-Sleep -s 2
$Excel.Quit()
$Excel = $Null
You should use $workbook.Close($false).
I have a code that allows me to search and replace in multiple Excel files within a folder and its subfolders.
However, this does not work properly yet, because I think that the following line contains an error:
$Workbook = $Excel.workbooks.open($Path + [System.IO.Path]::GetFileName("$item"))
All Excel files located directly in the main "test" folder are opened and saved correctly. However, the Excel files in the subfolders are not opened and saved.
This is the code:
$Path = "G:\test"
$files = Get-ChildItem -Path G:\test -Filter *.xlsx -Recurse -ErrorAction SilentlyContinue -Force
ForEach ($item in $files) {
$Excel = New-Object -ComObject Excel.Application
$Excel.visible = $false
$Workbook = $Excel.workbooks.open($Path + [System.IO.Path]::GetFileName("$item"))
$Worksheets = $Workbooks.worksheets
$Worksheet = $Workbook.Worksheets.Item(1)
$SearchString = "Test" #String to Find
$Range = $Worksheet.Range("A1:DZ100000").EntireColumn #Range of Cells to look at
$Search = $Range.find($SearchString)
if ($search -ne $null) {
$FirstAddress = $search.Address
do {
$Search.value() = $Search.value().Replace($SearchString, "Test2") #String to Update
$search = $Range.FindNext($search)
} while ( $search -ne $null -and $search.Address -ne $FirstAddress)
}
$WorkBook.Save()
$WorkBook.Close()
[void]$excel.quit()
}
I hope someone can help me.
I used below code to convert file from CSV to xlsx. But it only convert single file at a time. I want this to convert all the files in directory at a time.
$xl = New-Object -ComObject Excel.Application
$xl.Visible = $true
$Workbook = $xl.Workbooks.Open("$loglocation\errors_$server.csv")
$Worksheets = $Workbooks.Worksheets
$Workbook.SaveAs("$loglocation\errors_$server.xls",1)
$Workbook.Saved = $true
$xl.Quit()
With the PSExcel Module you can use Export-XLSX which makes this process very simple:
$loglocation = "C:\folder"
Get-ChildItem -Path $loglocation -Filter *.csv | foreach {
Export-XLSX -InputObject $_ -Path "$loglocation\$($_.BaseName).xlsx"
}
Try this, should work:
$filePath = Get-ChildItem -Path "path to csv" -filter *.csv
foreach ($file in $filePath )
{
$filename = $file.FullName
$filename
$xl = new-object -comobject excel.application
$xl.visible = $true
$Workbook = $xl.workbooks.open($filename)
$Worksheets = $Workbooks.worksheets
$Workbook.SaveAs($filename.Substring(0,$filename.Length-4) + ".xlsx",1)
$Workbook.Saved = $True
$xl.Quit()
}
Can move the excel connections outside the loop as well if you need to speed it up
I am running the script below as a scheduled task with the user logged on the server. It converts an xls file to csv using the Excel.Application COM object. The conversion works, but eventually breaks and I don't know why.
I have the task run the following command which should in theory allow it to run constantly:
powershell.exe -noexit -file "filename.ps1"
Any thoughts on what to try?
$server = "\\server"
$xls = "\path\XLS\"
$csv = "\path\CSV\"
$folder = $server + $xls
$destination = $server + $csv
$filter = "*.xls" # <-- set this according to your requirements
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{
IncludeSubdirectories = $true # <-- set this according to your requirements
NotifyFilter = [IO.NotifyFilters]"FileName, LastWrite"
}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$excelFile = $folder + $name
$E = New-Object -ComObject Excel.Application
$E.Visible = $false
$E.DisplayAlerts = $false
$wb = $E.Workbooks.Open($excelFile)
foreach ($ws in $wb.Worksheets) {
$n = "output_" + $name -replace ".XLS"
$ws.SaveAs($destination + $n + ".csv", 6)
}
$E.Quit()
}
I was doing something similar with word. I couldnt use Quit alone. I think using Quit hides Excel. Try releasing the com object by using:
$E.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($E)
Remove-Variable E
i dont know if you are opening the Excel application but if you are you can maybe use
$wb.close($false)
Let me know if it works...
Ref: https://technet.microsoft.com/en-us/library/ff730962.aspx?f=255&MSPPError=-2147217396