11

There was a set of recently asked questions about doing something with Internet Explorer via PowerShell. All of them contain codes to launch IE from PowerShell as an object, via $ie=new-object -comobject InternetExplorer.Application. The problem is, the proper way of closing IE that consists of calling $ie.quit() does not work - first, if that IE would have happened to have more than a single open tab, IE doesn't quit as a whole and only the tab that corresponds to the COM object is closed, and second, should it have only one tab, the window gets closed but the processes remain.

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

I have tried to research the methods on how to close a process started via New-Object -ComObject, and have found this: How to get rid of a COMObject. The example of that COMObject is Excel.Application, which indeed behaves as intended - calling quit() makes window close, and executing [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ex) if $ex is a created COMObject stops the Excel process. But this is not the case with Internet Explorer.

I have also found this question: How to get existing COM Object of a running IE which provides code to connect to IE via list of open windows, and works to an extent of IE launched from elsewhere, but if the COM object is created via PowerShell, this script is not able to completely stop IE's processes, if modified as such:

$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)
  }
}

In case of IE launched outside of PowerShell, the processes are stopped, but in case of IE launched within PowerShell, two processes remain, and this code reports to have found no IE windows to reach COM objects, therefore IE processes are (yet) unable to be stopped.

So, how to reach the apparently orphaned windowless IE processes and gracefully stop them? I am aware of Get-Process iexplore | Stop-Process, but this will stop any and all IEs, not just those launched by the script, and if the script is run as administrator or SYSTEM on a, say, remote desktop server, everyone's IEs will be stopped.

Environment: OS Windows 7 x64, PowerShell 4 (installed above PS version 2), IE11 version 11.0.9600.17691 (automatically updated). IE set to "Open home page" upon starting, so at least one tab is always open.

Community
  • 1
  • 1
Vesper
  • 18,599
  • 6
  • 39
  • 61

7 Answers7

12

Simply calling the Quit() method should normally suffice for gracefully terminating Internet Explorer processes, regardless of whether they were created by running iexplore.exe or by instantiating a COM object in PowerShell.

Demonstration:

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:\> _

If you have orphaned Internet Explorer processes to which you don't have a handle you can cycle through them like this:

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

To be totally on the safe side you can release the COM object after calling Quit() and then wait for the garbage collector to clean up:

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

[GC]::Collect()
[GC]::WaitForPendingFinalizers()
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • 1
    By the way, I have just tried this, and I have noticed an interesting thing. When I call `[Runtime.Interopservices.Marshal]::ReleaseComObject()` against the created IE COMObject it returns a nonzero value - this apparently indicates that there are more links to that COM object in the system. Also in my case three processes have been started, not two. This might differ by IE version and OS version, so I'll check again on other PCs. So far it looks like a bug introduced in IE11 or a bug in .NET 4.5, if this issue would also manifest on a PC with Win8.1 and IE11 (which isn't accessible now). – Vesper Jun 04 '15 at 12:11
  • Hmm, this might be because the OS is x64, the two remaining processes are one x64 and one x32, and killing only an x32 one results in graceful stop of an x64 one. So, this might really be a (yet another) bug in IE explicitly :) – Vesper Jun 04 '15 at 12:14
  • @Vesper I'd doubt it. The demonstration in my answer is from a 64-bit Windows 8.1 as well (with Internet Explorer 11). – Ansgar Wiechers Jun 04 '15 at 13:10
  • Anyway I'm going to try all these tricks on some other PCs first. There might be something with access rights for example, although running the same script in a PS run as administrator didn't change a thing for me. If I'm using `Start-Process` as a means of starting IE, simple `quit()` works once I get ahold of the COM object created with the process. But if I do the things I described in my question, it doesn't quit. – Vesper Jun 04 '15 at 13:19
  • Apparently this is abnormal for my PC to not shut down IE if I call `quit()` on its object. Every other PC I've tested so far (three, one Server 2012, one XP with IE8 and one 8.1) had IE stop its processes as soon as I tell it to quit. So I expect that this is really the proper way, and what I'm experiencing here is a bug of sort, I'll try a bit more to find what causes the bug. – Vesper Jun 05 '15 at 06:46
2

I've had similar problems with COM objects that wouldn't terminate using the quit() method. Interopservices.marshall also doesn't work a lot of times. My workaround : I do a get-process to get a list of all procs before I call the com object and right after : this way I have the PID of my instance. After my script runs it kills the process using stop-process.

Not the best way to do this but at least it works

bluuf
  • 936
  • 1
  • 6
  • 14
  • I wonder what kinds of systems behave like that. Perhaps there is an inherent bug in either some version of Windows or their COM modules that can be derived from PC specs. – Vesper Jun 05 '15 at 06:47
1

With a quick look around in the ComObject for IE, it seems that when it is created, it gives you a direct interface to the methods that make interacting with IE easier, for example Navigate() or ReadyState.

I did discover a property that seems to be what you are looking for and that would be Parent

Calling $IE.Parent.Quit() seemed to get rid of the PowerShell created instances.

$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...
SomeShinyObject
  • 7,581
  • 6
  • 39
  • 59
  • I've just tried, no dice, two processes remain. Also, `$ie.parent -eq $ie` returns true, so it shouldn't be `parent`. What's your OS, PS and IE version? – Vesper Jun 04 '15 at 11:58
  • I noticed that you had to give it a few seconds or so before you checked processes again – SomeShinyObject Jun 04 '15 at 11:59
  • Only thing that is different between our setup is OS Version. I'm at 8.1. Maybe broken in 7? – SomeShinyObject Jun 04 '15 at 12:04
  • It's possible, I'll return when I will have an either Win8.0, Win7 x32, Win8.1 or WinXP (with Powershell 1.0, most likely, COM doesn't depend on PS version) computer to test this behavior. – Vesper Jun 04 '15 at 12:15
  • Win8.1, WinXP and Server 2012 (aka Win8.0 server) responded nicely to just `quit()`. So it's probably something wrong in either Win7+IE11 (it wasn't designed for Win7) or plain Win7. – Vesper Jun 05 '15 at 06:48
1

I tried an experiment with Powershell launching Excel via 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

As soon as [GC]::collect() finished the process disappeared from taskmgr. This makes sense to me, because (in my understanding) a COM client is responsible for releasing references to the COM server. In this case .NET (via a Runtime Callable Wrapper) is the COM client.

The situation may be more complicated with IE, since there may be other tabs associated with a given IE process (and there's the frame merging that @Noseratio mentions), but at least this will get rid of the reference created by the PS script .

Χpẘ
  • 3,403
  • 1
  • 13
  • 22
0

There's HKCU\Software\Microsoft\Internet Explorer\Main\FrameMerging registry key that prevents merging IE "frame" processes, explained here. I haven't tried it myself, but I think it might solve your problem if you set it before you instantiate the InternetExplorer.Application COM object.

If that doesn't help, try launching a new IE instance with the following command line, prior to creating the COM object (I haven't tried that, either):

  • iexplore.exe -noframemerging -private -embedding

There is a possible race condition before this IE instance becomes available as a COM server, so you may want to put some delay before you create an object.

noseratio
  • 59,932
  • 34
  • 208
  • 486
0

This may be useful to you:

Get-Process | Where-Object {$_.Name -Match "iexplore"} | Stop-Process
zx485
  • 28,498
  • 28
  • 50
  • 59
Andrei C
  • 11
  • 1
  • A bare blob of code doesn't make a great answer. Maybe expand on this a little to give a little background, reasoning, caveats, etc. –  Aug 01 '18 at 19:43
  • 1
    I asked to kill a particular IE instance, as there may be IEs ran by users. So, no. – Vesper Aug 02 '18 at 03:42
0

This command closes all the Internet Explorer windows that are currently open/running:

Get-Process iexplore | Stop-Process
Toni
  • 1,555
  • 4
  • 15
  • 23
Prash
  • 1