Monthly Archive

Categories

CIM

IIS information

In my recent post about getting server information from a IIS web server I said I post about getting similar information from later machines.

 

You still have the root\MirosoftIISv2 namespace available if you install the IIS 6.0 tools but one question to keep in mind – how long will they continue to be available?

 

Your alternative is the root\webadministration names space. You can use the Site class to get the relevant information

 

$serverdata = @()
Get-CimInstance -Namespace root\webadministration -ClassName Site -ComputerName $env:COMPUTERNAME |
foreach {

$serverdata += New-Object -TypeName PSObject -Property @{
Port = [string]::Join(',', ($_.Bindings | select -ExpandProperty BindingInformation))
SiteName = $_.Name
SiteId = $_.id
PSComputerName = $_.PSComputerName
Status = Invoke-CimMethod -InputObject $_ -MethodName GetState | select -ExpandProperty ReturnValue
}

}
$serverdata

 

Remember that COM objects are inert so you can’t call the method directly on the object. otherwise the info is about the same

CDXML filter parameters

I was recently asked about adding a filter parameter to a cmdlet created through CDXML. If you’ve not seen it before (see PowerShell and WMI Chapters 18 & 19 from www.manning.com) CDXML allows you to creat ecmdlets by wrapping a WMI class in some simple XML.

 

The resultaing CDXML (Cmdlet Definition XML) is thn published as a module.  Here’s a simple example using the Win32_NetworkAdapterConfiguration class

<?xml version='1.0' encoding='utf-8'?>
<PowerShellMetadata xmlns='http://schemas.microsoft.com/cmdlets-over-objects/2009/11'>
  <Class ClassName='ROOT\cimv2\Win32_NetworkAdapterConfiguration'>
    <Version>1.0</Version>
    <DefaultNoun>NetworkAdapterConfiguration</DefaultNoun>
    <InstanceCmdlets>
      <GetCmdletParameters DefaultCmdletParameterSet='DefaultSet'>
      </GetCmdletParameters>
    </InstanceCmdlets> 
  </Class>
</PowerShellMetadata>

 

The first 2 lines are boilerplate. The NameSpace and WMI class are defined on line 3, follwoed by a version number (arbitary) and a default noun for you cmdlet to use.  Instance cmdlets defines how you’ll pull the data for existing instances of the class – in other words the Get-NetworkAdapterConfiguration cmdlet.

 

Save as a CDXML file and import as a module

Import-Module .\NetworkAdapterConfiguration.cdxml

 

Get-Module will sjow it as a Cim module with a single exported command.   Use it like any other cmdlet

PS> Get-NetworkAdapterConfiguration | ft -a

ServiceName  DHCPEnabled Index Description
-----------  ----------- ----- -----------
kdnic        True        0     Microsoft Kernel Debug Network Adapter
mwlu97w8     True        1     Marvell AVASTAR Wireless Composite Device
msu64w8      False       2     Surface Ethernet Adapter
mwlu97w8     True        3     Marvell AVASTAR 350N Wireless Network Controller
RFCOMM       False       4     Bluetooth Device (RFCOMM Protocol TDI)
BthPan       True        5     Bluetooth Device (Personal Area Network)
vwifimp      True        6     Microsoft Wi-Fi Direct Virtual Adapter
vwifimp      True        7     Microsoft Wi-Fi Direct Virtual Adapter
RasSstp      False       8     WAN Miniport (SSTP)
RasAgileVpn  False       9     WAN Miniport (IKEv2)
Rasl2tp      False       10    WAN Miniport (L2TP)
PptpMiniport False       11    WAN Miniport (PPTP)
RasPppoe     False       12    WAN Miniport (PPPOE)
NdisWan      False       13    WAN Miniport (IP)
NdisWan      False       14    WAN Miniport (IPv6)
NdisWan      False       15    WAN Miniport (Network Monitor)

 

Using the cmdlet is equivalent to

Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration

 

but is easier and requires less typing.

Very often you’ll want to pick a specific adapter – for instance

 

Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter 'Index=3'

 

You can implement the same kind of filters using CDXML. You add a queryable properties section as shown below:

<?xml version='1.0' encoding='utf-8'?>
<PowerShellMetadata xmlns='http://schemas.microsoft.com/cmdlets-over-objects/2009/11'>
  <Class ClassName='ROOT\cimv2\Win32_NetworkAdapterConfiguration'>
    <Version>1.0</Version>
    <DefaultNoun>NetworkAdapterConfiguration</DefaultNoun>
    <InstanceCmdlets>
      <GetCmdletParameters DefaultCmdletParameterSet='DefaultSet'>

        <QueryableProperties>
          <Property PropertyName='Index'>
            <Type PSType ='UInt32'/>
            <RegularQuery AllowGlobbing='true'>
              <CmdletParameterMetadata PSName='Index'  ValueFromPipelineByPropertyName='true' CmdletParameterSets='DefaultSet' />
            </RegularQuery>
          </Property>
        </QueryableProperties>       
             
      </GetCmdletParameters>
    </InstanceCmdlets> 
  </Class>
</PowerShellMetadata>

 

Set the paraemter name – same as property to use here – and the type (unsigned integer). Decide whether pipeline input and wildcards (globbing) are allowed and save the file.

 

Re-import the module (use the Force) and your new parameter is available

Get-NetworkAdapterConfiguration -Index 3

 

Its important to understand CDXMLMI – even if you never create a CDXML module – because 2/3 of the cmdlets in Windows Server 2012 and later are created this way.

BinaryMiLog cmdlets

It’s not often I come across soemthing brand new in PowerShell but yesterday when I was investigating New-CimInstance I discovered 2 cmdlets in the CimCmdlets module I hadn’t noticed before. These are:

Export-BinaryMiLog

Import-BinaryMiLog

 

The cmdlets are used to export, or import, CIM instances as a binary encoded file.  Think of them as  Export-Clixml and Import-Clixml but for CIM instances.

Their usage is very simple:

Get-CimInstance -ClassName Win32_OperatingSystem |
Export-BinaryMiLog -Path testfile.bmil

 

This creates a   30 KB binary file – its definitely not human readable!

You don’t need to use a bmil extension (its the one in the help file) and you can use a CIM instance object instead of the pipeline

$os = Get-CimInstance -ClassName Win32_OperatingSystem
Export-BinaryMiLog -InputObject $os -Path testfile2.stuff

 

Getting the data back is performed by Import-BinaryMiLog

$os1 = Import-BinaryMiLog -Path .\testfile.bmil
$os2 = Import-BinaryMiLog -Path .\testfile2.stuff

 

The results appear to be a standard CIM object

Compare-Object -ReferenceObject $os -DifferenceObject $os1 -IncludeEqual
Compare-Object -ReferenceObject $os -DifferenceObject $os2 -IncludeEqual
Compare-Object -ReferenceObject $os1 -DifferenceObject $os2 –IncludeEqual

 

These cmdlets give you way to persist CIM objects to disk so that they can be referenced at a later date. If you need to test for changes to a system this could be a useful technique

New-CimInstance cmdlet and the–Key parameter

I was asked a question about the –Key parameter on New-CimInstance today. I wasn’t sure of the answer so I’ve done some experimentation.

 

I tend to avoid New-CimInstance if I can preferring to use the Create method on the CIM class – however not all CIM classes have a create method so need to fall back on New-CimInstance.

 

I started by looking at the documentation. The help file for New-CimInstance says:

-Key<String[]>

Specifies the properties that are used as keys. CimSession and ComputerName cannot be used when Key is specified.

 

That then leads to the question how do I discover the Key or Keys of a CIM class.  You can’t use the –Qualifier parameter in Get-CimClass because that works at the class level and Key is a property qualifier.  Means you need to use some code

function Get-CimClassKey { 

    param (
   
        [string]$CIMnamespace = 'ROOT/cimv2',
       
        [string]$CIMclass
   
    )

    $class = Get-CimClass -Namespace $CIMnamespace -ClassName $CIMclass

    foreach ($property in $class.CimClassProperties) {
 
        $property | Select-Object -ExpandProperty Qualifiers |
        foreach {
            if ($_.Name -eq 'key'){
                $property
            }
        }

    }
}

 

The Key property of a class HAS to be given a value when a new instance of the class is created.

 

The New-CimInstance help file shows an example using Win32_Environment.  Adapring the example:

PS> New-CimInstance -ClassName Win32_Environment @{Name='RStest1'; VariableValue='test1'; UserName='RSSURFACEPRO2\Richard'}

Name             UserName                                            VariableValue
----             --------                                            -------------
RStest1          RSSURFACEPRO2\Richard                               test1

Using our function to discover the Keys of Win32_Environment

PS> Get-CimClassKey -CIMclass Win32_Environment

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

Name               : UserName
Value              :
CimType            : String
Flags              : Property, Key, ReadOnly, NullValue
Qualifiers         : {key, MappingStrings, MaxLen, read}
ReferenceClassName :

 

Adding the –Key parameter

PS> New-CimInstance -ClassName Win32_Environment @{Name='RStest2'; VariableValue='test2'; UserName='RSSURFACEPRO2\Richard'} -Key 'Name', 'UserName'

Name             UserName                                            VariableValue
----             --------                                            -------------
RStest2          RSSURFACEPRO2\Richard                               test2

 

Using Win32_Environment you can use the Key parameter, or not, as long as you define values for the Name and Username properties.

Another example in the New-CimInstance help file uses the Win32_Process class.  The key for that class is the Handle property

PS> Get-CimClassKey -CIMclass Win32_process

Name               : Handle
Value              :
CimType            : String
Flags              : Property, Key, ReadOnly, NullValue
Qualifiers         : {key, MaxLen, read}
ReferenceClassName :

 

the Handle is appears to be identical to the ProcessId in value as far as I can determine

 

This now gets  messy:

 

Just the Handle.  BTW exmple 3 in the documentation has an error as Handle is a string not an integer

PS> New-CimInstance -ClassName Win32_Process -Property @{Handle='0'}
New-CimInstance : Provider is not capable of the attempted operation
At line:1 char:1
+ New-CimInstance -ClassName Win32_Process -Property @{Handle='0'}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Win32_Process:CimInstance) [New-CimInstance], CimException
    + FullyQualifiedErrorId : HRESULT 0x80041024,Microsoft.Management.Infrastructure.CimCmdlets.NewCimInstanceCommand

 

Add the Key parameter

PS> New-CimInstance -ClassName Win32_Process -Property @{Handle='0'} -Key Handle
New-CimInstance : Provider is not capable of the attempted operation
At line:1 char:1
+ New-CimInstance -ClassName Win32_Process -Property @{Handle='0'} -Key ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Win32_Process (Handle = "0"):CimInstance) [New-CimInstance], CimException
    + FullyQualifiedErrorId : HRESULT 0x80041024,Microsoft.Management.Infrastructure.CimCmdlets.NewCimInstanceCommand

 

The only way it works is if you use –ClientOnly to make an in memory CIM instance that only exists in your PowerShell session

PS> New-CimInstance -ClassName Win32_Process -Property @{Handle='0'} -Key Handle -ClientOnly

Handle PSComputerName
------ --------------
0

 

You can remove the –Key parameter

PS> New-CimInstance -ClassName Win32_Process -Property @{Handle='0'} -ClientOnly

Handle PSComputerName
------ --------------
0

 

Win32_Process has a Create method that takes these parameters

PS> $class.CimClassMethods['Create'].Parameters

Name                       CimType Qualifiers                                 ReferenceClassName
----                                 ------- ----------                                 ------------------
CommandLine                 String {ID, In, MappingStrings}
CurrentDirectory            String {ID, In, MappingStrings}
ProcessStartupInformation Instance {EmbeddedInstance, ID, In, MappingStrings}
ProcessId                   UInt32 {ID, MappingStrings, Out}

 

Using Invoke-CimMethod

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

ProcessId ReturnValue PSComputerName
--------- ----------- --------------
     2648           0

 

Now trying New-CimInstance

PS> New-CimInstance -ClassName Win32_Process -Property @{Handle='0'; CommandLine='notepad.exe'} -Key Handle
New-CimInstance : Provider is not capable of the attempted operation
At line:1 char:1
+ New-CimInstance -ClassName Win32_Process -Property @{Handle='0'; Comm ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Win32_Process (Handle = "0"):CimInstance) [New-CimInstance], CimException
    + FullyQualifiedErrorId : HRESULT 0x80041024,Microsoft.Management.Infrastructure.CimCmdlets.NewCimInstanceCommand

 

Other variants of not including the Handle property and changing the handle value all fail with same error

 

Botton line is that New-CimInstance is a bit of a mess to use – with or without the –Key parameter (which doesn’t seem to do much).

 

If the CIM class hasa create method Id recommend that you use that as a lot of CIm classes (or their providers) don’t work with New-cimInstance. In reality given that many of the CIM classes are effectively read only – you can’t create a new instance of Win32_ComputerSystem for example – it probably doesn’t matter.

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 = {
    param($filter)
    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 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{
    [cmdletbinding()]
    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
    }
    $networkinfo
}
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"

OR

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

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

 

or

 

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