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

2024-04-05

使用此代码,我打开 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

这是我关闭它的方法:

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

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

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


tl;dr

使用以下内容idiom确保释放对 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.

For 一般指导有关如何释放 (Excel) COM 对象,请参阅底部.

$excel.Quit() is足够最终终止Excel进程, but when发生这种情况取决于垃圾收集器下次运行的时间。

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

因此,为了speed up释放时,您也必须显式释放这些引用,before运行垃圾收集器:

$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.

Because manually清除/删除所有相关变量容易出错且麻烦, 你可以instead automate该过程中,通过在本地创建引用 COM 对象的所有变量临时子范围, using & { ... }:

# 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()

释放 (Excel) COM 对象:

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

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

    • 然而,Excel 进程可能需要一段不同的、不可预测的时间才能真正终止,取决于超出范围的变量所在的对象的时间垃圾收集.
  • 如果你想释放COM对象尽快:

    • 您必须发布对以下内容的引用all您存储的 COM 对象个体变量:

      • 请注意,有不需要[System.Runtime.InteropServices.Marshal]::ReleaseComObject()来电;因为有一个更简单且更稳健的替代方案:
      • Either: Clear all variables referencing COM objects explicitly, by (see first code snippet above):
        • 要么:将它们全部设置为$null.
        • 或者:将他们的名字传递给Remove-Variable
      • Or, preferably: Release the references implicitly (see second code snippet above):
        • 使用引用 COM 对象的变量子范围, via a & { ... } block,这意味着引用将在离开子作用域时隐式释放。
    • 这些方法不仅比调用更简单、更简洁[System.Runtime.InteropServices.Marshal]::ReleaseComObject(),而且还可以防止以后尝试访问已发布的 COM 对象。

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

  • If you 此外想要确保释放 COM 对象已完全的在继续之前:

    • 注意:有可能很少需要这个,因为 Excel 通常会在其.Quit()方法被调用,例如关闭它已打开的文件。

    • You can call [GC]::WaitForPendingFinalizers()打电话后[GC]::Collect(), 但它是可能不起作用:管理对 COM 对象的访问的 RCW(运行时可调用包装器)他们自己最终确定并不保证释放COM资源当时; from the docs https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.finalreleasecomobject?view=netframework-4.8(强调):

      • “当COM对象上的引用计数变为0时,COM对象usually被释放了,虽然这取决于 COM 对象的实现并且超出运行时的控制范围."

      • 事实上,在本例中,Excel 流程确实not终止before the [GC]::WaitForPendingFinalizers()来电返回;这只发生在一秒钟左右然后.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

通过 Powershell 关闭时无法停止所有 Excel 进程 的相关文章

随机推荐