Creating and managing processes in PowerShell
PowerShell's get-process cmdlet gets the processes that are running on the local computer, and is restricted to the local computer only, you can't interact with processes that runs on remote computers nor create ones locally or remotely.
You can use WMI (see below), but I want to show another way of doing it, less documented one - static methods.
Static methods give you the option to break loose of the cmdlet 'wrapper' and work directly on the underlying NET class. To find which static methods you can use on processes we'll use PowerShell Get-Process cmdlet, we'll pipe it to Get-Member to reveal the underlying NET type that get-process is using:
PS:55 >Get-Process | Get-Member TypeName: System.Diagnostics.Process Name MemberType Definition ---- ---------- ---------- Handles AliasProperty Handles = Handlecount Name AliasProperty Name = ProcessName
(...)
Managing processes with static methods
As you can see, below the surface, Get-Process is using the [System.Diagnostics.Process] NET type to retrieve a process object for each process. From now on, you can work with the static methods of this type to interact with process objects. To find the static methods (surround the type name with square brackets), type:
PS > [System.Diagnostics.Process] | Get-Member -Static -MemberType method
TypeName: System.Diagnostics.Process
Name MemberType Definition ---- ---------- ----------(...) EnterDebugMode Method static System.Void EnterDebugMode()... Equals Method static System.Boolean Equals(Object... GetCurrentProcess Method static System.Diagnostics.Process G... GetProcessById Method static System.Diagnostics.Process G... GetProcesses Method static System.Diagnostics.Process[]... GetProcessesByName Method static System.Diagnostics.Process[]... LeaveDebugMode Method static System.Void LeaveDebugMode()... ReferenceEquals Method static System.Boolean ReferenceEqua... Start Method static System.Diagnostics.Process S...
(...)
The Definition column shows all the available overloads for each method. The notation for calling a static method is:
PS > [System.Diagnostics.Process]::StaticMethodName(<args>)
Lets find the overloads for the GetProcesses method, (each overload is delimited with a comma):
PS > ([System.Diagnostics.Process] | gm -s GetProcesses).Definition
static System.Diagnostics.Process[] GetProcesses(),
static System.Diagnostics.Process[] GetProcesses(String machineName)
The output shows two ways to call the method. You can call it with no arguments and get all running processes on the local computer or use the later to get processes from a remote computer. We are interested in the second one since it accept a string argument for a computer name. To save typing space we'll assign the type name to a variable:
PS > $process = [System.Diagnostics.Process]
# examples
$server="server"
$process = [System.Diagnostics.Process]
# get all running processes on the remote server
PS > $process::GetProcesses($server)
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 165 7 15364 21684 56 2992 vmserverdWin32 196 6 2764 5132 54 3740 LVComSX 73 4 1100 3392 30 2452 ctfmon
(...)
As you can see the output is the same output produced by the get-process. To find which methods you can invoke on a process, pipe it to get-member:
PS > $process::GetProcessById(2992,$server) | gm -member method
TypeName: System.Diagnostics.Process
Name MemberType Definition
---- ---------- ----------
(...)
Close Method System.Void Close()
CloseMainWindow Method System.Boolean CloseMainWindow()
CreateObjRef Method System.Runtime.Remoting.ObjRef Cr
Dispose Method System.Void Dispose()
Kill Method System.Void Kill()
Refresh Method System.Void Refresh()
Start Method System.Boolean Start()
ToString Method System.String ToString()
WaitForExit Method System.Boolean WaitForExit(...
(...)
This are the same methods you'll get if you run get-process | gm -member method. To call a static method simply append it to the command:
PS > $process::GetProcessById(2992,$server).Kill()
In the same manner you can get a list all properties of a process.
PS > $process::GetProcessById(2992,$server) | gm -member property
# get remote process by its process id
PS > $process::GetProcessById(2992,$server)
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 165 7 15364 21684 56 2992 vmserverdWin32
# get remote process by its name
PS > $process::GetProcessesByName("vmserverdWin32",$server)
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 165 7 15364 21684 56 2992 vmserverdWin32
# get PowerShell's process, locally
PS > [System.Diagnostics.Process]::GetCurrentProcess()
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 512 9 39340 33148 163 4.06 11952 powershell
Creating local processes
To create processes, locally, you need to use the static Start() method. To find what overloads this method takes:
PS > $process = [System.Diagnostics.Process]
PS > (($process | gm -static start).definition).replace("static","`nstatic")
static System.Diagnostics.Process Start(String fileName, String userName, SecureString password, String domain),
static System.Diagnostics.Process Start(String fileName, String arguments, String userName, SecureString password, String domain),
static System.Diagnostics.Process Start(String fileName)static System.Diagnostics.Process Start(String fileName, String arguments),
static System.Diagnostics.Process Start(ProcessStartInfo startInfo)
There are five different ways to call it, each overload accepts different set of arguments:
1. Run a file (executable) with alternate credentials and a secure password.
2. Adds an option to add arguments for the file name.
3. Gets only file name.
4. File name and arguments, no alternate credentials.
5. Gets a ProcessStartInfo object.
# Use the third overload to run notepad
PS > [System.Diagnostics.Process]::Start("notepad")
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
0 1 136 84 1 0.00 13720 notepad
# Use the fourth to run notepad with arguments, in this case make notepad load the system.ini file.
PS > [System.Diagnostics.Process]::Start("notepad","$env:windir\system.ini") Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 5 1 340 840 8 0.03 23728 notepad
BTW, if you want to avoid the default output, assign the command to a variable or cast it to [void], as in:
PS >
The fifth option is the one I want to use since it is using another type to specify what starting info to assign to the new process, including alternate credentials (though in plain text and not secure as the first and second overloads which uses the SecureString parameter). Here's a partial property list, you can find the full list here:
- Arguments
- FileName
- Password
- UserName
- WorkingDirectory
Using the New-Object cmdlet
In this example I'll use the New-Object cmdlet to instantiate a new System.Diagnostics.ProcessStartInfo object to specify a set of values to be used when the process start:
# Specifies a set of values that are used when you start a process.
$si = new-object System.Diagnostics.ProcessStartInfo
$si.fileName = "notepad.exe"
$si.Arguments= "$env:windir\system.ini"
$si.WorkingDirectory = $pwd
# this will launch the process in the background (hidden)
# $si.windowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
# run process under alternate credentials
# $si.UserName="username"
# $si.Password="Password"
# start the process
$process = New-Object System.Diagnostics.Process
$process.startInfo=$si
# this is also valid
# $process = [System.Diagnostics.Process]::Start($si)
# close notepad and watch the output
if($process.start()) { $process.waitForExit(); "existed"}
Note: The WaitForExit() method is available only for processes that are running on the local computer. If you need to track a process on a remote system construct a loop to check if the remote process exists.
Want to know how to list all name/value pairs of the [System.Diagnostics.ProcessWindowStyle] type or any other [enum] type, see my post on Listing enumeration type values.
Creating remote processes
So far so good. But how about creating new processes on remote computers? Well, that's another story (Class). None of the above, Get-Process nor its underlying type, let you do that. The only way to do it is by using WMI.
You can access WMI with the Get-WmiObject cmdlet or with PowerShell's Type accelerators (shortcuts). Type accelerators were developed by the Windows PowerShell team to make WMI scripts more easy to write for system administrators:
[WMI] - type accelerator for ManagementObject.
[WMICLASS] - type accelerator for ManagementClass.
[WMISEARCHER] - type accelerator for a ManagementObjectSearcher.
See the second resource link below for more information on this.
Here's a one-liner to create new process(es) on local/remote computers:
PS > ([WMICLASS]"\\$computer\ROOT\CIMV2:win32_process").Create("notepad") __GENUS : 2 __CLASS : __PARAMETERS __SUPERCLASS : __DYNASTY : __PARAMETERS __RELPATH : __PROPERTY_COUNT : 2 __DERIVATION : {} __SERVER : __NAMESPACE : __PATH : ProcessId : 7380 ReturnValue : 0
If the ReturnValue property is set to 0 then you've successfully created the process on the remote computer, the ProcessId property shows the process id for the new process on the remote computer.
Note: For security reasons the WMI Win32_Process.Create method cannot be used to start an interactive process remotely (it runs hidden, in the background).
Summary
The above technique on static methods, opens a whole new world of discovering PowerShell and NET classes. You can apply it to any cmdlet and work your way up or down, depending on your point of view :-). Soon enough you'll find your way to MSDN, where you can get more information and code examples on each class.
This post only scratches the surface and there's a lot more to write on processes... but now comes your part. Check out what Get-Service has to offer and what can you get by applying all of this to it.
Last but not least, I'd like to thank Hal Rottenberg from TechProsaic and the PowerScripting Podcast for his help with editing and prompting me to write this article.
Happy New Year!
Additional Resources:
Managing Processes in PowerShell
Improved Support for WMI
Getting Services associated with a Process via PowerShell
Process Class
ProcessStartInfo Class
Create Method of the Win32_Process Class
Casts and Get-WmiObject
30 comments:
I found the remote script execution very useful, however, I was wondering how can I check to see if this process has stopped?
([WMICLASS]"\\$computer\ROOT\CIMV2:win32_process").Create("notepad")
In my PowerShell script I am going to use this to install various MSI's and EXE's on a server however, I need to be able to tell PowerShell to wait until the first process is finished before firing off the next install. Can you provide some assistance with doing this?
Thanks,
Flea#
Hi Flea#
Put this in a script and run to see it in action:
$computer="."
$process = ([WMICLASS]"\\$computer\ROOT\CIMV2:win32_process").Create("notepad")
$procId=$process.ProcessId
#$procId
$exist=$true
while ($exist){
trap{
$script:exist=$false
continue
}
[void][System.Diagnostics.Process]::GetProcessById($procId,$computer)
$processid
start-sleep -s 1
}
"process id: $procId has exited"
Hey firstly thanks for the info, found it very useful, ive written a script to check service stats and to run new services also, but one of the services for some reason you must end it's corresponding process, the process is on a remote server, i followed this example,
heres a snippet:
$server = "GH-345632"
$process = [System.Diagnostics.Process]
$process::GetProcessById(1564,$server).Kill()
it's throwing me an error i can't resolve : Exception calling "Kill" with "0" argument(s): "Feature is not supported for remote machines."
Please Help!!!! as my head is in bits trying to figure out whats wrong. it can get any process information from the desired server but cannot kill any process!!
Sean
I like this,
([WMICLASS]"\\$computer\ROOT\CIMV2:win32_process").Create("notepad")
Is there any way to execute this on DMZ servers with different credential?
Sean,
Sorry for the delay, I've just came back from a long vacation :)
As your error message states it is not possible to kill process remotely, only local ones.
The docs on MSDN stses the same:
Check the Exceptions (NotSupportedException) section on the Kill method page:
http://msdn2.microsoft.com/en-us/library/system.diagnostics.process.kill.aspx
"You are attempting to call Kill for a process that is running on a remote computer.
The method is available only for processes running on the local computer."
Fear not, you can kill/Terminate it via WMI:
([WMI]"\\$server\root\cimv2:Win32_Process.Handle='1564'").Terminate()
If the ReturnValue is equel to 0 then you're OK.
Amit,
I think it can be done with a few more lines of code. I can't test it against a DMZ server, you may have to twick it a bit.
Keep in mind that firewall rules must permit WMI remote management calls to the screened server(s).
$cmd="notepad.exe"
$server="serverName"
$user="domain\userName"
$pass="p@ssw0rd"
$process = [WMIClass]"\\$server\ROOT\cimv2:Win32_Process"
$process.psbase.Scope.Options.userName=$user
$process.psbase.Scope.Options.Password=$pass
#$process.psbase.Scope.Options.Impersonation = [System.Management.ImpersonationLevel]::Impersonate
$process.psbase.Scope.Options.Authentication = [System.Management.AuthenticationLevel]::PacketPrivacy
$process.Create($cmd)
# get bach the process id and returnValue
$process.ProcessId
$process.ReturnValue
So, can I check the Responding property of a remote process? I try this:
@process::GetProcessesByName("processname","servername") | select -Property -Responding
and I get "Feature is not supported for remote machines."
Thanks...hope this isn't a stoopid question....
Sadly it is not supported for remote machines. It would be possible to get the value In PowerShell v2 remoting, I'll try to find a solution.
By the way, the blog has moved to a new location:
http://blogs.microsoft.co.il/blogs/ScriptFanatic/
-Shay
Hey guys how can I use asterisk here
([WMICLASS]"\\192.168.29.4\ROOT\CIMV2:win32_process").Create("cmd /c net send * test")
powershell recognize it like wildcard may be and can't run
This works
([WMICLASS]"\\192.168.29.4\ROOT\CIMV2:win32_process").Create("cmd /c net send 192.168.29.4 test")
Try to escape it with a backtick (i.e `):
([WMICLASS]"\\192.168.29.4\ROOT\CIMV2:win32_process").Create("cmd /c net send `* test")
Sorry, doesnt work i've tried. I have no idea what to do next. Tried to found in regular expressions - can't
What if you put the command inside single quetes?
([WMICLASS]"\\192.168.29.4\ROOT\CIMV2:win32_process").Create('cmd /c net send * test')
Doesn't work. Only with ips
And this?
$asterisk = '*'
...("cmd /c net send $asterisk test")
And this one too
Do you get any error message?
No error message.
Only this like everything is Ok
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 2
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ProcessId : 2836
ReturnValue : 0
ReturnValue of 0 means success. Can you replace * with your user/computer and see if it works?
If I put name of pc or ip message appears and returnvalue:0 but with asterisk message do not appears
And if you do the same from a dos prompt?
From dos promt on that pc I've runned net send * test and net send 192.168.5.1 test both works :(
Lets take this offline, email me to scriptolog AT gmail DOT com.
thanks very usefull
What to do if you what to kill process remotely and you dont know the handle only the process name?
Is there an easier way?
Kate,
To kill a process remotely you need to use WMI and the Terminate method (unless you're using PowerShell 2.0 and can issue Stop-Process on the remote machine).
Get-WMIObject Win32_Process -ComputerName comp1 -Filter "Name='processName.exe'" | Foreach-Object {$_.Terminate()}
Hy,
I have a problem when i try to run notepad PC using our code:
$cmd="notepad.exe"
$server="10.1.1.10"
$user="Administrator"
$pass="1qaz@WSX"
$process = [WMICLASS]"\\$server\ROOT\cimv2:Win32_Process"
$process.psbase.Scope.Options.userName=$user
$process.psbase.Scope.Options.Password=$pass
$process.psbase.Scope.Options.Impersonation = [System.Management.ImpersonationLevel]::Impersonate
$process.psbase.Scope.Options.Authentication = [System.Management.AuthenticationLevel]::PacketPrivacy
$process.Create($cmd)
# get bach the process id and returnValue
$process.ProcessId
$process.ReturnValue
i recive the following error:
Cannot convert value "\\(10.1.1.10)\ROOT\cimv2:Win32_Process" to type "System.Management.ManagementClass". Error: "The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)"
do you know what could be the problem?
also i've put $server in brackets $(server) : $process = [WMICLASS]"\\$($server)\ROOT\cimv2:Win32_Process"
Now i recive the following error:
Cannot convert value "\\10.1.1.10\ROOT\cimv2:Win32_Process" to type "System.Management.ManagementClass". Error: "Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))"
any firewalls inbetween?
Hi,
In case of Windows mobile standard emulators and devices there seems to be a problem with the GetProcessById API.Could you please explain the reason for this?
Thanks in advance.
Sorry, I cannot explain it. I don't use mobile emulators :(
Does mobiles have .net framwork?
Post a Comment