通过 Powershell 关闭时无法让所有 excel 个进程停止

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

使用此代码,我将打开 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 时所有进程都消失。 (这是有效的)

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

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

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

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

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

因为手动清除/删除所有相关变量容易出错且麻烦,您可以通过在本地创建引用 COM 对象的所有变量来自动化该过程临时子作用域,使用& { ... }:

& {  # 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 会话结束时(当然,它在 OS 用户会话作为一个整体结束时终止)。

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

    • 但是,Excel 进程可能需要一段时间才能真正终止,这取决于对象消失的时间-of-scope 变量是 garbage-collected.
  • 如果希望COM对象尽快释放:

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

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

    • 之后,调用 [GC]::Collect() 以强制进行即时垃圾收集 - 但请注意,您的代码已被 阻止 而垃圾收集器 运行s(尽管通常只是短暂的).

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

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

    • 您可以在调用 [GC]::Collect() 之后 调用 [GC]::WaitForPendingFinalizers(),但是 可能不起作用:管理对 COM 对象自身 的访问的 RCW(运行时间可调用包装器)正在最终确定 保证释放COM资源当时;来自 the docs(强调已添加):

      • ”当COM对象的引用计数变为0时,COM对象通常被释放,虽然这取决于COM对象的实现并且超出 运行time.

        的控制
      • 确实,在手头的情况下,Excel 进程不会 [GC]::WaitForPendingFinalizers() 呼叫 returns;这只发生在一秒钟左右 之后 .

另一种非常 hacky 的方法是显示 excel window,然后使用 hwnd 识别并关闭进程:)

# Create the Excel object
$Excel = New-Object -Com Excel.Application

### Do your stuff ###

# Show the spreadsheet so that a HWND value exists in the COM Object
$Excel.Visible = $true

# Save spreadsheet
$Workbook.Save()

# Close spreadsheet using generated HWND
Get-Process -Name "*excel*" | Where-Object {$_.MainWindowHandle -eq $Excel.hwnd} | Stop-Process