Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't get all excel processes to stop when closing through Powershell

With this code, I am opening excel(with visible = false so the user cannot see it), writing to a workbook, and then either opening excel(making it visible) after the script ends or closing it completely without saving. When I save excel, leave it open, end the script, and then manually close excel later, there are no background processes in the task manager. However, when I close excel with the script, it remains in the task manager.

Here is how I start excel:

    $script:excel = new-object -ComObject excel.application # create excel object
    $excel.visible = $false # hide excel window
    $script:workbook = $excel.Workbooks.Add() # add excel file
    $script:ws1 = $workbook.Worksheets.Item(1) # create new sheet

Here is how I close it:

        [gc]::Collect()
        [gc]::WaitForPendingFinalizers()
        if ($script:closeOnX) {
            #only do this if not keeping excel open
            Write-Host "Closing Excel"
            $excel.Quit()
        }
        [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel)

closeOnX is just a flag so it only actually closes the excel app on certain occasions. The rest is executed each time the script ends.

When I end the script and close excel at the same time, I want only the current excel process to close (which is why I don't want to stop-process) and not close other workbooks that the user may be working on.

When I end the script, save and open the excel, I want all the processes to be gone when the user manually closes excel. (This is working)

like image 280
danualpxc Avatar asked Mar 29 '19 18:03

danualpxc


People also ask

How do I stop background processes in Excel?

Restart the computer. 2. End Process to Winword.exe and Excel.exe in the task manager 3. Kill the process using taskkill.exe Although 1 is the easiest, it would also be time consuming to wait for the computer to start up again, launch all of the programs etc.

Why is Excel not closing?

Several things might potentially cause Excel not close on demand. For instance, a lack of RAM could be the culprit or perhaps having too many applications open at once, which places a heavy load on the processor in your computer. So, close all other applications first, and then attempt to close Excel again.

How do I close Excel from Task Manager?

To manually close any process/task from the task manager, all you have to do is select the task/process in the task manager and use the End Task/End Process button to close the task.


1 Answers

For general guidance on how to release (Excel) COM objects, see the bottom section.

$excel.Quit() is enough to eventually terminate the Excel process, but when that happens depends on when the garbage collector happens to run the next time.

Your attempt to explicitly release Excel with [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) is insufficient, because variables $script:workbook and $script:ws1 still have references to Excel COM objects that will not be released until the variables have gone out of scope and these references are eventually garbage collected.

Therefore, in order to speed up the release, you must release these references explicitly too, before running the garbage collector:

$script:excel = new-object -ComObject excel.application # create excel object
$script:workbook = $excel.Workbooks.Add() # add a workbook
$script:ws1 = $workbook.Worksheets.Item(1) # reference the 1st sheet

# ...

# You must *always* call .Quit(), otherwise the Excel process lingers
# for the entire OS users session.
$script.excel.Quit()

# Relinquish references to *all* Excel objects.
$script:excel = $script:workbook = $script:ws1 = $null
# Alternative:
# Remove-Variable -Scope Script excel, workbook, ws1

# With all references released, running the garbage collector
# should now release the COM objects and terminate Excel
# shortly after.
[GC]::Collect()
# Note that calling [GC]::WaitForPendingFinalizers() afterwards
# to wait for *completion* of the *doesn't work here*,
# because the CLR-managed RCWs (Runtime-Callable Wrappers for COM objects)
# do not guarantee deterministic release of the underlying COM objects.

Because manually clearing / removing all relevant variables is error-prone and cumbersome, you can automate the process by creating all variables that reference COM objects locally in a temporary child scope, using & { ... }:

& {  # Create a temporary child scope.

  $excel = new-object -ComObject excel.application # create excel object
  $workbook = $excel.Workbooks.Add() # add a workbook
  $ws1 = $workbook.Worksheets.Item(1) # reference the 1st sheet

  # You must *always* call .Quit(), otherwise the Excel process lingers
  # for the entire OS users session.
  $excel.Quit()

} # On exiting this block, $excel, $workbook, and $ws1
  # go out of scope and release the COM objects when the
  # garbage collector runs next.

# Run the garbage collector now.
# The Excel process should terminate shortly after.
[GC]::Collect()

Releasing (Excel) COM Objects:

  • ALWAYS call .Quit() - without it, the Excel process that is created behind the scenes is never terminated, not even when the PowerShell session ends (of course, it is terminated when the OS user session as a whole ends).

  • $excel.Quit() is usually all that is needed (unless global variables variables are used to store references to Excel objects), because with the script / function variables that reference COM objects going out of scope, the underlying COM objects are eventually released automatically too.

    • However, it may take a - varying, unpredictable - while for the Excel process to actually terminate, depending on when the objects that that the gone-out-of-scope variables are garbage-collected.
  • If you want the COM objects to be released as quickly as possible:

    • You must release references to all COM objects you've stored in individual variables:

      • Note that there is NO NEED for [System.Runtime.InteropServices.Marshal]::ReleaseComObject() calls; because there is a simpler and more robust alternative:
      • Either: Clear all variables referencing COM objects explicitly, by (see first code snippet above):
        • either: setting them all to $null.
        • or: passing their names to Remove-Variable
      • Or, preferably: Release the references implicitly (see second code snippet above):
        • Use variables that reference COM objects in a child scope, via a & { ... } block, which means that the references will implicitly be released on leaving the child scope.
    • These approaches are not only simpler and more concise than calling [System.Runtime.InteropServices.Marshal]::ReleaseComObject(), but also prevent later attempts at accessing already-released COM objects.

    • Afterwards, call [GC]::Collect() to force instant garbage collection - but note that your code is blocked while the garbage collector runs (albeit usually only briefly) .

  • If you additionally want to make sure that releasing the COM objects has completed before you continue:

    • Note: There's probably rarely a need for this, because Excel usually releases resources when its .Quit() method is called, such as closing files it has open.

    • You can call [GC]::WaitForPendingFinalizers() after calling [GC]::Collect(), but it is likely not to work: The RCWs (runtime-callable wrappers) that manage access to the COM objects themselves being finalized does not guarantee release of the COM resources at that time; from the docs (emphasis added):

      • "When the reference count on the COM object becomes 0, the COM object is usually freed, although this depends on the COM object's implementation and is beyond the control of the runtime."

      • Indeed, in the case at hand the Excel process does not terminate before the [GC]::WaitForPendingFinalizers() call returns; that only happens within a second or so afterwards.

like image 176
mklement0 Avatar answered Oct 30 '22 02:10

mklement0