Expert Solutions: Beginner Event 8 of the 2010 Scripting Games

ScriptingGuy1

 

Bookmark and Share

(Note: These solutions were written for Beginner Event 8 of the 2010 Scripting Games.)

Beginner Event 8 (Windows PowerShell)

Photo of Arnaud Petitjean

Arnaud Petitjean

————

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:

   1: PS > Get-Process

   2: Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id 

   3: ProcessName

   4: ------- ------ ----- ----- ----- ------ -- -----------

   5: 57 3 

   6: 1000 4540 47 0,05 712 conhost

   7: 59 3 1968 5516 49 2,50 1816 conhost

   8: 59 3 

   9: 1964 5640 48 23,93 3332 conhost

  10: 544 6 1248 2808 35 1,92 428 csrss

  11: 537 12 

  12: 33800 27048 294 32,32 512 csrss

  13: 80 4 1820 4448 42 0,12 2920 dwm

  14: 1178 44 

  15: 59112 64984 262 37,02 3524 explorer

  16: 110 6 3040 8224 74 0,08 620 

  17: GrooveMonitor

  18: ...

  19: 0 0 0 24 0 0 Idle

  20: 518 18 11976 24124 150 0,97 328 

  21: iexplore

  22: 581 19 13336 26536 165 0,73 1096 iexplore

  23: 356 14 5416 17488 112 

  24: 0,75 2688 iexplore

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:

   1: PS > Get-Process | Get-Member

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:

   1: PS > Get-WmiObject Win32_Process | Get-Member

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:

Beg8.ps1

   1: # Arnaud Petitjean, PowerShell-Scripting.com

   2: # 2010 Scripting Games Beginner 

   3: Event 8

   4: Get-WmiObject Win32_process | 

   5: foreach { Write-Output ($_.name + ' 

   6: ' + $_.GetOwner().user) }

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.

Image of results of running Beg8.ps1

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:

Beg8bonus1.ps1

   1: # Arnaud Petitjean, PowerShell-Scripting.com

   2: # 2010 Scripting Games Beginner 

   3: Event 8

   4: # Sorting the process alphabetically

   5: Get-WmiObject Win32_process | 

   6:  

   7: foreach { Write-Output ($_.name + ' ' + $_.GetOwner().user) } | 

   8:  

   9: Sort-Object

Last bonus, but not the least: grouping instances of the same process, as shown in this script:

Beg8bonus2.ps1

   1: # Arnaud Petitjean, PowerShell-Scripting.com

   2: # 2010 Scripting Games Beginner 

   3: Event 8

   4: # grouping instances of the same process

   5: Get-WmiObject 

   6: Win32_process | 

   7: Select-Object -Property Name,@{ Name="Owner"; Expression = 

   8: {$_.GetOwner().user} } | 

   9: Group-Object -Property name

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.

Image of results of running Beg8bonus2.ps1

Beginner Event 8 (VBScript)

Photo of Michael Frommhold

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:

   1: Set myLocator = 

   2: CreateObject("WbemScripting.SWbemLocator")

   3: myLocator.Security_.AuthenticationLevel 

   4: = PktPrivacy

   5: myLocator.Security_.ImpersonationLevel = 

   6: Impersonate

   7: myLocator.Security_.Privileges.Add SeDebugPrivilege, True

   8: Set 

   9: myWMI = myLocator.ConnectServer(".", "root\cimV2")

  10: Set wmiCollection = 

  11: myWMI.ExecQuery("SELECT * FROM Win32_Process")

  12: For Each wmiProcess In 

  13: wmiCollection

  14: WScript.Echo "Name: " & wmiProcess.Name

  15: WScript.Echo 

  16: "ProcessID: " & wmiProcess.ProcessID

  17: WScript.Echo "ParentProcessID: " 

  18: & wmiProcess.ParentProcessID

  19: Next

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:

   1: Set wmiReturn = myWMI.ExecMethod("Win32_Process.Handle='" & 

   2: wmiProcess.ProcessID & "'", "GetOwner")

   3: WScript.Echo "Owner: " & 

   4: wmiReturn.Domain & "\" & wmiReturn.User

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:

   1:  

   2: Set myProcesses = 

   3: CreateObject("ADODB.Recordset")

   4: myProcesses.Fields.Append "Name", adWChar, 

   5: 255

   6: myProcesses.Fields.Append "ProcessID", 

   7: adInteger

   8: myProcesses.Fields.Append "ParentProcessID", 

   9: adInteger

  10: myProcesses.Fields.Append "Owner", adBSTR, 

  11: 255

  12: myProcesses.Fields.Append "Level", adInteger

  13: myProcesses.Open

  14: For 

  15: Each wmiProcess In wmiCollection

  16: myProcesses.AddNew 

  17:  

  18: myProcesses("Name").Value = 

  19: wmiProcess.Name

  20: myProcesses("ProcessID").Value = 

  21: wmiProcess.ProcessID

  22: myProcesses("ParentProcessID").Value = 

  23: wmiProcess.ParentProcessID

  24: myProcesses("Owner").Value = 

  25: GetProcessOwner(wmiProcess.ProcessID)

  26: Next

  27: myProcesses.Sort = "Name"

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:

   1: {Name}

   2: PID Owner

   3: ===============

   4: {cmd.exe}

   5: 4976 Domain\User

   6: 8900 

   7: Domain\User

   8: + 3140 Domain\User

   9: + 2960 Domain\User

  10: 9708 Domain\User

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

Outputs

Call: cscript /nologo ProcessHandler.vbs from an elevated command shell:

Image of running cscript /nologo ProcessHandler.vbs from elevated command shell

Call: cscript /nologo ProcessHandler.vbs from a non-elevated command shell:
Image of running cscript /nologo ProcessHandler.vbs from non-elevated command shell

Call: cscript /nologo ProcessHandler.vbs /SID from an elevated command shell:

Image of running cscript /nologo ProcessHandler.vbs /SID from elevated command shell

Call: cscript /nologo ProcessHandler.vbs /kt:PID from an elevated command shell:

Image of running cscript /nologo ProcessHandler.vbs /kt:PID from elevated command shell

 

Ed Wilson and Craig Liebendorfer, Scripting Guys