Archive for June, 2013

I know I’ve been quiet on here for the last several weeks, but I am actually working on some posts. Today I wanted to mention an obscure problem that I cam across the other day.

So, what was my problem? Well, I’m planning on releasing a group of functions that I’ve been working on that will allow you to work with almost any type of security descriptors (SDs) that you can get your hands on (SDs for files, folders, registry keys, printers, services, shares, WMI namespaces, PS configuration files, etc). I’m going to post it on the TechNet Script Repository, and I’ll have a series of blog posts detailing how to use each function, and how to work with some of the SDs.

I mention all of that to setup the problem: the function that gets SDs using WMI/CIM needed a way to uniquely represent the instance that the SD belongs to. If you call Get-Acl on a file, you’ll notice that the object returned contains a path property along with the security descriptor info. If you get SDs for several files at once, then try to get a list of all of the access control entries (ACEs) contained on all of them, the path helps you keep track of what object each ACE belongs to. If you use WMI to get the SDs for printers, network shares, etc from multiple computers at once, it helps to have an identifier to attach to each of the objects returned, just like Get-Acl does with files. I’ll go into this in a lot more detail in a future blog post.

If you call Get-WmiObject, the perfect unique identifier is located in the __PATH property on each object returned. As far as I can tell, the Get-CimInstance cmdlet doesn’t give this information (more specifically, the [Microsoft.Management.Infrastructure.CimInstance] object returned by the cmdlet doesn’t have this information available). It turns out that it has a place holder for the information in the ‘CimSystemProperties’ collection that each instance contains, but that property isn’t currently implemented.

Jeff Hicks presented at the Mississippi PowerShell User Group this past Tuesday, and, after his awesome presentation, I mentioned the issue to him and showed him how I was getting around it. He has a really good blog post working through the issue, and he created a really good helper function to add the __PATH property to a CimInstance object. I recommend taking a look at it before continuing.

After I came up with a way to get the WMI path information from a CimInstance, I did think of one other alternate use for it: you can convert a CimInstance into a ManagementObject (that’s what Get-WmiObject returns). I’m not exactly sure why you’d want to do it, but you can :). Here’s my function that gets the path information:

#requires -version 3.0
function Get-CimPathFromInstance {

    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ValueFromPipeline)]
        [ciminstance] $InputObject
    )

    process {
        $Keys = $InputObject.CimClass.CimClassProperties | 
            Where-Object { $_.Qualifiers.Name -contains "Key" } |
            Select-Object Name, CimType | 
            Sort-Object Name

        $KeyValuePairs = $Keys | ForEach-Object { 

            $KeyName = $_.Name
            switch -regex ($_.CimType) {

                "Boolean|.Int\d+" {
                    # No quotes surrounding value:
                    $Value = $InputObject.$KeyName
                }

                "DateTime" {
                    # Conver to WMI datetime
                    $Value = '"{0}"' -f [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($InputObject.$KeyName)
                }

                "Reference" {
                    throw "CimInstance contains a key with type 'Reference'. This isn't currenlty supported (but can be added later)"
                }

                default {
                    # Treat it like a string and cross your fingers:
                    $Value = '"{0}"'  -f ($InputObject.$KeyName -replace "`"", "\`"")
                }
            }

            "{0}={1}" -f $KeyName, $Value
        }

        if ($KeyValuePairs) { 
            $KeyValuePairsString = ".{0}" -f ($KeyValuePairs -join ",")
        }
        else {
            # This is how WMI seems to handle paths with no keys
            $KeyValuePairsString = "=@" 
        }

        "\\{0}\{1}:{2}{3}" -f $InputObject.CimSystemProperties.ServerName, 
                               ($InputObject.CimSystemProperties.Namespace -replace "/","\"), 
                               $InputObject.CimSystemProperties.ClassName, 
                               $KeyValuePairsString


    }
}

And here’s how you can convert a CimInstance into a ManagementObject:


PS> Get-CimInstance -Query "SELECT * FROM Win32_PingStatus WHERE Address='127.0.0.1'" | 
>> Get-CimPathFromInstance | Tee-Object -Variable Path

\\client01\root\cimv2:Win32_PingStatus.Address="127.0.0.1",BufferSize=32,NoFragmentation=False,
RecordRoute=0,ResolveAddressNames=False,SourceRoute="",SourceRouteType=0,Timeout=4000,Timestamp
Route=0,TimeToLive=128,TypeofService=0

PS> [wmi] $Path

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms) 
------        -----------     -----------      -----------                              -----    -------- 
client01      127.0.0.1       192.168.10.15    fe80::16a:e187:f957:f77c%13              32       0        

If you’ve got a way to convert it one way, you need a way to convert it the other way, too:

#requires -version 3.0
function Get-CimInstanceFromPath {

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
        [Alias('__PATH')]
        [string] $Path
    )

    process {
        if ($Path -match "^\\\\(?<computername>[^\\]*)\\(?<namespace>[^:]*):(?<classname>[^=\.]*)(?<separator>\.|(=@))(?<keyvaluepairs>.*)?$") {
            $Query = "SELECT * FROM {0}" -f $matches.classname

            switch ($matches.separator) {

                "." {
                    # Key/value pairs are in string, so add a WHERE clause
                    $Query += " WHERE {0}" -f [string]::Join(" AND ", $matches.keyvaluepairs -split ",")
                }
            }

            $GcimParams = @{
                ComputerName = $matches.computername
                Query = $Query
                ErrorAction = "Stop"
            }

        }
        else {
            throw "Path not in expected format!"
        }

        Get-CimInstance @GcimParams
    }

}

PS> $WmiBios = Get-WmiObject Win32_BIOS

PS> $WmiBios.__PATH | Get-CimInstanceFromPath | Tee-Object -Variable CimBios

SMBIOSBIOSVersion : xxx
Manufacturer      : Dell Inc.
Name              : Default System BIOS
SerialNumber      : xxxxxxx
Version           : DELL   - 1
PSComputerName    : client01

PS> $CimBios.GetType().FullName
Microsoft.Management.Infrastructure.CimInstance


PS> # You can also just pipe a ManagementObject directly to it
PS> Get-WmiObject Win32_BIOS | Get-CimInstanceFromPath

SMBIOSBIOSVersion : xxx
Manufacturer      : Dell Inc.
Name              : Default System BIOS
SerialNumber      : xxxxxxx
Version           : DELL   - 1
PSComputerName    : client01

Again, I’m not sure why you’d want to do it, but I realized you could, so I wanted to put it out there. If for some reason this is of any value to you, just be warned that there’s a good chance you’ll find instances where you get an error casting the CIM path string into a [wmi] ManagementObject. This is because there’s a good chance that the path isn’t always properly formed.

If you do actually have a use for it, please leave a comment to let me know.

Advertisements