Expert Solution for 2011 Scripting Games Advanced Event 1:Use PowerShell to Find Process Module Versions

Expert Solution for 2011 Scripting Games Advanced Event 1:Use PowerShell to Find Process Module Versions

  • Comments 6
  • Likes

Summary: Guest blogger, Trevor Sullivan, solves 2011 Scripting Games advanced Event 1 and finds process module versions.

Microsoft Scripting Guy, Ed Wilson, here. Today we start two weeks of expert commentators’ solutions for the 2011 Scripting Games. To provide the solution for Advanced Event 1, we have Trevor Sullivan.

Hello, my name is Trevor Sullivan. I have been working on a Windows PowerShell module recently that makes working with Windows Management Instrumentation (WMI) permanent event registrations much easier! The name of this module is PowerEvents—appropriately named to combine Windows PowerShell and WMI eventing. You can download this module free from http://powerevents.codeplex.com, and there is ample documentation in the form of a PDF document, YouTube videos, and the CodePlex.com wiki.

The Advanced Windows PowerShell Event 1 for the 2011 Microsoft Scripting Games, requires that one retrieve file information from a potentially large number of remote systems. Thankfully, this is quite simple to do by using a combination of Windows PowerShell and WMI.

Most of my scripts, whether PowerShell or VBScript, use Windows Management Instrumentation (WMI) to retrieve information about computers or perform actions against them. WMI is particularly useful, because it inherently includes support for accessing information from remote computers, both workstations and servers. Additionally, authentication is handled by using pass-through credentials, which makes writing and executing scripts against it very easy in an Active Directory domain environment. In my script for this challenge, I use the Win32_ProcessExecutable WMI class, in the root\cimv2 WMI namespace, to discover which executable files (libraries, drivers, etc.) have been loaded by a process. This WMI class provides a reference to the CIM_DataFile instance for the executable file in question, which provides detailed file information such as the version, size, path, and so on.

Taking this concept, I wrapped the code inside an advanced function called Get-ExecutableInfo. This reusable function accepts any process name, executable name, and remote computer name from which to retrieve the information.

The other piece to this challenge was to have a list of computers against which to run the code. The Active Directory database is the perfect place to get this information because it theoretically contains a list of all computers on an enterprise network, and it provides for centralized authentication. Windows PowerShell also has type accelerators to make working with Active Directory simple. In this script, I use the [adsisearcher] type accelerator, along with a simple LDAP filter: “(objectClass=computer)”. When the results are retrieved from this query, I iterate over each search result, and call the Get-ExecutableInfo function using the computer’s name, “notepad.exe”, and “winspool.drv” as my parameters for the remote computer, process name, and executable name to get, respectively.

This script demonstrates how easy Windows PowerShell makes working with WMI and Active Directory. WMI is useful for many administrative tasks, including retrieval of file information. Active Directory is the perfect place to get a centralized list of computers in your network environment. I hope that this has been educational for you, and I look forward to writing again.

The complete script is shown here:

function Get-ExecutableInfo {

      [CmdletBinding()]

      param(

            [Parameter(Position=0)]

            [System.String]

            $ComputerName,

            [Parameter(Position=1)]

            [System.String]

            $ProcessName,

            [Parameter(Position=2, Mandatory = $true)]

            [System.String]

            $ExecutableName

      )

 

      process {

            # If computer is not accessible, do not proceed.

            if (-not (Test-Connection $ComputerName -Count 1)) {

                  break

            }

           

            # Find notepad process (just need any one instance)

            $Process = $null;

            $Process = @(Get-WmiObject -ComputerName $ComputerName -Class Win32_Process -Filter "Name = '$ProcessName'")[0];

            #Write-Host $Process.ProcessID

            #Write-Host $ExecutableName

           

            # Get

            if ($Process)

            {

                  # Retrieve list of all executable files that Notepad has loaded

                  $ExecutableList = Get-WmiObject -ComputerName $ComputerName -Query "ASSOCIATORS OF {$($Process.__PATH)} WHERE ResultClass = CIM_DataFile"

                  # Get the first instance of the executable file we are looking for

                  $Executable = @($ExecutableList | ? { $_.Name -like "*$ExecutableName*" })[0]

                  $Output = "`"$($Executable.__SERVER)`",`"$($Executable.FileName)`",`"$($Executable.FileSize)`",`"$($Executable.Name)`",`"$($Executable.Version)`""

                  Write-Output -InputObject $Output

            }

            else

            {

                  break;

            }

      }

}

 

# Retrieves a list of all computers from Active Directory

function Get-Computers()

{

      ([adsisearcher]"(objectClass=computer)").FindAll()

}

 

Clear-Host

# This is only an example!

$ComputerList = Get-Computers

foreach ($Computer in $ComputerList)

{

      Get-ExecutableInfo $Computer.properties.dnshostname notepad.exe 'winspool.drv'

}

Thank you, Trevor, for providing this solution for the Advanced Event 1.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hello Trevor,

    thank you very much for sharing the solution with us!

    I already saved and went through it. It is quite easy to understand except the WMI query containing the "ASSOCIATORS OF" clause, which is something I don't understand.

    There is one thing, you should consider to change: There is nearly no error handling included (which we should provide for the events) and running the script, does produce errors immedeately!

    I get errors with "Test-Connection" and "$process = @(Gwmi ....)[0] at once and ... even worse ... the script doesn't continue then. It stops after it hit the "break" command.

    I'm not a domain adim in our company and the script isn't running elevated ( which might not be necessary)

    I know that error handling is cumbersome and in PS it is even worse than e.g. writing C# code, because you can't catch each error! Some errors can only be handled by adding an erroraction paramter, which makes the whole code look really ugly at times! And admittedly I sometimes trend to use an errorPreference instead for a whole script which is nothing I would recommend!

    Even my short scripts for the beginner events don't shine ( they don't deserve the "beauty of code" award! Having done the first conceptual design without error handling, a script sometimes really "shines" but adding error handling "covers the shine with dust" :-) ... but we have to accept it ...

    kind regards, Klaus

  • I give this 1 star out of 5 using the judges criteria.

    "1 star is awarded if the submission looks like it answers the question or meets the criteria of the scenario."

    "but you should include the capability to run it against multiple machines" - No, get-computers doesn't work because of test-connection/break statments, function only accepts one machine name

    "Liberal use of comments" - No

    "Anything that goes beyond the bare minimum requirements of the scenario" - No

    "Use of graphical elements (that are not specified in the script)" - No

    The following are things that you should strive to avoid:

    "Not using a Windows PowerShell cmdlet or a parameter of a cmdlet that will clearly meet the needs of the script" - Yes

    "Using WMI, .net, COM or other technology when a simple Windows PowerShell cmdlet will do the trick" - Yes

    "Creating a complex function and NOT including comment-based help so that people have a chance to actually understand how to use your function" - Yes

    "Using aliases in scripts—especially really obscure aliases that make your code difficult to read" - Yes

    "Top Ten Suggestions at the End of Week 1":

    "Functions should return objects." - No

    "You should use the Windows PowerShell native capabilities to produce output" - No

    "Top Ten Mistakes Made During Week 1":

    "If a script requires elevated rights to run, your script should check for those rights." No check, script can't run against elevated processes

    "If you have a parameter that must be supplied, make it a mandatory parameter rather than simply throwing an error if it is missing." Computername is required and not checked for.

    "If you have a script that has multiple parameters, use comment-based Help tags to provide Help to the user who needs to use the script." No help

    "When you arrange your examples for comment-based Help, proceed from the simple to the more complex. If your script supports piping, include an example of that as well." No Help

    "If your script can easily cause an error (for example, getting a process that does not exist) you should provide an appropriate level of error handling" No error handling

    levadfasdferrohandling"values that can be supplied to the parameter. A way to handle the error is to use Try/Catch." No Error handling

  • Sorry but this code does not really work as expected.

    The 'break'; does not stop continued execution when the host is not pingable.  The first param is NOT mandatory so it will not be prompted for.

    There are numerous other issues with the code that make it a bad example.

  • Hey folks, I know the script isn't perfect, and for that I apologize. I've been extremely busy with project work recently and also don't have the capacity to test this in a full production environment.

    I at least wanted to be able to demonstrate the retrieval of module information using WMI, and that is primarily what the code / article above does. I'll leave it to the rest of you to perfect it :)

    Cheers,

    Trevor Sullivan

  • You can also get this information using the cmdlet Get-Process instead of using WMI.

    Get-Process -name "notepad" -module | where { $_.ModuleName -eq "winspool.drv" } | select ModuleName, Size, FileName, FileVersion

    This isn't a whole solution but it gives you another idea of how to get to the core data needed for the task.

  • thank you