【问题标题】:Can't get all excel processes to stop when closing through Powershell通过Powershell关闭时无法停止所有excel进程
【发布时间】:2020-03-12 01:18:59
【问题描述】:

使用此代码,我正在打开 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 powershell user-interface com rcw


    【解决方案1】:

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

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

    您尝试使用 [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) 显式释放 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 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
    # at 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 会话结束也不会终止(当然,它会在操作系统用户终止时终止)整个会话结束)。

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

      • 但是,Excel 进程可能需要一个变化的、不可预测的时间才能真正终止,具体取决于超出范围的变量何时是对象垃圾收集
    • 如果您希望尽快释放 COM 对象

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

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

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

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

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

      • 您可以在调用[GC]::Collect() 之后调用[GC]::WaitForPendingFinalizers(),但它可能不起作用:管理对COM 对象它们自己被最终确定并不能保证在那个时候释放 COM 资源;来自the docs(强调):

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

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

    【讨论】:

    • 很棒的东西,但是您知道如何在受限语言模式下退出应用程序吗?不允许使用 $excel.quit()。我发现的唯一方法是终止进程,如果打开任何其他 excel 进程,这可能会很糟糕。
    • @C.Graham:有趣的问题;我想不出一个解决方案,但我鼓励你创建一个新的问题帖子,专注于这个问题,这也将为正确的答案提供空间。
    • 我想出了一个,但它涉及在调用新的 comobject 之前创建一个 PID 数组,并在保存 excel 后关闭任何未在数组中打开的。在这里发布问答stackoverflow.com/questions/69049051/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-10
    • 2015-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多