如何在Powershell约束模式下关闭excel ComObject



我在网上找不到任何关于如何在约束模式下关闭ComObject的信息。正常的过程是使用$com.Quit(),但是在约束模式下是不允许的。此外,[System.Runtime.InteropServices.Marshal]::ReleaseComObject()在约束模式下是不允许的。

有人建议我在以下线程中由mklement0提出一个新问题:不能通过Powershell关闭时停止所有excel进程

让我提供一个更符合powershell习惯的解决方案,它也应该执行得更好:

# Capture the PIDs (process IDs) of all *preexisting* Excel processes
# In PowerShell 7+, you can simplify to (see bottom section for a discussion):
#    $exelPidsBefore = (Get-Process -ErrorAction Ignore Excel).Id ?? @()
$excelPidsBefore = @(
Get-Process -ErrorAction Ignore Excel | Select-Object -ExpandProperty Id
)
# Perform the desired programmatic Excel operations:
# Create an Excel COM Automation object, which
# invariably creates a *new* Excel process.
$excel = New-Object -ComObject Excel.Application
# Determine the PID of the just-launched new Excel process.
# Note: This assumes that no *other* processes on your system have 
#       simultaneously launched Excel processes (which seems unlikely).
$excelComPid = 
Compare-Object -PassThru $excelPidsBefore (Get-Process -ErrorAction Ignore Excel).Id
# Work with the Excel Automation object.
# ...
# Clean up by terminating the COM-created Excel process.
# NOTE: 
#  * As stated in your question, you would normally use $excel.Quit()
#    but given that your running in *constrained language mode*, you
#    are not permitted to invoke *methods*.
Stop-Process -Id $excelComPid

作为一个完全可选的旁白:

  • 通常情况下Get-Process -ErrorAction Ignore Excel | Select-Object -ExpandProperty Id可以简化为(Get-Process -ErrorAction Ignore Excel).Id(正如后面所做的那样),这要归功于成员访问枚举——成员枚举表达式不仅更简洁,而且更高效。

  • 在这种情况下不能正常工作的原因是,如果GetProcess没有输出,则返回$null,@()然后将其包装成一个数组,导致一个单元素数组,其唯一的元素是$null,并且将这样的数组传递给Compare-Object失败

  • 成员访问枚举在无输入和单输入对象情况下的行为是不幸的,但这是无法修复的,以免破坏向后兼容性。

    • 参见GitHub issue #6802进行讨论。
  • PowerShell (Core) 7+中,可以使用??(null-coalescing operator)以如下方式解决问题:

    $exelPidsBefore = (Get-Process -ErrorAction Ignore Excel).Id ?? @()
    

我想到了一种方法来做到这一点,因为我在另一个线程中提出了这个问题。它不像只调用. quit()那样整洁,但它可以工作。在调用新的ComObject之前,我获得应用程序(本例中为excel)的任何打开进程的列表并将其存储在一个数组中。然后,在保存文件后,我关闭所有不在初始数组中的打开的pid。

的例子:

$StartOpenExcel = get-process excel | ForEach-Object {$_.id} #make an array with the pids;
​$excel=New-Object -ComObject excel.application;
{...do something here }
$workbook.SaveAs($fileName,51);#save the excel doc
Get-Process excel | ForEach-Object{
if($StartOpenExcel -contains $_.id -eq $false){
kill -Id $_.Id;
}
}
II $fileName; #open the excel

最新更新