Monthly Archive

PowerShell and WMI

Creating Registry Key

I had a question left on the blog asking how to create a registry key. My preferred method is to use the CIM class = StdRegProv. Its a static class so you don’t need to create an object


[uint32]$hklm = 2147483650
$newkey = 'SOFTWARE\NewKey'

Invoke-CimMethod -ClassName StdRegProv -MethodName CreateKey -Arguments @{hDefKey = $hklm; sSubKeyName = $newkey}


Define the variables for the HIVE in this case HKLM – local machine .  Notice that the value has to be an unsigned integer.


The key you want to create is just the path to the key. if you need to create multiple levels of subkeys they will all create from a single path.


Then use Invoke-CimMethod to call the CreateKey method on StdRegProv. The hash table in Arguments parameter has the method parameter names and appropriate  values.


If everything works you’ll get a return value of 0.


How did I know which parameters the method took?


I used Get-CimClass but that’s a story for another post.

Accessing WMI

There are 3 sets of cmdlets for working with WMI classes – the WMI cmdlets, the WSMAN cmdlets and the CIM cmdlets.  The protocols used by these 3 sets are different.


The WMI cmdlets introduced in PowerShell 1 & 2 use DCOM for local and remote access under all circumstances


The WSMAN cmdlets introduced in PowerShell 2 use WSMAN (WinRm)

The CIM cmdlets introduced in PowerShell 3 use:

- DCOM for local access if ComputerName parameter NOT used

- WSMAN for local access IF –ComputerName parameter is used

- WSMAN (WinRM) for remote access 

- WSMAN if a default CIM session is used for remote access

- DCOM if a CIM session is created using DCOM as the protocol option


The CIM cmdlets are easier to use than the WSMAN cmdlets and are the recommended way to access WMI classes.

WMI wildcards and filtering

A question on the forum asking about filtering WMI results raises a number of interesting points.


The user wanted to pass a computername and a filter term to pull product information from remote machines. I ended up with this

$computername = $env:COMPUTERNAME
$filter = 'Live'

$scriptblock = {
    Get-WmiObject -Class Win32_product -Filter "Name LIKE '%$filter%'" |
    Select  IdentifyingNumber, Name, LocalPackage }

Invoke-Command -ComputerName $computername -ScriptBlock $scriptblock -ArgumentList $filter


You can pass an argument into the scriptblock you use with invoke-command by using the –Argumentlist parameter.

More interesting is the –Filter parameter on Get-Wmi-Object

-Filter "Name LIKE '%$filter%'"


Notice that % is the wildcard not * as you’d use for a string.  Its always better to filter the results from Get-WmiObject using –Filter rather than a where-object after the call.


Of course you can just use the wmi or cim cmdlets directly for this problem which is even better

Get-WmiObject -Class Win32_Product -ComputerName $computername -Filter "Name LIKE '%$filter%'" | Select  IdentifyingNumber, Name, LocalPackage


Get-CimInstance -ClassName Win32_Product -ComputerName $computername -Filter "Name LIKE '%$filter%'" | Select  IdentifyingNumber, Name, LocalPackage

WMI cmdlets and credentials

If you’re working with the WMI cmdlets and need to pass credentials you’ll end up with a statement something like this

Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computer -Credential $cred


If the computer name defaults to the local host or you use . or ‘localhost’ as the computer name you’ll get an error

PS> Get-WmiObject -Class Win32_ComputerSystem -ComputerName $env:COMPUTERNAME  -Credential $cred
Get-WmiObject : User credentials cannot be used for local connections
At line:1 char:1
+ Get-WmiObject -Class Win32_ComputerSystem -ComputerName $env:COMPUTER ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Get-WmiObject], ManagementException
    + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand


You need to build in some logic to prevent credentials being used ig you’re working against the local machine.  One way to this is to create a bunch of Get-WmiObject statements and use if statements to decide which to use.


I think there’s a neater way if you use splatting

#$computer = $env:COMPUTERNAME
#$computer = 'localhost'
#$computer = '.'
$computer = 'server02'

$params = @{
    'Class' =  'Win32_ComputerSystem '
    'ComputerName' = $computer

switch ($computer){
    "$env:COMPUTERNAME" {break}
    'localhost' {break}
    '.'   {break}
    default {$params += @{'Credential' = $cred}}

Get-WmiObject @params


Splatting involves creating a hash table of the parameters and their values. You can then use a switch statement to decide if computer matches any of the local name variants. If it doesn’t then add the credential


You could extend this slightly to cope with not having a computer name  in the initial set of params and only add it if required

$params = @{
    'Class' =  'Win32_ComputerSystem'

if ($computer) {
    $params += @{'ComputerName' = $computer}
    switch ($computer){
        "$env:COMPUTERNAME" {break}
        'localhost' {break}
        '.'   {break}
        default {$params += @{'Credential' = $cred}}

Get-WmiObject @params


Then you only test the computer name if you need to.

WMI linked classes

You will find that many WMI classes have links – some are associations built in to WMI (a subject for another time) while other classes can be linked based on property values. An example of the latter is the Win32_NetworkAdapter and Win32_NetworkAdapterConfiguration classes. The DeviceId on Win32_NetworkAdapter  matches Index on Win32_NetworkAdapterConfiguration .


The follwoing function (adapted from a question on the forum) shows one way this link can be used

function Get-NetworkInfo{
    param( [string]$ComputerName )

    $networkinfo = @()

    $networks = Get-WmiObject Win32_Networkadapter -Filter 'NetEnabled=True' -ComputerName $ComputerName
    $adapter = 0
    foreach($network in $networks){

        $id = $network.DeviceId
        $IPinfo = Get-WmiObject win32_networkadapterconfiguration -Filter "Index = $id" -ComputerName $ComputerName
        $winServers = 0
        $winServers = ($IPinfo.WinsPrimaryServer -join ','),($IPinfo.WinsSecondaryServer -join ',')

        $adapter += 1

        $props = @{
            'Adapter' = $adapter;
            'Manufacturer' = $network.Manufacturer;
            'Description' = $network.Description;
            'Connection' = $network.NetConnectionID;
            'SpeedGB' = [math]::Round($network.Speed / 1GB, 2)
            'IPAddress' = $IPinfo.IPAddress -join ','
            'Submask' = $IPinfo.IPSubnet -join ','
            'Gateway' = $IPinfo.DefaultIPGateway -join ','
            'DNSServers' = $IPinfo.DnsServerSearchOrder-join ','
            'WinServers' = $winServers -join ','
            'DomainSuffixes' = $IPinfo.DNSDomainSuffixSearchOrder -join ','
        $networkinfo += New-Object -TypeName psobject -Property $props
Get-NetworkInfo -ComputerName $env:COMPUTERNAME

NICs with IP addresses

A question on the forum asked about discovering those network adapters that had IP addresses configured. The user had tried

PS> Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "IPAddress IS NOT NULL"
Get-WmiObject : Invalid query "select * from Win32_NetworkAdapterConfiguration where IPAddress IS NOT NULL"
At line:1 char:1
+ Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "IPAdd ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-WmiObject], ManagementException
    + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand


Actually they’d tried the full WQL query. I don’t do that because its more typing than using the class name and a filter.


The problem is that IPAddress is a string array and WQL won’t let you query against an array of properties.


You have 2 choices

Get-WmiObject -Class Win32_NetworkAdapterConfiguration | where IPAddress


which is fine fro local machines but means you are filtering out results on the local machine when querying a remote machine – wasteful.


A better solution is to use the IPEnabled property. This is a boolean that is set to TRUE when an IP address (static or DHCP) has been set on a NIC. Your query then becomes

Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $true"


Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $true"

Win32_ReliabilityRecords Class

The Win32_ReliabilityRecords class was introduced with Windows 7. It contains information from the Windows Event Log related to system reliability.


The most interesting properties are the Message and the TimeGenerated

£> Get-WmiObject -class win32_reliabilityRecords | select -First 1 | fl Message, TimeGenerated

Message       : Installation Successful: Windows successfully installed the following update: Definition Update for  Windows Defender - KB2267602 (Definition 1.207.1367.0)
TimeGenerated : 20150929170914.620000-000


Notice the usual delightful date format on TimeGenerated


Easiest way to resolve this and get sensibly formatted date is to use the CIM cmdlets

£> Get-CimInstance -ClassName win32_reliabilityRecords | select -First 1 | fl Message, TimeGenerated

Message       : Installation Successful: Windows successfully installed the following update: Definition Update for  Windows Defender - KB2267602 (Definition 1.207.1367.0)
TimeGenerated : 29/09/2015 18:09:14


Now you have a date you can read easily.

WMI dates

Dates as reported by WMI still seem to cause a lot of problems. If you use the WMI cmdlets

£> Get-WmiObject -Class Win32_OperatingSystem | select *date* | fl

InstallDate   : 20131205101649.000000+000
LocalDateTime : 20150728121320.002000+060


That format is year, month, day, hour, minute, second then fractions of a second after the decimal point with the final +nnn indicating an offset from Greenwich Mean Time  (UTC) for time zones and daylight saving time.


You can read the date presented by WMI but its not intuitive.


The PowerShell team added a ConvertToDateTime() to the object output by WMI so that you can easily perform date conversions

£> Get-WmiObject -Class Win32_OperatingSystem | select @{N='Install'; E={$_.ConvertToDateTime($_.Installdate)}}, @{N='Lo
calDate'; E={$_.ConvertToDateTime($_.LocalDateTime)}} | fl

Install   : 05/12/2013 10:16:49
LocalDate : 28/07/2015 12:16:26


Though my preferred solution these days is to use the CIM cmdlets as they convert the date for you without any extra effort

£> Get-CimInstance -ClassName Win32_OperatingSystem | select *date* | fl

InstallDate   : 05/12/2013 10:16:49
LocalDateTime : 28/07/2015 12:17:29

Disk identification

A recent question on the forums regarded using the Win32_LogicalDisk class to retrieve disk size and free space data.  A filter based on the disk letter was being used. The main error with the code was that the filter was being expressed as



rather than



The colon is necessary as its part of the DeviceId data – if you are in doubt about the form of the data required by the filter then examine the full output of the class to see an example.


There were a couple of other basic issues.


Firstly always output objects.


Secondly use the size constants MB, GB etc rather than calculating refresh each time.


The final modified code looks like this

$computername = $env:COMPUTERNAME
$partition = 'C:'
$description = 'backup_server'

Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceId='$partition'"     -ComputerName $computername |
select PSComputerName,
@{Name='Partition'; Expression={$_.DeviceId}},
@{Name='Description'; Expression={$description}},
@{Name='Size(GB)';Expression={[math]::Round(($_.Size / 1GB), 2)}},
@{Name='FreeSpace(GB)';Expression={[math]::Round(($_.FreeSpace / 1GB), 2)}}

CIM filters

I was looking up Win32_SystemDriver on the MSDN site and noticed there was some PowerShell example code


Get-WmiObject -Class Win32_SystemDriver |
Where-Object -FilterScript {$_.State -eq "Running"} |
Where-Object -FilterScript {$_.StartMode -eq "Manual"} |
Format-Table -Property Name,DisplayName


A better way to write this would be:

Get-WmiObject -Class Win32_SystemDriver -Filter "State='Running' AND StartMode='Manual'" | Format-Table -Property Name, DisplayName –AutoSize




Get-CimInstance -ClassName Win32_SystemDriver -Filter "State='Running' AND StartMode='Manual'" | Format-Table -Property Name, DisplayName -AutoSize


Do the filtering in the CIM call – especially if you’re running this against a number of remote machines. That way you limit the network traffic you’re returning