通过 Powershell 关闭时无法停止所有 Excel 进程

dan*_*pxc 9 com excel powershell user-interface rcw

使用此代码,我打开 excel(使用visible = false,以便用户看不到它),写入工作簿,然后在脚本结束后打开 excel(使其可见)或完全关闭它而不保存。当我保存 Excel、使其保持打开状态、结束脚本,然后稍后手动关闭 Excel 时,任务管理器中没有后台进程。但是,当我使用脚本关闭 Excel 时,它仍保留在任务管理器中。

以下是我开始使用 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
Run Code Online (Sandbox Code Playgroud)

这是我关闭它的方法:

        [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)
Run Code Online (Sandbox Code Playgroud)

closeOnX 只是一个标志,因此它仅在某些情况下实际关闭 Excel 应用程序。其余部分在每次脚本结束时执行。

当我结束脚本并同时关闭 Excel 时,我只想关闭当前的 Excel 进程(这就是我不想停止进程的原因),而不关闭用户可能正在处理的其他工作簿。

当我结束脚本、保存并打开 Excel 时,我希望当用户手动关闭 Excel 时所有进程都消失。(这是工作)

mkl*_*nt0 13

太长了;博士

使用以下惯用法确保释放对 COM 对象的所有引用,这与调用结合$excel.Quit()使用可确保 Excel 进程(最终)终止:

& {  # Create a temporary child scope.

  $excel = New-Object -ComObject excel.application # create excel object

  # ... work with the $excel object and its object model,
  #     using whatever local variables needed.

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

} # All variables created inside { ... } go out of scope 
  # when this block is exited, ensuring release of all COM objects.
Run Code Online (Sandbox Code Playgroud)

有关如何释放 (Excel) COM 对象的一般指南,请参阅底部部分


背景资料:

$excel.Quit() 足以最终终止Excel 进程,但何时发生取决于垃圾收集器下次运行的时间。

您尝试显式释放 Excel[System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel)是不够的,因为变量$script:workbook$script:ws1仍然具有对 Excel COM 对象的引用,这些对象在变量超出范围并且这些引用最终被垃圾收集之前不会被释放。

因此,为了加快释放速度,您还必须在运行垃圾收集器之前显式释放这些引用:

$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 user 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.
Run Code Online (Sandbox Code Playgroud)

首选替代方案

  • 由于手动清除/删除所有相关变量容易出错且麻烦,因此您可以通过在临时子作用域中创建本地引用 COM 对象的所有变量来自动化该过程使用& { ... }
# PREFERABLE ALTERNATIVE to the code above:
& {  # 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()
Run Code Online (Sandbox Code Playgroud)

释放 (Excel) COM 对象:

  • ALWAYS 调用.Quit()- 如果没有它,在幕后创建的 Excel 进程永远不会终止,即使 PowerShell 会话结束时也不会终止(当然,当操作系统用户会话整体结束时它也会终止)。

  • $excel.Quit()通常这就是需要的(除非使用全局变量来存储对 Excel 对象的引用),因为当引用 COM 对象的脚本/函数变量超出范围时,底层 COM 对象最终也会自动释放。

    • 然而,Excel 进程实际终止可能需要一段不同的、不可预测的时间,具体取决于何时对超出范围的变量进行垃圾收集
  • 如果您希望 COM 对象尽快被释放

    • 您必须释放对存储在各个变量中的所有COM 对象的引用

      • 请注意,无需致电[System.Runtime.InteropServices.Marshal]::ReleaseComObject();因为有一个更简单、更强大的替代方案
      • 要么:清除所有显式引用 COM 对象的变量(参见上面的第一个代码片段):
        • 要么:将它们全部设置为$null.
        • 或者:将他们的名字传递给Remove-Variable
      • 或者,最好是:隐式释放引用(参见上面的第二个代码片段):
        • 通过块使用在子作用域中& { ... }引用 COM 对象的变量,这意味着在离开子作用域时将隐式释放引用。
    • 这些方法不仅比调用更简单、更简洁[System.Runtime.InteropServices.Marshal]::ReleaseComObject(),而且还可以防止以后尝试访问已发布的 COM 对象。

    • 然后,调用[GC]::Collect()强制即时垃圾收集 - 但请注意,当垃圾收集器运行时(尽管通常只是短暂的),您的代码会被阻塞。

  • 如果您还想确保在继续之前已完成释放 COM 对象:

    • 注意:可能很少需要这样做,因为 Excel 通常会在调用其方法时释放资源.Quit(),例如关闭已打开的文件。

    • 您可以在调用后调用[GC]::WaitForPendingFinalizers()[GC]::Collect(),但它可能不起作用:管理对正在最终确定的 COM 对象本身的访问的 RCW(运行时可调用包装器)并不保证当时COM 资源的释放;来自文档(添加了重点):

      • “当 COM 对象上的引用计数变为 0 时,COM 对象通常会被释放,尽管这取决于 COM 对象的实现并且超出了运行时的控制范围。”

      • 事实上,在当前的情况下,Excel 进程不会调用返回之前终止[GC]::WaitForPendingFinalizers();这只会在一秒钟左右之后发生