(Note: These solutions were written for Beginner Event 8 of the 2010 Scripting Games.)
Beginner Event 8 (Windows PowerShell)
- System Architect and Windows PowerShell trainer
- Windows PowerShell MVP
- Co-Founder of the French Windows PowerShell Community
- Author of the book Windows PowerShell (versions 1 et 2) : Guide de référence
pour l’administration système. Editions ENI. 2010
The first thing that comes to mind when dealing with processes is that there’s a command for that. Let’s try the Get-Process cmdlet:
This is, of course, just an extract of the process list on my system. What we can notice here is that we do not have an owner property. But don’t be discouraged: We are going to “cook” our process objects (which is what we got) in order to see if the owner property exists. As you may know, Windows PowerShell does not display by default every property of the objects (even if they exist), but only a small set that has been selected by the Windows PowerShell Team. For more information about this mechanism, take a look at this topic by typing: Help about_Format.ps1xml
Now we are going to get the members of a process object by using the Get-Member cmdlet on Get-Process:
The result is too long, to be exposed here, but we have no luck; there is no owner property anywhere. So how to obtain this information?
Our scripting experiences in an old life (before Windows PowerShell) make us immediately think about WMI, and especially about the Win32_Process class. Let see if there’s any property or method which can help us to solve our problem:
Surprise ! There’s a method whose name is GetOwner. The goal is approaching.
Now it’s time to have a look on MSDN about how to use this method, here’s the link: http://msdn.microsoft.com/en-us/library/aa394372(v=VS.85).aspx.
As you can notice, we have to call this method and to ask the user property to grab the owner of the process. The other property, domain, returns the domain the user belongs to.
So let’s try this script:
In order to get the process owner, don’t forget to launch your Windows PowerShell console with Administrator privileges.
The results from running Beg8.ps1 are seen in the following image.
Thanks to the pipeline and the foreach instruction, we analyze each object returned by Get-WmiObject Win32_process, and we build a string. As usual, $_ represents an object, the one being passed through the pipeline. So we concatenate the name property of the process with a white space and then we add the process owner user name. We use the parentheses in order to force the interpreter to evaluate an expression. It would provoke an error to omit them. Finally, the string now created is passed to the Write-Output cmdlet in order to be output.
To get the first bonus, we just have to pass the result via the pipeline to the Sort-Object cmdlet, which gives this script:
Last bonus, but not the least: grouping instances of the same process, as shown in this script:
In this script, which is a little bit different compared to the previous ones, we are creating a custom object with two properties: the name of the process, and a compute property, which is the process owner. Last, we use the Group-Object cmdlet in order to let Windows PowerShell group our objects based on their name. Isn’t it magical?
The results from running Beg8bonus2.ps1 are seen in the following image.
Beginner Event 8 (VBScript)
Name: Michael Frommhold
Title: Premier Field Engineer Microsoft Corp
Full script: http://bit.ly/2010sgb8vbscript
I'm a PC and I want to know what I'm doing. We want to list all running processes and their owners on a computer, sorted alphabetically and grouped.
Retrieving a collection of all running processes is sort of simple if you use the proper toolset. In this case, this would be WMI. You simply execute a query against the Win32_Process class in the root\cimV2 namespace and walk the returned collection object, as shown here:
Let's inspect how we do that. First of all we initialize a SWbemLocator object (myLocator). That's cool because we can pass authentication and impersonation settings to the WMI connection we want to establish, as well as the privileges we want to enable for this connection, before we actually connect to the computer and the desired namespace. Setting value PktPrivacy (value 6) to AuthenticationLevel authenticates and signs and encrypts each data packet. ImpersonationLevel Impersonate (value 3) uses the caller's credentials to authenticate against the SWbemService object. The method Add of the property Privileges adds, if necessary, and activates (switch True) or deactivates (switch False) a privilege in the token of the call to initialize the SWbemService object. If we want to terminate processes owned by other accounts than ourselves (built into the script as well), we need to activate privilege SeDebugPrivilege.
The SWbemService object (myWMI) is initialized via the SWbemLocator.ConnectServer method, passing the computer name (. = local host) and the namespace to be connected.
Executing the query SELECT * FROM Win32_Process will give us the collection of all running processes on the local host in the wmiCollection object. To get the processes' information, we just walk through the collection and examine each SWbemObject (wmiProcess) in our collection.
To get the process owner, we need to execute another WMI method, GetOwner from Win32_Process, and identify the process by its ProcessID. This is shown here:
To identify the owner account we collect account authority (Domain) and account name (User). This is all the magic we need to get the desired information.
Next step: Sorting the collected information
Thinking of a list object, that can be used in VBS, and having a Sort method in its backpack, arrays, collections, and dictionaries are out of scope. Bubble sorting is definitely not cool. But lucky us, there are ADO (ActiveX Data Object) RecordSet objects, implementing a Sort method as seen here:
All we have to do is initial an unbound RecordSet object (myProcesses), add the desired fields we want to fill with data (.Fields.Append), fill the RecordSet, and then finally sort it by any field we consider reasonable (myProcesses.Sort).
Third step: Group processes
My idea was to show a tree view of processes with the same name and child processes within the group indented from the position of its parent process, as in the following example:
This example shows us a cmd.exe process with process ID (PID) 4976 that has no child and no parent processes in this group. The process with PID 8900 has a child process with PID 3140, which has a child process with PID 2960.
To accomplish this we make an extensive use of the Filter method of RecordSet objects and store the grouped processes in a new RecordSet object (see methods GroupProcesses, SortGroup, LoopSubs and AddEntry of the ProcEx class in the script below).
First, we walk the record set myProcesses and check whether there is more than one instance of each process (GroupProcesses).
In case there is only one instance, we add the record set representing the process to the grouped record set myGroup with value 0 for the field Level (AddEntry 0). This will mark a process as the first process in a group and will be used later when building the output string.
If there are multiple instances of this process method, SortGroup is called where we check the examined process for existing parent processes within the group. If there are there no parents and no children, we add the process to the grouped record set myGroup with value 0 for the field Level. If any children are present, the process is added with value 1 for the field Level, and the children are walked recursively and added with the appropriate value for Level to the grouped record set myGroup in LoopSubs.
Final task: Display list
The output string is built in the method BuildreturnString of the ProcEx class. Here we walk the grouped record set and examine the values of the Level field.
In case of value 0, the name of the process enclosed by curly braces is added to the output string. Next, the output line starts with two spaces followed by the PID and the owner. Value 1 will just add two spaces, the PID, and the owner as new line. Any child process identified by a value higher than 1 will be added with two more leading spaces and a plus sign to visualize the tree view.
Add-on: Elevated check (method CheckElevated).
The IShellDispatch2 method CanStartStopService is used against the spooler service to indicate whether we started the script from an elevated/administrative command shell.
Add-on: Show OwnerSID (command line argument /SID).
If you pass the switch /SID calling the script, the tree view will additionally display the owner SID.
Add-on: Terminate a process (command line argument /k:PID).
Passing the switch k with the PID of a process will terminate the process by calling the WMI method: Terminate from Win32_Process (see method KillProcess).
Add-on: Terminate a process tree (command line argument /kt:PID).
Switch kt given together with the PID of a process with child processes will terminate recursively all child processes of the given process and the process itself (see method KillProcessTree).
Call: cscript /nologo ProcessHandler.vbs from an elevated command shell:
Call: cscript /nologo ProcessHandler.vbs from a non-elevated command shell:
Call: cscript /nologo ProcessHandler.vbs /SID from an elevated command shell:
Call: cscript /nologo ProcessHandler.vbs /kt:PID from an elevated command shell:
Ed Wilson and Craig Liebendorfer, Scripting Guys