【问题标题】:How to properly close Internet Explorer when launched from PowerShell?从 PowerShell 启动时如何正确关闭 Internet Explorer?
【发布时间】:2016-08-28 20:32:01
【问题描述】:

最近有一组关于通过 PowerShell 使用 Internet Explorer 进行操作的问题。所有这些都包含通过$ie=new-object -comobject InternetExplorer.Application 从 PowerShell 作为对象启动 IE 的代码。问题是,由调用 $ie.quit() 组成的 关闭 IE 的正确方法不起作用 - 首先,如果该 IE 碰巧有多个打开的选项卡,则 IE 不会退出作为一个整体,只有与 COM 对象对应的选项卡被关闭,其次,如果它只有一个选项卡,则窗口将关闭但进程仍然存在。

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 启动的进程的方法,并发现:How to get rid of a COMObject。该 COMObject 的示例是 Excel.Application,它的行为确实符合预期 - 调用 quit() 使窗口关闭,如果 $ex 是已创建的 COMObject,则执行 [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ex) 会停止 Excel 进程。但 Internet Explorer 并非如此。

我还发现了这个问题:How to get existing COM Object of a running IE 它提供了通过打开的窗口列表连接到 IE 的代码,并且可以在从其他地方启动的 IE 的范围内工作,但是如果 COM 对象是通过 PowerShell 创建的,则此脚本是如果这样修改,则无法完全停止 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)
  }
}

如果 IE 在 PowerShell 之外启动,进程会停止,但如果 IE 在 PowerShell 中启动,则保留两个进程,并且此代码报告没有找到 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 在启动时设置为“打开主页”,因此至少有一个标签始终处于打开状态。

【问题讨论】:

    标签: internet-explorer powershell com


    【解决方案1】:

    通常只需调用Quit() 方法就足以正常终止Internet Explorer 进程,无论它们是通过运行iexplore.exe 还是通过在PowerShell 中实例化COM 对象而创建的。

    演示:

    PS C:\> $env:PROCESSOR_ARCHITECTURE AMD64 PS C:\> (Get-WmiObject -Class Win32_OperatingSystem).Caption 微软视窗 8.1 企业版 PS C:\> 获取进程 | ? { $_.ProcessName -eq 'iexplore' } PS C:\> $ie = New-Object -COM 'InternetExplorer.Application' PS C:\> 获取进程 | ? { $_.ProcessName -eq 'iexplore' } 处理 NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 352 20 4244 14164 176 0.05 3460 探索 407 32 6428 23316 182 0.23 5356 PS C:\> $ie.Quit() PS C:\> 获取进程 | ? { $_.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()
    

    【讨论】:

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

    COM 对象也有类似的问题,使用 quit() 方法不会终止。 Interopservices.marshall 很多时候也不起作用。 我的解决方法:在调用 com 对象之前和之后,我做了一个 get-process 来获取所有 procs 的列表:这样我就有了实例的 PID。在我的脚本运行后,它会使用停止进程终止进程。

    这不是最好的方法,但至少它有效

    【讨论】:

    • 我想知道什么样的系统会有这样的行为。可能在某些版本的 Windows 或其 COM 模块中存在可以从 PC 规范派生的固有错误。
    【解决方案3】:

    快速浏览一下 ComObject for IE,似乎在创建它时,它为您提供了一个与 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。您的操作系统、PS 和 IE 版本是什么?
    • 我注意到您必须等待几秒钟左右才能再次检查进程
    • 我们的设置唯一不同的是操作系统版本。我在8.1。也许在 7 中坏了?
    • 有可能,当我有 Win8.0、Win7 x32、Win8.1 或 WinXP 时我会回来(使用 Powershell 1.0,很可能,COM 不依赖于 PS 版本)计算机来测试这种行为。
    • Win8.1、WinXP 和 Server 2012(又名 Win8.0 服务器)对 quit() 的响应很好。所以它可能在 Win7+IE11(它不是为 Win7 设计的)或普通 Win7 中出现问题。
    【解决方案4】:

    我尝试使用 Powershell 通过 COM 启动 Excel:

    $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() 完成,该进程就会从 taskmgr 中消失。这对我来说很有意义,因为(在我的理解中)COM 客户端负责释放对 COM 服务器的引用。在这种情况下,.NET(通过 Runtime Callable Wrapper)是 COM 客户端。

    IE 的情况可能更复杂,因为可能有其他选项卡与给定的 IE 进程相关联(并且有 @Noseratio 提到的框架合并),但至少这将摆脱 PS 创建的引用脚本。

    【讨论】:

      【解决方案5】:

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

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

      • iexplore.exe -noframemerging -private -embedding

      在此 IE 实例可用作 COM 服务器之前可能存在竞争条件,因此您可能希望在创建对象之前延迟一些时间。

      【讨论】:

        【解决方案6】:

        这可能对你有用:

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

        【讨论】:

        • 一个简单的代码块并不能成为一个很好的答案。也许在此基础上进行一些扩展,以提供一些背景、推理、警告等。
        • 我要求杀死一个特定的 IE 实例,因为可能有用户运行的 IE。所以,没有。
        【解决方案7】:

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

        Get-Process iexplore | Stop-Process
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-09-21
          • 2013-02-13
          • 1970-01-01
          • 1970-01-01
          • 2010-09-21
          • 1970-01-01
          • 2015-08-17
          相关资源
          最近更新 更多