How to cleanup $IE object, close current processes [duplicate] - powershell

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.

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()

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

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...

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 .

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.

This may be useful to you:
Get-Process | Where-Object {$_.Name -Match "iexplore"} | Stop-Process

This command closes all the Internet Explorer windows that are currently open/running:
Get-Process iexplore | Stop-Process

Related

In PowerShell, pass a complex object (SmoServer) to a background job in the ArgumentList - stuck in "NotStarted"

I am trying to pass a pre-built SmoServer object to a background job, to parallelize some operations against multiple SQL Servers. However, when I try to do this, the Child job of the invoked job gets stuck in a "NotStarted" state. A very basic test:
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO")
$SmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server MySqlServer
Start-Job -Name Test -ScriptBlock {
param($SmoServer)
$SmoServer.Databases.Name
} -InitializationScript {
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO"); Import-Module SQLPS -DisableNameChecking
} -ArgumentList $SmoServer
The job starts, but the ChildJob gets stuck "NotStarted"
PS C:\Users\omrsafetyo> Get-Job Test
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
5 Test BackgroundJob Running True localhost param($SmoServer) $Smo...
PS C:\Users\omrsafetyo> Get-Job Test | select -expand childjobs
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
6 Job6 NotStarted True localhost param($SmoServer) $Smo...
I had encountered this a while ago, and never found a solution. And then I came across -IntializationScript, and thought that might be the silver bullet. It doesn't seem it is.
This same behavior is true with Invoke-Command. If I just run Invoke-Command, the command works fine. However, if I run Invoke-Command -AsJob, and pass an SmoServer object, it still fails.
How do I pass these complex objects that need an assembly/module loaded up front in the ArgumentList to a background job?
PowerShell jobs are run in a separate process, and objects passed as arguments are serialized (via something like Export-CliXml or the moral equivalent). When your object is "rehydrated" in the background process, it will not be an instance of Microsoft.SqlServer.Management.Smo.Server anymore, but rather just an object that looks like one (it will have the same properties, as they were serialized in the originating process).
You can pass .NET objects to different runspaces within the same process. An easy way to do this would be to use the PSThreadJob module.
An experiment to demonstrate what happens to objects passed to background jobs:
$srcFi = [System.IO.FileInfo]::new( 'C:\windows\system32\ntdll.dll' )
$srcFi.GetType().FullName
start-job -ScriptBlock { param( $fi ) $fi.GetType().FullName } -Arg #( $srcFi ) | Receive-Job -Wait
Output:
System.IO.FileInfo
System.Management.Automation.PSObject

Trying to kill a process by NAME and CPU Time

What I'm trying is to close a windows of explorer.exe without killing the whole process, because that will restart the whole taskbar. So far I can only identify it by CPU Time which separated from the windows explorer.exe because it start after.
Is there a PowerShell script or command out there that could help me achieve this?
Take a closer look at the Shell.Application COM Object.
The method 'Windows()' returns an object foreach open explorer-window.
You can call the the method 'Quit()' of these Objects to close the windows, here is a example:
$explorerPathYouWantToKill = "C:\"
$shellApp = New-Object -COM 'Shell.Application'
($shellApp.Windows() | Where-Object {$_.LocationURL -eq "file:///$($explorerPathYouWantToKill.Replace('\','/'))"}) | Foreach {
$_.Quit()
}

Not Seeing Expected Output When Capturing Results of Command to Variable and Writing to Console in Powershell 2.0

I'm trying to understand what's going on here in Powershell (v2.0, if important). I'm capturing the results of a command to a variable and when I write it to the console, I'm not getting the results I expect. Everything but the output is functioning as expected.
This is an MCVE that acts in the same way as a script that I wrote. I've just broken it down so that I can provide commentary on what's happening, where it's not working the way I think it should work, and what I think may be happening.
In this first snippet, I'm validating the status of the service MyService
on computer svr0123. This gives the output that I'm expecting.
PS C:\Temp> Get-Service -Name MyService -CN svr0123
Status Name DisplayName
------ ---- -----------
Stopped MyService My_Service_Display_Name
PS C:\Temp>
In this second snippet, I'm doing the same, only assigning the output to
$results with the intention of restarting any stopped services. Again, this
gives the output I'm expecting.
PS C:\Temp> $results = Get-Service -Name MyService -CN svr0123
PS C:\Temp> Write-Output $results
Status Name DisplayName
------ ---- -----------
Stopped MyService My_Service_Display_Name
PS C:\Temp>
Finally, I'm restarting the service, then writing the contents of $results
to the console. This does not function as expected. I would anticipate
that the contents of $results would be the same as the previous two outputs,
but instead I get:
PS C:\Temp> $results | Where { $_.Status -eq "Stopped" } | Set-Service -Status Running
PS C:\Temp> Write-Output $results
Status Name DisplayName
------ ---- -----------
Running MyService My_Service_Display_Name
PS C:\Temp>
This is incorrect unless each time I reference the contents of
$results it is calling the Get-Service command again, which is counterintuitive. If that's the case, it
appears that I'm not storing the output of the command, but rather I'm
storing an alias to the command. If I write the contents of $results to the console before doing the restart, everything outputs as expected.
This is a trivial fix, but I'm trying to understand the "Why" behind what I'm observing. My questions are:
Is this, in fact, what is occurring? Where in the Powershell documentation
can I learn more about this?
If this is what is occurring, is there a way that I can just store the
output so that I'm not incurring multiple calls? It's trivial in this
case, but my script will be used on a busy network and may at times have
to query hundreds of servers in a given run.
When you call Get-Service -Name MyService -CN svr0123 it is returning a ServiceController object, not just the text of the output.
So, when you call this line:
$results = Get-Service -Name MyService -CN svr0123
$results is not just a string variable containing the output of the command. It is actually a variable of type ServiceContoller.
Run this to see:
$results.GetType()
You will get this:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False ServiceController System.ComponentModel.Component
So, the second time you execute Write-Output $results, the service has been started and the $results variable is displaying the current status of the service.
I think it does not re-call Get-Service when you display the service object. I think the $service object has an internal state which is a text value, and when you pipe it to Set-Service that state value gets changed as well as the service being started/stopped.
Since trying to change it directly with $service.Status = "Running" generates an error because the property is read-only, this change could be happening through the service object's own $service.Start() and $service.Stop() methods found from $service | get-member
Supporting evidence from some quick tests:
I $service = get-service testsvc; $service and see the state is Running, then I go to Control Panel and stop the service there, and the $service state does not change.
I call $service.Start() directly to restart the service, and I get an exception (possibly because my PowerShell is not running as an Admin) so the service does not actually start running, however $service.Status does change (incorrectly) to say the service is running.
This way I get a disconnect between the reported status and the actual status is convincing me, and the way it seems implausible/impractical for every object to "know" how it was generated and arbitrarily re-run that code (what if it was a 30 minute query to generate it?) but I don't know for sure what the interactions are.

PowerShell: Getting Help on Get-Process -Property CPU

With PowerShell 3, I tried to get help on what properties are available for CPU; while using Get-Process. I just tried a shot in the dark, as below:
Help Get-Process -Property CPU
But, failed. Any help, please!
What are you looking for? Information about your processor? Get-Process list running processes(e.g. internet explorer) on your computer, not info about your processor-chips(CPU). Ex:
Get-Process
Output:
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
284 25 7128 8748 103 1608 AppleMobileDeviceService
75 7 1136 1528 44 1588 armsvc
703 82 6612 7732 114 1,25 4212 AsusAudioCenter
Information about your processor can be found using:
Get-WmiObject Win32_Processor
Output:
Caption : Intel64 Family 6 Model 42 Stepping 7
DeviceID : CPU0
Manufacturer : GenuineIntel
MaxClockSpeed : 3400
Name : Intel(R) Core(TM) i7-2600 CPU # 3.40GHz
SocketDesignation : LGA1155
To get all properties about your CPU use Get-WmiObject Win32_Processor | fl *. To get a list of avaiable properties, use the Get-Member cmdlet to examine the object that Get-WmiObjectreturns:
Get-WmiObject Win32_Processor | Get-Member
Your shot in the dark missed. Also, since your description of what went wrong is nothing more than "But, failed.", I can only guess at what your problem might be. In order to better help you use help you need to help us by providing pertinent information about your problem such as error messages.
Firstly, Help (or the Get-Help cmdlet) does not have a -Property parameter. -Parameter might be what you looking for, however running Help Get-Process -Parameter CPU will reveal that the Get-Process cmdlet does not have a CPU parameter.
Secondly, Get-Process returns instances of the System.Diagnostics.Process class. The documentation or running Get-Process | Get-Member will show you what properties that class exposes. You can retrieve them by running something like...
Get-Process | Select-Object -Property (
'ProcessName',
'Id',
'ProcessorAffinity',
'UserProcessorTime',
'PrivilegedProcessorTime',
'TotalProcessorTime'
);
Finally, unlike previous versions PowerShell 3.0 does not install local help content. You need to run the Update-Help cmdlet to download and install help content. Alternatively, when running Get-Help you can pass the -Online parameter which will open the help content from MSDN in a web browser.

Can I use PowerShell 1.0 to list processes along with their PIDs and Command Lines?

EDIT by OP: My question presupposed that PowerShell was the best tool for this job. There is a simpler way of achieving my goal. A friend just told me about: iisapp.vbs. It displays exactly the info I need without requiring PowerShell.
I'm working with dozens of ASP.NET websites running locally and when I want to debug a particular website named, for example, foo.site.com I go through the following steps:
Run Process Explorer (from SysInternals) and find which w3wp.exe was started with foo.site.com on its command line.
Note the Process ID (PID) of that w3wp.exe process.
In Visual Studio attach to that process ID.
Is there a way to write a PowerShell script that will print the PID and Command Line Arguments of every w3wp.exe process running on my computer?
When I run get-process w3wp I get:
> get-process w3wp
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
688 28 44060 64576 226 2.75 196 w3wp
750 26 48328 68936 225 3.38 1640 w3wp
989 36 54596 83844 246 4.92 1660 w3wp
921 33 54344 80576 270 4.24 5624 w3wp
773 27 48808 72448 244 2.75 5992 w3wp
No Command Line information :(
Thanks!
EDIT: I am looking for the command line arguments that were passed to w3wp.
gwmi win32_process -filter "name='w3wp.exe'" | select name,processId,commandLine
It should do the trick. I find it weird that powershell doesn't provide command line information by default. Note : I've only tested it in powershell 2.0, but as it use wmi, it should work in 1.0.
EDIT : the final version used by Tim Stewart (to avoid display problem, see comment) :
gwmi win32_process -filter "name='powershell.exe'" | format-table -autosize name,processId,commandLine
My first instinct was to use get-process and look at the startinfo property:
get-process w3wp | select-object id, path, #{Name="Args";Expression = {$_.StartInfo.Arguments}}
Unfortunately, this doesn't work because $_.StartInfo.Argments is always null. WMI works, though.
get-wmiobject win32_process -filter "name='w3wp.exe'" | select-object processid, commandline
This should work:
get-process | format-table Id,Path