Monthly Archive

Categories

PowerShell and CIM

WMI and CIM accelerators

In PowerShell an accelerator is a shortcut to a .NET type. The WMI accelerators have been around since PowerShell v1. The WMI accelerators were heavily used in v1 fill some of the gaps in cmdlet coverage. The CIM accelerators appeared in PowerShell v3 (I think – only discovered them recently!). This is how you use the WMI and CIM accelerators.

 

There are three WMI accelerators

wmiclass is shortcut for System.Management.ManagementClass
wmi is shortcut for System.Management.ManagementObject
wmisearcher is shortcut for System.Management.ManagementObjectSearcher

 

And four CIM accelerators are

ciminstance is shortcut for Microsoft.Management.Infrastructure.CimInstance
cimclass is shortcut for Microsoft.Management.Infrastructure.CimClass
cimtype is shortcut for Microsoft.Management.Infrastructure.CimType
cimconverter is shortcut for Microsoft.Management.Infrastructure.CimConverter

plus

CimSession which is a shortcut for Microsoft.Management.Infrastructure.CimSession. Use this to set a parameter type.

 

Notice that there isn’t a direct correspondence between the WMI and CIM accelerators.

PowerShell v6 only has the CIM accelerators

 

The WMI accelerators are used like this:

WMICLASS

This can be used for creating new instances of CIM classes

PS> $p = [wmiclass]'Win32_Process'
PS> $p.Create("notepad.exe")

This is easily replicated using the CIM cmdlets

PS> Invoke-CimMethod -ClassName Win32_Process -MethodName create -Arguments @{CommandLine='notepad.exe'}

 

WMI

The [wmi] accelerator is used to find an instance BUT you have to use the class key!

PS> [wmi]"root\cimv2:Win32_Process.Handle='7264'"

NOTE the handle has to be the one reported by CIM NOT the one reported by Get-Process!

Its much easier to use Get-CimInstance and filter on the name

Get-CimInstance -ClassName Win32_Process -Filter "Name='pwsh.exe'"

 

WMISEARCHER

This is used to find CIM instances:

PS> $query = [wmisearcher]"SELECT * FROM Win32_Process WHERE Name='pwsh.exe'"
PS> $query.Get()

Its easier to use Get-CIMinstance these days

PS> Get-CimInstance -ClassName Win32_Process -Filter "Name='pwsh.exe'"

 

Looking at the CIM accelerators

CIMINSTANCE

This one doesn’t seem to be usable for anything but a type decorator on a parameter.

 

CIMCLASS

Using Get-CimClass

PS> Get-CimClass -ClassName Win32_process | fl

CimSuperClassName : CIM_Process
CimSuperClass : ROOT/cimv2:CIM_Process
CimClassProperties : {Caption, Description, InstallDate, Name...}
CimClassQualifiers : {Locale, UUID, CreateBy, DeleteBy...}
CimClassMethods : {Create, Terminate, GetOwner, GetOwnerSid...}
CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties
CimClassName : Win32_Process

The output is of type Microsoft.Management.Infrastructure.CimClass for which cimclass is an accelerator BUT there doesn’t seem to be a way to use the accelerator to access a class. I think this one is only usable as a type on a parameter for a function where you want to pass in a CIM class object.

 

CIMTYPE

Microsoft.Management.Infrastructure.CimType is an enum that contains the CIM (and WMI) datatypes:

PS> [cimtype]::Boolean
Boolean
PS> [cimtype]::UInt32
UInt32

The full set of CIM data types is

PS> [enum]::GetNames([cimtype])
Unknown
Boolean
UInt8
SInt8
UInt16
SInt16
UInt32
SInt32
UInt64
SInt64
Real32
Real64
Char16
DateTime
String
Reference
Instance
BooleanArray
UInt8Array
SInt8Array
UInt16Array
SInt16Array
UInt32Array
SInt32Array
UInt64Array
SInt64Array
Real32Array
Real64Array
Char16Array
DateTimeArray
StringArray
ReferenceArray
InstanceArray

 

CIMCONVERTOR

Some of the CIM data types shown above don’t directly correspond to .NET types you’re used to from PowerShell. You can use [cimconvertor] which is shortcut for Microsoft.Management.Infrastructure.CimConverter to discover the corresponding .NET or CIM data type

.NET to CIM

PS> [cimconverter]::GetCimType([int32])
SInt32
PS> [cimconverter]::GetCimType([double])
Real64

CIM to .NET

PS> [cimconverter]::GetDotNetType([cimtype]::SInt32)

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType

PS> [cimconverter]::GetDotNetType([cimtype]::Instance)

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True CimInstance System.Object

 

For the most part I think the WMI and CIM accelerators are best ignored. Use the CIM cmdlets instead. The cimtype and cimconverter accelerators are useful when developing code to check on types between CIM and .NET

CIM references and associations

Way back in 2011, when I were just a young lad, I wrote about WMI or CIM references and associations - https://wordpress.com/read/blogs/16267735/posts/1673

ASSOCIATORS show the end point of the link between CIM classes and REFERENCES shows the linking class.

I used the Win32_NetworkAdapter class in the example because it has a known association with Win32_NetworkAdapterConfiguration.

 

One thing I appear to have not made clear is that you have to use the key to the CIM class in the query for the reference or association. The key for a Win32_networkadapter class is DeviceID.

This became clear when a reader pointed out that this won’t work:

PS> Get-WmiObject -Query "REFERENCES OF {win32_printer.name = 'fax'}"
Get-WmiObject : Invalid object path
At line:1 char:1
+ Get-WmiObject -Query "REFERENCES OF {win32_printer.name = 'fax'}"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject], ManagementException
+ FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand

 

But that

Get-WmiObject -Query "REFERENCES OF {Win32_Printer.DeviceID='Fax'}"

works. Again DeviceId is the key to the class.

 

Usually its better to go straight to the association and bypass the reference.

A better approach than writing WQL queries is to use the CIM cmdlets

$p = Get-CimInstance -ClassName Win32_Printer -Filter "Name='Fax'"

Get-CimAssociatedInstance -InputObject $p

will show you the data from the associated classes.

 

If you just want to see the associated class names

PS> Get-CimAssociatedInstance -InputObject $p | select CIMclass

CimClass
--------
root/cimv2:Win32_PrinterDriver
root/cimv2:Win32_PrinterConfiguration
root/cimv2:Win32_ComputerSystem
root/cimv2:CIM_DataFile

 

And if you just want the data for a particular associated class

PS> Get-CimAssociatedInstance -InputObject $p -ResultClassName Win32_PrinterDriver

Caption :
Description :
InstallDate :
Name : Microsoft Shared Fax Driver,3,Windows x64
Status :
CreationClassName : Win32_PrinterDriver
Started :
StartMode :
SystemCreationClassName : Win32_ComputerSystem
SystemName :
ConfigFile : C:\WINDOWS\system32\spool\DRIVERS\x64\3\FXSUI.DLL
DataFile : C:\WINDOWS\system32\spool\DRIVERS\x64\3\FXSUI.DLL
DefaultDataType :
DependentFiles : {C:\WINDOWS\system32\spool\DRIVERS\x64\3\FXSWZRD.DLL,
C:\WINDOWS\system32\spool\DRIVERS\x64\3\FXSTIFF.DLL,
C:\WINDOWS\system32\spool\DRIVERS\x64\3\FXSRES.DLL,
C:\WINDOWS\system32\spool\DRIVERS\x64\3\FXSAPI.DLL}
DriverPath : C:\WINDOWS\system32\spool\DRIVERS\x64\3\FXSDRV.DLL
FilePath :
HelpFile :
InfName :
MonitorName :
OEMUrl :
SupportedPlatform : Windows x64
Version : 3
PSComputerName :

CIM_ or Win32_

If you dig into the classes available on a Windows machine you’ll see a mixture of prefixes – namely CIM_ and Win32_ used for the classes. So which should you use CIM_ or Win32_

 

Lets start by seeing whats available:

PS> Get-CimClass -ClassName *Volume*


    NameSpace: ROOT/CIMV2

CimClassName
------------
Win32_VolumeChangeEvent
Win32_VolumeQuota
Win32_VolumeQuotaSetting
Win32_VolumeUserQuota
CIM_StorageVolume
Win32_Volume
CIM_VolumeSet
Win32_ShadowVolumeSupport
CIM_LogicalDiskBasedOnVolumeSet
Win32_ShadowDiffVolumeSupport

 

The CIM_ classes follow the standard definition from the DMTF - https://www.dmtf.org/standards/cim

The Win32_ classes are Microsoft’s versions of the equivalent CIM_ class often with additional properties and methods.

 

I’ve always recommended using the Win32_ classes because they are “optimised” for Windows. The one exception that I’ve found is CIM_Datafile that doesn’t have a Win32_ equivalent.

 

A question on the forum asked why this was failing

PS> Get-CimInstance -ClassName CIM_StorageVolume -Filter "DriveLetter='C:'"
 Get-CimInstance : Invalid query
 At line:1 char:1
 + Get-CimInstance -ClassName CIM_StorageVolume -Filter "DriveLetter='C: ...
 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     + CategoryInfo          : InvalidArgument: (:) [Get-CimInstance], CimException
     + FullyQualifiedErrorId : HRESULT 0x80041017,Microsoft.Management.Infrastructure.CimCmdlets.GetCimInstanceCommand

 

When this worked

PS> Get-CimInstance -ClassName CIM_StorageVolume | select Name, DriveLetter, Capacity

Name                                              DriveLetter     Capacity
 ----                                              -----------     --------
 \\?\Volume{c1c4c5bb-0000-0000-0000-100000000000}\                366997504
 C:\                                               C:          511210610688
 \\?\Volume{c1c4c5bb-0000-0000-0000-801c77000000}\                529526784
 D:\                                               D:

 

The reason is that you’re not getting back CIM_StorageVolume:

PS> Get-CimInstance -ClassName CIM_StorageVolume | Get-Member


    TypeName: Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Volume

 

You’re getting Win32_Volume which DOES have a DriveLetter property. Get-WmiObject works in the same way. Looking at the properties:

PS> (Get-CimClass -ClassName CIM_StorageVolume ).CimClassProperties | Where Name -like "D*"


Name               : Description
Value              :
CimType            : String
Flags              : Property, ReadOnly, NullValue
Qualifiers         : {read}
ReferenceClassName :

Name               : DeviceID
Value              :
CimType            : String
Flags              : Property, ReadOnly, NullValue
Qualifiers         : {CIM_Key, read}
ReferenceClassName :

 

No DriveLetter. Now try Win32_Volume

PS> (Get-CimClass -ClassName Win32_Volume ).CimClassProperties | Where Name -like "D*"


Name               : Description
Value              :
CimType            : String
Flags              : Property, ReadOnly, NullValue
Qualifiers         : {read}
ReferenceClassName :

Name               : DeviceID
Value              :
CimType            : String
Flags              : Property, Key, ReadOnly, NullValue
Qualifiers         : {CIM_Key, read, key, MappingStrings...}
ReferenceClassName :

Name               : DirtyBitSet
Value              :
CimType            : Boolean
Flags              : Property, ReadOnly, NullValue
Qualifiers         : {read}
ReferenceClassName :

Name               : DriveLetter
Value              :
CimType            : String
Flags              : Property, NullValue
Qualifiers         : {read, write}
ReferenceClassName :

Name               : DriveType
Value              :
CimType            : UInt32
Flags              : Property, ReadOnly, NullValue
Qualifiers         : {MappingStrings, read}
ReferenceClassName :

 

Win32_Volume has 2 extra properties that start with the letter D, including DriveLetter. Win32_Volume is derived from, and builds on, CIM_StorageVolume

PS> Get-WmiObject -Class Win32_Volume | select -First 1 | select -ExpandProperty __Derivation
 CIM_StorageVolume
 CIM_StorageExtent
 CIM_LogicalDevice
 CIM_LogicalElement
 CIM_ManagedSystemElement

 

If you want to get volume information on a Windows box use Win32_Volume or better still on Windows 8 and later use Get-Volume.

The only possible reason for using CIM_StorageVolume is that you’re learning more about CIM or you’re trying some cross-platform task. As I can’t think of a platform other than Windows that has implemented CIM_StorageVolume not sure how far you’ll get on the latter. If you do try CIM_StorageVolume you can filter using the Name property:

Get-CimInstance -ClassName CIM_StorageVolume -Filter "Name='C:\\'"

 

Note that you have to use C:\\ not C:\ because \ is an escape character in WQL.

Alternatively, use where-object to filter:

Get-CimInstance -ClassName CIM_StorageVolume | where DriveLetter -eq 'C:'

 

If you’re working remotely the first option is best as it reduces the amount of data being returned across the network.

 

If in doubt use the Win32_ class rather than the CIM class.

 

Windows Updates CIM classes

When Windows 10 and Server 2016 were released they contained a new CIM namespace - ROOT/Microsoft/Windows/WindowsUpdate

 

This contained a CIM class MSFT_WUOperationsSession that had 2 very useful methods – ScanForUpdates and  ApplyApplicableUpdates.

 

These methods enabled you to find and install updates from the Windows Update site or a WSUS server if you’d configured the machine to use WSUS.

 

Best of all the CIM class could be accessed and used remotely which was a huge step forward over the old COM object that couldn’t be used remotely.

 

Things changed with Windows 10 Fall Creators Update (1709) and Windows Server 1709. MSFT_WUOperationsSession still exists but the methods you need to scan for updates and apply updates are now on the MSFT_WUOperations class. The great thing is that they’re static methods so using them is easier. The bad – no really, really bad – thing is that this class CAN’T BE ACCESSED REMOTELY through a Windows remoting session,  a CIM session or a SSH remoting session.

 

This takes us back to the bad old days of using COM objects. There doesn’t seem to be any reason or explanation for this decision.

 

I have managed to use the class remotely through a PowerShell direct remoting session though which means that I can force updates in my lab. Its not really an answer for production though

Monitor resolution

A question on the forum about getting monitor resolution led to this code

Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorId | 
foreach { 
   
  $filter = ("InstanceName = '$($psitem.InstanceName)'").Replace("`\", "`\`\") 
    
  $maxres = Get-CimInstance -Namespace root\wmi -ClassName WmiMonitorListedSupportedSourceModes -Filter $filter | 
  Select-Object -ExpandProperty MonitorSourceModes | 
  Sort-Object -Property {$_.HorizontalActivePixels * $_.VerticalActivePixels} -Descending | 
  Select-Object -First 1 
  
  if ($psitem.UserFriendlyNameLength -gt 0) { 
    $name = ($psitem.UserFriendlyName -notmatch '^0$' | foreach {[char]$_}) -join "" 
  } 
  else { 
    $name = 'Unknown' 
  }

  $props = [ordered]@{ 
     Manufacturer = ($psitem.ManufacturerName -notmatch '^0$' | foreach {[char]$_}) -join "" 
     Name = $name 
     Serial = ($_.SerialNumberID -notmatch '^0$' | foreach {[char]$_}) -join "" 
     MaximumResolution = "$($maxres.HorizontalActivePixels) x $($maxres.VerticalActivePixels)" 
  } 
  
  New-Object -TypeName PSObject -Property $props 

}

NOTE – we’re working in the root\wmi namespace not PowerShell’s default of root\cimv2

Use WmiMonitorId to get the attached monitors and for each of them create a filter using the instance name. You have to replace \ with \\ when dealing with WMI.

Use WmiMonitorListedSupportedSourceModes and expand MonitorSourceModes. use sort-object to find the maximum resolution (multiply horizontal by vertical)

Create an output object. I had to deal with the name of the monitor separately because one of my monitors didn’t have a user friendly name.

Results look like this

Manufacturer Name    Serial       MaximumResolution 
 ------------ ----    ------       ----------------- 
 GSM          22EA63  304NDJX51788 1920 x 1080      
 LEN          Unknown 0            1600 x 900

Use CIM cmdlets not WMI cmdlets

WMI and CIM seem to cause a LOT of confusion. Its really simple. CIM is an industry standard from DMTF.org. WMI was Microsoft’s implementation of CIM way back in Windows NT days. The complication is that Microsoft had a set of WMI cmdlets in PowerShell v2. In PowerShell v3 they introduced a set of CIM cmdlets that work differently. The bottom line is you should use CIM cmdlets not WMI cmdlets.

Here’s three reasons  why you should use CIM cmdlets not WMI cmdlets.

 

Reason 1: the WMI cmdlets are deprecated and won’t have much if any further work done on them. The WMI help files tell you to use the CIM cmdlets! PowerShell v6 doesn’t have the WMI cmdlets – just the CIM cmdlets.

 

Reason 2: the CIM cmdlets use WS-MAN for remote access. The WMI cmdlets use DCOM which is blocked by default on Windows firewall. Using the CIM cmdlets makes your remoting easier.

 

Reason 3: The CIM cmdlets do some extra work for you. For instance finding the time a machine was started:

PS> Get-WmiObject -Class Win32_OperatingSystem | select LastBootUpTime

LastBootUpTime
--------------
20171001113546.966894+060

 

Read it left to right = Year = 2017; Month = 10; day = 01; hour = 11; minute = 35; second = 46.966894 with a +60 minute offset for daylight saving time.

That date format is painful to deal with so you convert it. the nice PowerShell Team added a conversion method for you

PS> Get-WmiObject -Class Win32_OperatingSystem | select @{N='LastBootUpTime'; E={$_.ConvertToDateTime($_.LastBootUpTime) }}

LastBootUpTime
--------------
01/10/2017 11:35:46

 

It works great but means you have to do more work.

By contrast the CIM cmdlets do the work for you

PS> Get-CimInstance -ClassName Win32_OperatingSystem | select LastBootUpTime

LastBootUpTime
--------------
01/10/2017 11:35:46

 

On a slightly off topic point anyone thinking of attending the PowerShell Summit next April will be aware that we have an Iron Scripter event planned for the last day. IF access to WMI classes is required anyone using the WMI cmdlets instead of the CIM cmdlets will be marked down. You have been warned Smile

Examples of replacing WMI cmdlet with CIM cmdlet

Following my last post I was asked about these Examples of replacing WMI cmdlet with CIM cmdlet.

Example 1

gwmi win32_operatingsystem -computername $Computer -credential $creds,

$cs = New-CimSession -Credential $creds -ComputerName $computer
Get-CimInstance -ClassName Win32_operatingsystem -CimSession $cs

Example 2

get-wmiobject -query “SELECT * FROM Meta_Class WHERE __Class = ‘Win32_Process'” -namespace “root\cimv2” -computername $computer -credential $creds

$cs = New-CimSession -Credential $creds -ComputerName $computer
Get-CimInstance -Query “SELECT * FROM Meta_Class WHERE __Class = ‘Win32_Process'” -CimSession $cs

Example 3

Get-WmiObject -Namespace $namespace -Class SMS_fullcollectionmembership -ComputerName $SCCMServer -filter “Name = ‘$computer'” -credential $creds

$cs = New-CimSession -Credential $creds -ComputerName $SCCMServer
Get-CimInstance -Namespace $namespace -ClassName SMS_fullcollectionmembership ” -filter “Name = ‘$computer'” -CimSession $cs

CIM not WMI

I still see a lot of people using the WMI cmdlets – Get-WmiObject etc. You really should be using CIM nit WMI. In other words use Get-CimInstance rather than get-WmiObject etc etc.

Why do I say that?

Two main reasons.

Firstly, the WMI cmdlets are effectively deprecated. Any further development effort will be for the CIM cmdlets.

Secondly, and to my mind more important, is that the CIM cmdlets use WS-MAN for access to remote machines. If you have PowerShell remoting enabled you have access to the machine via a CIM session – either ephemeral using the cmdlet name or persistent using a CIM session.

The WMI cmdlets use DCOM for remoting which is blocked by default on the Windows firewall and most other firewalls and routers giving the RPC server is unavailable error.

The only time there is justification for using the WMI cmdlets is if you’re on a machine that has Powershell v2 installed and if that’s the case why haven’t you upgraded? If you can’t does that mean you’re running an application (usually Exchange or System Center) that doesn’t allow you to upgrade PowerShell.

Maybe  its time to perform that upgrade.

As another thought PowerShell v6 includes the CIM cmdlets but not the WMI cmdlets!

Change a computer’s description

The Win32_OperatingSystem class exposes the machines Description. This is how you can easily change a computer’s description.

PS> Get-CimInstance -ClassName Win32_OperatingSystem | select Description

Description 
-----------

PS> Get-CimInstance -ClassName Win32_OperatingSystem | Set-CimInstance -Property @{Description = 'Richards Laptop'} 
PS> Get-CimInstance -ClassName Win32_OperatingSystem | select Description

Description 
----------- 
Richards Laptop

You can see that the description is originally blank. Get the CimInstance of Win32_OperatingSystem and pipe it to Set-CimInstance. The property to change and its new value are held in the hash table that’s the value given to the –Property parameter. You can modify multiple properties at once – just add them as property-value pairs to the hash table

More diskinfo

Yesterday I showed how to get the disk, partition and logical disk information using CIM. Today I want to show more diskinfo techniques.

This time we’ll use the Storage module which was introduced with Windows 8. Underneath the covers it uses CIM – just different classes. The storage module doesn’t differentiate between volumes  and logical disks – it just uses volumes.

To start at the physical disk and get the partition and volumes:

$diskinfo = Get-Disk | foreach {

  $parts = Get-Partition -DiskNumber $psitem.DiskNumber | where DriveLetter

  $disk = $psitem

  foreach ($part in $parts) { 
    
    Get-Volume -Partition $part | 
    foreach { 
      $props = $null

      $props = [ordered]@{ 
        Disk = $disk.Number 
         Model = $disk.Model 
        Firmware = $disk.FirmwareVersion 
        SerialNUmber = $disk.SerialNumber 
        'DiskSize(GB)' = [math]::Round(($disk.AllocatedSize / 1GB ), 2) 
        Partitions = $disk.NumberOfPartitions 
        Partition = $part.PartitionNumber 
        BootPartition = $part.IsBoot 
        'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2) 
        VolumeBlockSize = $psitem.AllocationUnitSize 
        LDiskName = $psitem.DriveLetter 
        FileSystem = $psitem.FileSystem 
         LDiskSize =  [math]::Round(($psitem.Size / 1GB ), 2) 
        LDiskFree =  [math]::Round(($psitem.SizeRemaining / 1GB ), 2) 
       }

      New-Object -TypeName PSObject -Property $props 
    } 
  } 
 } 
 $diskinfo

And to go the other way

$diskinfo =  Get-Volume | 
 where {$_.DriveLetter -AND $_.DriveType -eq 'Fixed'} | 
foreach {

      $part = Get-Partition -DriveLetter $psitem.DriveLetter 
       
      $disk = Get-Disk -Partition $part

      $props = $null

      $props = [ordered]@{ 
        Disk = $disk.Number 
         Model = $disk.Model 
        Firmware = $disk.FirmwareVersion 
        SerialNUmber = $disk.SerialNumber 
        'DiskSize(GB)' = [math]::Round(($disk.AllocatedSize / 1GB ), 2) 
        Partitions = $disk.NumberOfPartitions 
        Partition = $part.PartitionNumber 
        BootPartition = $part.IsBoot 
        'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2) 
         VolumeBlockSize = $psitem.AllocationUnitSize 
        LDiskName = $psitem.DriveLetter 
         FileSystem = $psitem.FileSystem 
        LDiskSize =  [math]::Round(($psitem.Size / 1GB ), 2) 
        LDiskFree =  [math]::Round(($psitem.SizeRemaining / 1GB ), 2) 
      }

      New-Object -TypeName PSObject -Property $props 
 } 
 $diskinfo

The number of blocks doesn’t seem to be available – suppose you could calculate it – otherwise the information is the same as with the CIM classes you saw last time. Some of the property names are different.