Monthly Archive

Categories

PowerShell and WMI

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!

Linking disks, partitions and logical drives

A question of the forums was asking about discovering disk information. They were trying to pipe the output of Get-WmiObject into another Get-WmiObject. that won’t work. There is another way. On Windows machines physical drives are divided into 1 or more partitions which are each divided into one or more logical disks. Linking disks, partitions and logical drives is a relatively simple process.

You can start at the physical disk and work down to the logical disks or start at the logical disk and work back to the physical disk. Lets start with the logical disk.

$diskinfo = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" | 
foreach { 
  $props = $null 
  
  $part = Get-CimAssociatedInstance -InputObject $psitem -ResultClass Win32_DiskPartition 
  $disk = Get-CimAssociatedInstance -InputObject $part -ResultClassName Win32_DiskDrive 
  
  $props = [ordered]@{ 
     Disk = $disk.Index 
     Model = $disk.Model 
     Firmware = $disk.FirmwareRevision 
     SerialNUmber = $disk.SerialNumber 
     'DiskSize(GB)' = [math]::Round(($disk.Size / 1GB ), 2) 
     Partitions = $disk.Partitions 
     Partition = $part.index 
     BootPartition = $part.BootPartition 
     'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2) 
     Blocks = $part.NumberOfBlocks 
     BlockSize = $part.BlockSize 
     LDiskName = $psitem.Caption 
     FileSystem = $psitem.FileSystem 
      LDiskSize =  [math]::Round(($psitem.Size / 1GB ), 2) 
     LDiskFree =  [math]::Round(($psitem.FreeSpace / 1GB ), 2) 
  }

  New-Object -TypeName PSObject -Property $props

}

$diskinfo

Use Get-CimInstance to retrieve the instances of the Win32_LogicalDisk class. Use a filter for DriveType = 3 – which is local disks (as far as the server is concerned – they could be on a SAN or NAS).

Foreach of the disks get the associated partition and use that object to get the associated physical drive.

CIM (WMI) has the concept of associators and references.

A reference is a pointer showing you which instance is associated with another instance. For example:

PS> Get-CimInstance -ClassName Win32_LogicalDiskToPartition


Antecedent      : Win32_DiskPartition (DeviceID = "Disk #0, Partition #1") 
Dependent       : Win32_LogicalDisk (DeviceID = "C:") 
EndingAddress   : 511578663935 
StartingAddress : 368050176 
PSComputerName  :

Logical disk C: is associated with partition #1 on disk #0

If you want to actually get the associated class then you do this

PS> $ld = Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DeviceID = "C:"' 
 PS> Get-CimAssociatedInstance -InputObject $ld -ResultClass Win32_DiskPartition

Name             NumberOfBlocks       BootPartition        PrimaryPartition     Size                Index 
 ----             --------------       -------------        ----------------     ----                ----- 
 Disk #0, Part... 998458230            False                True                 511210613760        1

or

PS> Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DeviceID = "C:"' |   Get-CimAssociatedInstance -ResultClass Win32_DiskPartition

Name             NumberOfBlocks       BootPartition        PrimaryPartition     Size                Index 
 ----             --------------       -------------        ----------------     ----                ----- 
 Disk #0, Part... 998458230            False                True                 511210613760        1

Once you’ve go the partition and physical disk instances. Populate your output object and loop. Notice that the pipeline is output directly to the variable $diskinfo. You don’t need to build arrays – get the pipeline to do it for you.

Each logical disk gets an output like this

Disk              : 0 
 Model             : Samsung SSD 840 PRO Series 
 Firmware          : DXM06B0Q 
SerialNUmber      : S1AXNSAF329511V 
DiskSize(GB)      : 476.93 
 Partitions        : 3 
 Partition         : 1 
BootPartition     : False 
PartitionSize(GB) : 476.1 
 Blocks            : 998458230 
BlockSize         : 512 
LDiskName         : C: 
FileSystem        : NTFS 
LDiskSize         : 476.1 
LDiskFree         : 212.33

That’s working up the stack. What about working down. That’s a similar process:

$diskinfo = Get-CimInstance -ClassName Win32_DiskDrive | 
foreach { 
  $disk = $psitem 
  
  $parts = Get-CimAssociatedInstance -InputObject $psitem -ResultClass Win32_DiskPartition

  foreach ($part in $parts) { 
    
    Get-CimAssociatedInstance -InputObject $part -ResultClassName Win32_LogicalDisk | 
    foreach { 
       $props = $null

      $props = [ordered]@{ 
        Disk = $disk.Index 
        Model = $disk.Model 
        Firmware = $disk.FirmwareRevision 
        SerialNUmber = $disk.SerialNumber 
        'DiskSize(GB)' = [math]::Round(($disk.Size / 1GB ), 2) 
        Partitions = $disk.Partitions 
        Partition = $part.index 
        BootPartition = $part.BootPartition 
         'PartitionSize(GB)' = [math]::Round(($part.Size / 1GB ), 2) 
        Blocks = $part.NumberOfBlocks 
        BlockSize = $part.BlockSize 
        LDiskName = $psitem.Caption 
        FileSystem = $psitem.FileSystem 
        LDiskSize =  [math]::Round(($psitem.Size / 1GB ), 2) 
        LDiskFree =  [math]::Round(($psitem.FreeSpace / 1GB ), 2) 
      }

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

Start with getting the instances of Win32_Diskdrive. Foreach instance get the associated partitions - Win32_DiskPartition.

Iterate through the partitions and get the associated logical disk. Create your object and output.

NOTE: neither of these techniques will show the partitions that don’t contain logical drives so you won’t see the boot partition and other “hidden partitions” on modern Windows machines. if you need those look at Win32_DiskPartition directly.

Finding a CIM class

One of the problems you might find is finding a CIM class. You know its name but you don’t know which namespace its in.

The old WMI cmdlets allow you to search the namespaces recursively

PS> Get-WmiObject -Class Win32_Process -Namespace root -Recurse -List


   NameSpace: ROOT\CIMV2

Name                                Methods              Properties 
 ----                                -------              ---------- 
 Win32_Process                       {Create, Terminat... {Caption, CommandLine, CreationClassName, CreationDate...}

But the CIM cmdlets don’t have this functionality. I’ve been meaning to do something about this for ages but finally got motivated by something I read while proof reading PowerShell in Action – yes its getting closer, much closer.

What I ended up with is these 2 functions

function get-namespace { 
 [cmdletBinding()] 
param ([string]$namespace = 'root') 
  Get-CimInstance -Namespace $namespace -ClassName '__NAMESPACE' | 
  foreach { 
        "$namespace\" + $_.Name 
        get-namespace $("$namespace\" + $_.Name) 
  } 
 }

function find-cimclass { 
 [cmdletBinding()] 
param ( 
 [string]$namespace = 'root', 
 [string]$classname 
 )

$class = $null

## test namespace for class 
 $class = Get-CimClass -Namespace $namespace -ClassName $classname -ErrorAction SilentlyContinue

if (-not $class) { 
  $namespaces = get-namespace -namespace $namespace 
  foreach ($name in $namespaces){ 
    $class = $null 
    $class = Get-CimClass -Namespace $name -ClassName $classname -ErrorAction SilentlyContinue 
    if ($class){break} 
  } 
 }

$class 
 }

Find-Cimclass takes a namespace and class name as parameters. It tries to find the class in the given namespace. If it can’t find it then get-namespace is called to generate a list of namespaces to search. The function iterates over the collection of  namespaces testing each one for the class. When it finds the class it returns the class information.

Get-namespace  searches for all instances of the __Namespace class in the given namespace. it then recursively call itself to test each of those namespaces. That way you get the whole tree.

If you’re searching for a given class I recommend that you start at the root class to ensure that you test everywhere.

Find the logged on user

One method of finding the logged on users is to use CIM

$ComputerName = $env:COMPUTERNAME

Get-CimInstance -ClassName Win32_Process -ComputerName $ComputerName -Filter "Name = 'explorer.exe'" | 
foreach { 
 
 $lguser = Invoke-CimMethod -InputObject $psitem -MethodName GetOwner 
 
 $Properties = @{ 
 ComputerName = $ComputerName 
 User = $lguser.User 
 Domain = $lguser.Domain 
 Time = $User.CreationDate 
 } 
 
 New-Object -TypeName PSObject -Property $Properties 
 }

Get the Win32_Process instances for explorer.exe and foreach of them use the GetOwner method to get the owners names and domain. Create an object and ouput

 

wmic deprecated

I saw a forum post today where the question involved the use of the wmi command line tool wmic.

 

Wmic was deprecated in Windows Server 2012 - https://technet.microsoft.com/en-us/library/hh831568(v=ws.11).aspx. It will eventually be removed.

 

You should use the CIM cmdlets instead of wmic. The syntax is much easier and the resultant code is easier to understand.

 

A little known fact – the PowerShell project was originally started as a replacement for wmic.

Applying updates through WSUS

I like to keep the virtual machines in my test lab up to date so have a WSUS server to download and manage updates. The difficulty is applying the updates. With Windows 2012 R2 I used a module that would contact the WSUS server and apply the updates – the was especially useful on server core installations.

I found with Windows 2016 that this COM based module wasn’t reliable so after a bit of investigation discovered that there are some CIM classes that you can use to discover and apply available updates and see what updates have been applied.

 

All I need is a simple set of code so wrote a bare bones module that offers three functions:

#Scan for available updates
function Get-AvailableUpdate {
[CmdletBinding()]
param()
$ci = New-CimInstance -Namespace root/Microsoft/Windows/WindowsUpdate -ClassName MSFT_WUOperationsSession
$result = $ci | Invoke-CimMethod -MethodName ScanForUpdates -Arguments @{SearchCriteria="IsInstalled=0";OnlineScan=$true}
$result.Updates
}

#Install all available updates
function Install-AvailableUpdate {
[CmdletBinding()]
param()
$ci = New-CimInstance -Namespace root/Microsoft/Windows/WindowsUpdate -ClassName MSFT_WUOperationsSession
Invoke-CimMethod -InputObject $ci -MethodName ApplyApplicableUpdates
}

#list installed updates
function Get-InstalledUpdate {
[CmdletBinding()]
param()
$ci = New-CimInstance -Namespace root/Microsoft/Windows/WindowsUpdate -ClassName MSFT_WUOperationsSession
$result = $ci | Invoke-CimMethod -MethodName ScanForUpdates -Arguments @{SearchCriteria="IsInstalled=1";OnlineScan=$true}
$result.Updates
}

 

Testing so far seems to be good. As this is just for me I’m bothering with adding error testing or other production ready stuff. This works and I’ll fix problems as they occur

ComputerName parameters for CIM and WMI cmdlets

Accessing a remote system and running

Get-WmiObject -ClassName Win32_LogicalDisk -ComputerName $computer

or

Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $computer

is a standard approach.

 

If you’re creating a function with that code in you may put the local machine as a default parameter:

$computer = $env:COMPUTERNAME

 

Running Get-WmiObject locally will work quite happily because you’re using COM to access the local machine.

Get-CimInstance may well fail with this error

PS> Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $computer
Get-CimInstance : The client cannot connect to the destination specified in the request. Verify that the service on the destination is running and is accepting requests. Consult the logs anddocumentation for the WS-Management service running on the destination, most commonly IIS or WinRM.
If the destination is the WinRM service, run the following command on the destination to analyze and configure the WinRM service: "winrm quickconfig".
At line:1 char:1
+ Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $computer
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ConnectionError: (root\cimv2:Win32_LogicalDisk:String) [Get-CimInstanc
   e], CimException
    + FullyQualifiedErrorId : HRESULT 0x80338012,Microsoft.Management.Infrastructure.CimCmdlets.GetC
   imInstanceCommand
    + PSComputerName        : RSSURFACEPRO2

 

The CIM cmdlets use WSMAN to connect to remote machines. This is triggered by using the –ComputerName parameter. The error means you haven’t got the winrm service running on the local machine. On modern Windows remoting, and therefore winrm, are enable by default for servers but disable for client OS e.g. Windows 10.

 

Easiest way to get this to work is run Enable-PSremoting from and elevated prompt.

Working with multiple CIM objects

Many of the CIM objects we work with in our computers come in multiple instances – disks and network cards are a couple of examples. Many times when you see examples you’ll see something like this:

$disks = Get-WmiObject -Class Win32_LogicalDisk

foreach ($disk in $disks){
  if ($disk.Size -gt 0) {
    $disk | select DeviceId,
    @{N='Free'; E={[math]::Round($disk.FreeSpace/1GB, 2)}},
    @{N='Size'; E={[math]::Round($disk.Size/1GB, 2)}},
    @{N='PercUsed'; E={[math]::Round((($disk.Size - $disk.FreeSpace) / $disk.Size) * 100, 2)}}
  }
}

 

Get a collection of objects. Iterate through them with foreach and do something.

 

You can, and should, do this as a pipeline operation. The code above is really a hangover from VBScript coding.

Converting the above to a pipeline gives

Get-WmiObject -Class Win32_LogicalDisk |
where Size -gt 0 |
foreach {
    $_ | select DeviceId,
    @{N='Free'; E={[math]::Round($_.FreeSpace/1GB, 2)}},
    @{N='Size'; E={[math]::Round($_.Size/1GB, 2)}},
    @{N='PercUsed'; E={[math]::Round((($_.Size - $_.FreeSpace) / $_.Size) * 100, 2)}}
}

 

NOTE – before anyone starts complaining yes I know you can use a  filter on Get-WmiObject I’m explaining the principle! Also, I know that you could go straight into the select on the pipeline but if you want to add extra processing e.g. send an email if the disk is more than 80% used you need the foreach

 

You can do similar things with NICs for example

Get-WmiObject -ClassName Win32_PerfFormattedData_Tcpip_NetworkInterface |
where CurrentBandwidth -gt 0 |
foreach {
  
   $props = [ordered]@{
     Name = $psitem.Name
     Computer = $psitem.PSComputerName
     PercUtilisation = (($psitem.BytesTotalPersec * 8) / $psitem.CurrentBandWidth) * 100
   }
  
   New-Object -TypeName PSObject -Property $props

} |
where PercUtilisation -gt 0.5 |
foreach {
   $text =  @"
   $($_.Name) on $($_.Computer)
        
   Bandwidth utilized:  $($_.PercUtilisation) %
"@
 
$text
 
}

 

Rather than displaying $text send an email if the utilisation is too big. 

 

where PercUtilisation -gt 0.5

is used so I actually get to see results. You probably want 60% or more on your production machines

Server Uptime

Its easy to get the last boot time of a Windows machine but how do you get the uptime

 

function Get-Uptime {
[CmdletBinding()]
param (
   [string]$ComputerName = $env:COMPUTERNAME
)

$os = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $ComputerName

$uptime = (Get-Date) - $os.LastBootUpTime

$uptime

}

 

Use Get-CimInstance to get the Win32_OperatingSystem class. To calculate the uptime subtract the value of LastBootTime from the current time and date.

You’ll get a Timespan object returned.

PS> Get-Uptime

Days              : 1
Hours             : 10
Minutes           : 32
Seconds           : 26
Milliseconds      : 838
Ticks             : 1243468385381
TotalDays         : 1.4391995201169
TotalHours        : 34.5407884828056
TotalMinutes      : 2072.44730896833
TotalSeconds      : 124346.8385381
TotalMilliseconds : 124346838.5381

 

Pick out whichever properties you need for your report