如何在PowerShell中启动Internet Explorer并正确关闭?

11

最近有一组关于通过PowerShell操作Internet Explorer的问题。它们都包含了启动IE对象的代码,如$ie=new-object -comobject InternetExplorer.Application。问题是,使用正确的方法关闭 IE,也就是调用 $ie.quit() 并没有起作用。首先,如果该IE窗口有不止一个打开的标签页,那么整个IE不会退出,只有对应COM对象的标签页被关闭。其次,如果该IE窗口只有一个标签页,那么窗口会关闭,但是进程仍然在运行。

PS > get-process iexplore

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    352      31     7724      24968   142     0,58   3236 iexplore
    228      24    22800      15384   156     0,19   3432 iexplore

我尝试研究如何关闭通过New-Object -ComObject启动的进程的方法,并找到了这个链接:如何摆脱COM对象。该COM对象的示例为Excel.Application,它确实表现出预期的行为——调用quit()会关闭窗口,如果$ex是已创建的COM对象,则执行[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ex)可以停止Excel进程。但这不适用于Internet Explorer。

我还发现了这个问题:如何获取正在运行的IE的现有COM对象,其中提供了通过打开窗口列表连接到IE的代码,并且在从其他地方启动IE的情况下可以正常工作,但如果通过PowerShell创建COM对象,则无法完全停止IE的进程,如果修改为这样:

$shellapp = New-Object -ComObject "Shell.Application"
$ShellWindows = $shellapp.Windows()
for ($i = 0; $i -lt $ShellWindows.Count; $i++)
{
 if ($ShellWindows.Item($i).FullName -like "*iexplore.exe")
  {
  $ie = $ShellWindows.Item($i)
  $ie.quit()
  [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ie)
  }
}

如果在PowerShell之外启动IE,则进程会停止,但是如果在PowerShell中启动IE,则会保留两个进程,此代码报告找不到任何IE窗口以访问COM对象,因此IE进程无法被(尚未)停止。

那么,如何访问明显的无主窗口IE进程并优雅地停止它们?我知道Get-Process iexplore | Stop-Process,但这将停止所有IE,而不仅仅是由脚本启动的IE,如果脚本作为管理员或SYSTEM在远程桌面服务器上运行,则所有人的IE都将停止。

环境: 操作系统Windows 7 x64,PowerShell 4(安装在PS版本2以上),IE11版本11.0.9600.17691(自动更新)。 IE设置为“打开主页”以启动,因此至少始终打开一个选项卡。

7个回答

12

通常只需简单调用Quit()方法即可优雅地终止Internet Explorer进程,无论这些进程是通过运行iexplore.exe还是在PowerShell中实例化COM对象创建的。

演示:

PS C:\> $env:PROCESSOR_ARCHITECTURE
AMD64
PS C:\> (Get-WmiObject -Class Win32_OperatingSystem).Caption
Microsoft Windows 8.1 Enterprise
PS C:\> Get-Process | ? { $_.ProcessName -eq 'iexplore' }
PS C:\> $ie = New-Object -COM 'InternetExplorer.Application'
PS C:\> Get-Process | ? { $_.ProcessName -eq 'iexplore' }

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    352      20     4244      14164   176     0.05   3460 iexplore
    407      32     6428      23316   182     0.23   5356 iexplore

PS C:\> $ie.Quit()
PS C:\> Get-Process | ? { $_.ProcessName -eq 'iexplore' }
PS C:\> _

如果您有一些孤立的Internet Explorer进程并且没有其句柄,可以像下面这样循环遍历它们:

(New-Object -COM 'Shell.Application').Windows() | Where-Object {
    $_.Name -like '*Internet Explorer*'
} | ForEach-Object {
    $_.Quit()
}

为了完全保险起见,您可以在调用 Quit() 后释放 COM 对象,然后等待垃圾回收器清理:

(New-Object -COM 'Shell.Application').Windows() | Where-Object {
    $_.Name -like '*Internet Explorer*'
} | ForEach-Object {
    $_.Quit()
    [Runtime.Interopservices.Marshal]::ReleaseComObject($_)
}

[GC]::Collect()
[GC]::WaitForPendingFinalizers()

1
顺便说一下,我刚试了一下,发现了一个有趣的事情。当我对创建的IE COM对象调用[Runtime.Interopservices.Marshal] :: ReleaseComObject()时,它返回一个非零值 - 这显然表明系统中还有更多链接到该COM对象的链接。在我的情况下,启动了三个进程,而不是两个。这可能因IE版本和操作系统版本而异,因此我将在其他PC上再次检查。到目前为止,看起来像是IE11引入的错误或.NET 4.5中的错误,如果此问题也会在Win8.1和IE11(现在无法访问)的PC上显示。 - Vesper
嗯,这可能是因为操作系统是x64,剩下的两个进程一个是x64,另一个是x32,只杀死一个x32会导致x64优雅地停止。所以,这可能真的是IE中的(又一个)bug :) - Vesper
@Vesper 我不怀疑。我回答中的演示也来自于一台64位的Windows 8.1电脑(带有Internet Explorer 11)。 - Ansgar Wiechers
无论如何,我首先要在其他电脑上尝试所有这些技巧。例如,可能存在访问权限问题,尽管在以管理员身份运行的PS中运行相同的脚本对我来说没有任何改变。如果我使用Start-Process作为启动IE的手段,一旦我掌握了该进程创建的COM对象,简单的quit()就可以起作用。但是,如果我按照我在问题中描述的那样做,它就不会退出。 - Vesper
显然,如果我在IE对象上调用quit(),我的PC不能关闭IE是不正常的。到目前为止,我测试过的其他PC(三台,一台Server 2012,一台安装了IE8的XP和一台8.1)都会在我告诉它退出时停止IE的进程。因此,我认为这才是正确的方式,而我在这里遇到的是某种错误,我将尝试更多地查找导致该错误的原因。 - Vesper

2

我曾经遇到过类似的问题,使用quit()方法无法终止COM对象。Interopservices.marshall也经常无效。

我的解决方法:在调用COM对象之前,我会通过get-process 获取所有进程的列表,然后再获取一次,这样就可以获得实例的PID了。执行完我的脚本后,使用stop-process 来杀死该进程。

虽然不是最好的方法,但至少能够解决问题。


我想知道有哪些系统会表现出这样的行为。也许某个版本的Windows或它们的COM模块存在固有的漏洞,可以从PC规格中推导出来。 - Vesper

1
通过快速查看IE的ComObject,似乎当它被创建时,会为您提供直接接口以使与IE交互更加容易,例如Navigate()或ReadyState。我发现一个属性似乎是您正在寻找的,那就是Parent。调用$IE.Parent.Quit()似乎可以消除PowerShell创建的实例。
$IE = New-Object -ComObject InternetExplorer.Application
Get-Process | Where-Object {$_.Name -Match "iex"}

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    291      20     5464      14156   200     0.16   1320 iexplore
    390      30     5804      20628   163     0.14   5704 iexplore

$IE.Parent.Quit()
(Get-Process | Where-Object {$_.Name -Match "iex"}).GetType()
You cannot call a method on a null-valued expression...

我刚刚尝试了一下,不行,还剩下两个进程。另外,$ie.parent -eq $ie 返回 true,所以 parent 不应该是问题所在。请问你的操作系统、PowerShell 和 IE 版本是多少? - Vesper
我注意到你需要等几秒钟才能再次检查进程。 - SomeShinyObject
我们设置之间唯一不同的是操作系统版本。我使用的是8.1。也许在7上出了问题? - SomeShinyObject
可能的话,我会在我有Win8.0、Win7 x32、Win8.1或WinXP(很可能是带有Powershell 1.0的,COM不依赖于PS版本)的电脑来测试这种行为时返回。 - Vesper
Win8.1、WinXP和Server 2012(又称Win8.0服务器)对于quit()命令的响应良好。因此,可能是Win7+IE11存在问题(它并不是为Win7设计的),或者是纯Win7出现了问题。 - Vesper

1

我尝试通过 Powershell 启动 Excel 来进行实验,使用了 COM 接口:

$x = New-Object -com Excel.Application
$x.Visible = $True
Start-Sleep 5 # make it stay visible for a little while
$x.Quit()
$x = 0 # Remove .NET's reference to com object
[GC]::collect() # run garbage collection

一旦[GC] :: collect()完成,进程就会从任务管理器中消失。在我看来这是有道理的,因为(在我的理解中)COM客户端负责释放对COM服务器的引用。在这种情况下,.NET(通过运行时可调用包装器)是COM客户端。 与IE相关的情况可能更加复杂,因为可能存在与给定IE进程相关联的其他选项卡(以及@Noseratio提到的框架合并),但至少这将消除由PS脚本创建的引用。

0

这可能对你有用:

Get-Process | Where-Object {$_.Name -Match "iexplore"} | Stop-Process

一段单独的代码并不能成为一个出色的答案。或许可以稍微扩展一下,提供一些背景、推理、注意事项等等。 - user1531971
1
我要求关闭特定的IE实例,因为可能有用户运行了IE。所以,不行。 - Vesper

0

这个命令会关闭所有当前打开/运行的Internet Explorer窗口:

Get-Process iexplore | Stop-Process

0

有一个注册表键 HKCU\Software\Microsoft\Internet Explorer\Main\FrameMerging 可以防止合并 IE 的“框架”进程,在这里解释。我自己没有尝试过,但我认为如果在实例化 InternetExplorer.Application COM 对象之前设置它,它可能会解决你的问题。

如果这样做没有帮助,请尝试使用以下命令行启动新的 IE 实例,并在创建 COM 对象之前进行操作(我也没有尝试过):

  • iexplore.exe -noframemerging -private -embedding

在此 IE 实例可用作 COM 服务器之前,可能存在潜在的竞争条件,因此您可能需要在创建对象之前放置一些延迟。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接