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