Get-WmiCustom (aka: Get-WMIObject with timeout!)

I make heavy use of WMI.

But when using it to gather information from customer’s machines for assessments, I sometimes find the occasional broken WMI repository. There are a number of ways in which WMI can become corrupted and return weird results. Most of the times you would just get errors, such as “Class not registered” or “provider load failure”. I can handle those errors from within scripts.

But there are some, more subtle – and annoying – ways in which the WMI repository can get corrupted. the situations I am talking about are the ones when WMI will accept your query… will say it is executing it… but it will never actually return any error, and just stay stuck performing your query forever. Until your client application decides to time out. Which in some cases does not happen.

Now that was my issue – when my assessment script (which was using the handy Powershell Get-WmiObject cmdlet) would hit one of those machines… the whole script would hang forever and never finish its job. Ok, sure, the solution to this would be actually FIXING the WMI repository and then try again. But remember I am talking of an assessment: if the information I am getting is just one piece of a bigger puzzle, and I don’t necessarily care about it and can continue without that information – I want to be able to do it, to skip that info, maybe the whole section, report an error saying I am not able to get that information, and continue to get the remaining info. I can still fix the issue on the machine afterward AND then run the assessment script again, but in the first place I just want to get a picture of how the system looks like. With the good and with the bad things. Especially, I do want to take that whole picture – not just a piece of it.

Unfortunately, the Get-WmiObject cmdlet does not let you specify a timeout. Therefore I cooked my own function which has a compatible behaviour to that of Get-WmiObject, but with an added “-timeout” parameter which can be set. I dubbed it “Get-WmiCustom”

Function Get-WmiCustom([string]$computername,[string]$namespace,[string]$class,[int]$timeout=15)
{
$ConnectionOptions = new-object System.Management.ConnectionOptions
$EnumerationOptions = new-object System.Management.EnumerationOptions

$timeoutseconds = new-timespan -seconds $timeout
$EnumerationOptions.set_timeout($timeoutseconds)

$assembledpath = "\\" + $computername + "\" + $namespace
#write-host $assembledpath -foregroundcolor yellow

$Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions
$Scope.Connect()

$querystring = "SELECT * FROM " + $class
#write-host $querystring

$query = new-object System.Management.ObjectQuery $querystring
$searcher = new-object System.Management.ManagementObjectSearcher
$searcher.set_options($EnumerationOptions)
$searcher.Query = $querystring
$searcher.Scope = $Scope

trap { $_ } $result = $searcher.get()

return $result
}

You can call it as follows, which is similar to how you would call get-WmiObject

get-wmicustom -class Win32_Service -namespace "root\cimv2" -computername server1.domain.dom

or, of course, specifying the timeout (in seconds):

get-wmicustom -class Win32_Service -namespace "root\cimv2" -computername server1.domain.dom –timeout 1

and obviously, since the function returns objects just like the original cmdlet, it is also possible to pipe them to other commands:

get-wmicustom -class Win32_Service -namespace "root\cimv2" -computername server1.domain.dom –timeout 1 | Format-Table

11 Responses to “Get-WmiCustom (aka: Get-WMIObject with timeout!)”

  1. Andrew Says:

    Hello!

    I'm writing script for collecting network adapters data using WMI.
    I tried to use your great code (ooh, that 10 000 000 days timeout was a someone's crazy idea… ), but have faced some problems.
    The code works well if queried computer is either available or totally offline. If it is online but do not returns WMI object properly, I have PowerShell totally crashed with the message "An error has occurred that was not properly handled. Addidtional information is shown below. The Windows PowerShell process will exit". After that PowerShell console closing… Do you have any idea how to fix such behaviour?

    I am using such function call: get-wmicustom -class win32_networkadapterconfiguration -namespace "root\cimv2" -computername sunset -timeout 10
    Crash occurs when executing $result = $searcher.get() reaching timeout.

    Thank you very much.

  2. Daniele Muscetta Says:

    I have not seen this… but that is what the trap is for.

    try changing
    trap { $_ } $result = $searcher.get()

    to
    trap { $_; continue } $result = $searcher.get()

  3. D Says:

    Hi there,

    This is cool and seems to work well for single queries. As a Powershell novice I'm trying to figure out how I can make it work in a script that reads computer names from a CSV file and queries the space on the C: partition via your function (which I modified quickly to add a filter, which does work). I tried the following code but it returns errors like 'Exception calling "Connect" with "0" argument(s): "The RPC server is unavailable."' I guess this means the computer names aren't being read in properly. Any idea how to make this work? Thanks so much!

    Function Get-WmiCustom([string]$computername,[string]$namespace,[string]$class,[int]$timeout=15)
    {
    $ConnectionOptions = new-object System.Management.ConnectionOptions
    $EnumerationOptions = new-object System.Management.EnumerationOptions

    $timeoutseconds = new-timespan -seconds $timeout
    $EnumerationOptions.set_timeout($timeoutseconds)

    $assembledpath = "\\" + $computername + "\" + $namespace
    #write-host $assembledpath -foregroundcolor yellow

    $Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions
    $Scope.Connect()

    $querystring = "SELECT * FROM " + $class + " where DriveType=3 AND DeviceID='C:'"
    #write-host $querystring

    $query = new-object System.Management.ObjectQuery $querystring
    $searcher = new-object System.Management.ManagementObjectSearcher
    $searcher.set_options($EnumerationOptions)
    $searcher.Query = $querystring
    $searcher.Scope = $Scope

    trap { $_ } $result = $searcher.get()

    return $result
    }

    $csv = "machines.csv"

    $import = Import-CSV $csv

    $import | foreach {
    get-wmicustom -class win32_logicaldisk -namespace "root\cimv2" -computername {$_.hostname} | Select SystemName,DeviceID,VolumeName,@{Name="size(GB)";Expression={"{0:N1}" -f($_.size/1gb)}},@{Name="freespace(GB)";Expression={"{0:N1}" -f($_.freespace/1gb)}}
    }

  4. Daniele Muscetta Says:

    I should see how your CSV file looks like… why not just a simple TXT file, with one machine per row?

    foreach ($comp in (get-content c:\computers.txt))
    {
    get-wmicustom -class win32_logicaldisk -namespace "root/cimv2" – computername $comp
    }

  5. Kevin Says:

    Thanks for this post, it is very frustrating that most powershell cmdlets do not have a timeout value. I made a modification to your function to add the ability to add the -filter attribute, just two changes were needed. I added – [string]$filter – to the variable definition line and made this modification to the $querystring variable

    $querystring = "SELECT * FROM " + $class + $(if ($filter) {" WHERE $filter"})

  6. Daniele Muscetta Says:

    Sweet addition, thanks.

  7. Sam Says:

    This code is killing me. I have a system that times out, but can't find a way to trap the error so that I can make further decisions on it… Seems like PowerShell doesn't always stop processing lines during the pause. What am I missing in this code?

    trap {$_;$Script:WMIWorks = $False; Write-Host "Value of WMITest: $WMIWorks";continue}
    $ComputerName = ""
    $Error.Clear()
    $ConnectionOptions = new-object System.Management.ConnectionOptions
    $EnumerationOptions = new-object System.Management.EnumerationOptions
    $timeoutseconds = new-timespan -seconds 10
    $EnumerationOptions.set_timeout($timeoutseconds)
    $assembledpath = "\\" + $computername + "\root\cimv2"
    $Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions
    $Scope.Connect()
    $querystring = "SELECT * FROM Win32_NetworkAdapterConfiguration"
    $query = new-object System.Management.ObjectQuery $querystring
    $searcher = new-object System.Management.ManagementObjectSearcher
    $searcher.set_options($EnumerationOptions)
    $searcher.Query = $querystring
    $searcher.Scope = $Scope
    $WMIWorks = $True
    $AdapterDetails = $Searcher.Get()
    #IF the WMI call times out, this IF statement and ELSE statement get ignored. I don't know why.
    If ($WMIWorks -eq $False)
    {
    Write-Host "We will do lots of things differently"
    # We might do a DNS lookup
    # We might ping the host
    # We may change where where we write the out put to
    # We might write this to a different spread sheet in excel and color it red
    }
    Else
    {
    $AdapterDetails
    Write-Host "We will do things as planned"
    #Write the out put to Excel and color code it green
    }
    Write-Host "Value of WMI Test is: $WMIWorks"

  8. Daniele Muscetta Says:

    Why did you place the trap that high? in my example it is just wrapping the $searcher.get() –> which is where the timeout would occur.

  9. Kelly S Says:

    Trying to use your function but have run into a very strange behavior… I'm pulling server names from a DB then query them for OS and compare that to what the DB has already. Problem is… the results returned come back with the exact same OS from wmi for all servers. Change back to using get-wmiobject and problem disappears…

    Any ideas?

  10. Daniele Muscetta Says:

    Kelly, I am not sure, I would have to see/test your script myself to try to understand what is happening there….

  11. Ben Langton Says:

    Excellent function; just what I needed. Thank you!


%d bloggers like this: