12

No matter what I try, Excel 2013 continues to run in the background on Windows 10 no matter what commands I throw at the end of my PowerShell script. I've tried adding all suggestions I've found to the end of my script and the only Excel object I open continues to remain open. Here is what I have at the end of my script. Any other suggestions?

## Quit Excel and Terminate Excel Application process:
$xlsxwb.quit
$xlsxobj.Quit

[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsxobj)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsxwb)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsxSh1)
Start-Sleep 1
'Excel processes: {0}' -f @(Get-Process excel -ea 0).Count
mtb2434
  • 147
  • 1
  • 2
  • 7
  • 1
    I always end up killing the process with `Stop-Process`. Sledge-hammer approach. Nothing else seems to do any good. – Chris Dent Aug 19 '16 at 17:59
  • please try using .quit(). Also, release application object last, release the objects in the reverse sequence of creation. – Uttam Jul 18 '20 at 14:40
  • 1
    The solution is presented [here in stackoverflow](https://stackoverflow.com/questions/55423220/cant-get-all-excel-processes-to-stop-when-closing-through-powershell). – alfred s. Dec 02 '20 at 12:51

6 Answers6

22

I ran into the same problem and tried various solutions without success. I got closer when I started releasing all of the COM objects I saved as variables, not just the ones for the workbook, worksheet, and Excel application.

For example, take the following example code:

$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $False
$Workbook = $Excel.Workbooks.Open("C:\Temp\test.xlsx")
$Worksheet = $Workbook.Worksheets.Item(1)
$UsedRange = $Worksheet.UsedRange
$Range = $Worksheet.Range("A1:B10")
$Workbook.Close()
$Excel.Quit()
[void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($Range)
[void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($UsedRange)
[void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($Worksheet)
[void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($Workbook)
[void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
[GC]::Collect()

If you were to take out just one of the ReleaseComObject statements, the Excel process would remain open. In my code I release all the ones like ranges, tables, etc. first and then I do the worksheet, workbook, and finally the Excel application itself. Then because that only seemed to work like 90% of the time, I added the garbage collection command at the end and finally had a solution that seems to work every time without having to kill the process.

Note: My system is Windows 8.1 with PowerShell v5 and Office 2013.

Jon Dechiro
  • 1,396
  • 12
  • 7
6

Here's a simple example below. It will likely require some additional code for more complex procedures.

function _FullQuit {
    while ( $this.Workbooks.Count -gt 0 ) {
        $this.Workbooks.Item(1).Close()
    }
    $this.Quit()
    [System.Runtime.InteropServices.Marshal]::ReleaseComObject($this)
    [System.GC]::Collect()
    [System.GC]::WaitForPendingFinalizers()
}

function New-Excel {
$object = New-Object -ComObject "Excel.Application"
$object | Add-Member -MemberType ScriptMethod -Name FullQuit -Value {_FullQuit}
$object
}

$xl = New-Excel

$wb1 = $xl.Workbooks.Open("C:\Data1.csv")
$wb2 = $xl.Workbooks.Open("C:\Data2.csv")

$xl.FullQuit()
annoying_squid
  • 513
  • 5
  • 10
2
  1. Create Excel Application.
  2. Make it visible
  3. Get Process Id of the application.
  4. Hide Excel Application.
  5. Stop process my process id. Sample Code

    # Create Excel Application
    $excel = New-Object -comobject Excel.Application
    
    # Make it visiable
    $excel.Visible = $true
    
    # Get Windows handle of the application
    $excelWinHwnd = $excel.Hwnd
    
    # Get Process Id of the application
    $process = Get-Process Excel | Where-Object {$_.MainWindowHandle -eq $excelWinHwnd}
    $excelProcessId = $process.Id
    
    # Hide the application : Run In background 
    $excel.Visible = $false
    
    # Kill/Stop the process by id
    Stop-Process -Id $excelProcessId
    
DigviJay Patil
  • 986
  • 14
  • 31
1

The above solutions did not work for me, in the sequence I needed the final step was .saveas(file.xlsx) which meant that the remaining unsaved document still popped a gui interface requiring user interaction to save/don't save/cancel.

I ended up with the following, which is admittedly rough, but worked for me.

At the beginning of the script:

$existingExcel = @() 
Get-Process Excel | % {$existingExcel += $_.ID }
function Stop-Excel
    {
    Get-process EXCEL | % {IF($_.ID -notmatch $existingExcel){Stop-Process -ID $_.ID}}
    }

and at the end of the script

Stop-Excel

This has the advantage of completely destroying any lingering excel processes without terminating any other live excel processes that may be in use by the users that run have to this script.

The disadvantages are that when you next load excel you are presented with a crashed excel document recovery dialogue.

Patrick
  • 265
  • 1
  • 5
  • 19
0

Alternative answer:

I understand that my reply is late, still, consider following approach to get this done.

  1. At the beginning, get the PIDs of any/all instances of excel.exe, using command:

    tasklist /FI "imagename eq excel.exe"
    
  2. Run the part of script that generates its instance of excel.exe. Use the command in step 1 to identify and save PID of newly generated instance of excel.exe (say wxyz), this will be different from already existing PIDs saved in step 1.

  3. At the end of script, close the specific instance (with PID saved in step 2), using command:

    TASKKILL /f /PID wxyz
    

where wxyz is 4 digit PID saved in step 2.

Cà phê đen
  • 1,883
  • 2
  • 21
  • 20
Rajesh
  • 1
  • 1
0

If nothing else works reliably, try to make the Excel application's DisplayAlerts to tru just before quitting.

$xlapp.DisplayAlerts=$true
$xlapp.quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject([System.__ComObject]$xlapp)
$xlapp=$null
remove-variable xlapp
[GC]::Collect()

This always works for me.

Uttam
  • 596
  • 1
  • 6
  • 11