In the first part of this series, we discussed what WMI namespace security was and why it is something you would want to change.   For this blog post, I’ll show a Powershell script for retrieving the current security descriptor of a WMI namespace.  Note that everything I’m doing in the Powershell script can be done in vbscript, but I’ll leave that as an exercise to the reader.  Here’s the entire script.  I’ll discuss the various sections to explain why I did something.  Note that this is not intended to be a discussion about Powershell, so I’m focusing on the WMI specific parts.

# Copyright (c) Microsoft Corporation.  All rights reserved. 

# For personal use only.  Provided AS IS and WITH ALL FAULTS. 

 

# Get-WmiNamespaceSecurity.ps1

# Example: Get-WmiNamespaceSecurity root/cimv2

 

Param ( [parameter(Mandatory=$true,Position=0)][string] $namespace,

    [string] $computer = ".",

    [System.Management.Automation.PSCredential] $credential = $null)

 

Process {

    $ErrorActionPreference = "Stop"

 

    Function Get-PermissionFromAccessMask($accessMask) {

        $WBEM_ENABLE            = 1

        $WBEM_METHOD_EXECUTE         = 2

        $WBEM_FULL_WRITE_REP           = 4

        $WBEM_PARTIAL_WRITE_REP     = 8

        $WBEM_WRITE_PROVIDER          = 0x10

        $WBEM_REMOTE_ACCESS            = 0x20

        $READ_CONTROL = 0x20000

        $WRITE_DAC = 0x40000

       

        $WBEM_RIGHTS_FLAGS = $WBEM_ENABLE,$WBEM_METHOD_EXECUTE,$WBEM_FULL_WRITE_REP,`

            $WBEM_PARTIAL_WRITE_REP,$WBEM_WRITE_PROVIDER,$WBEM_REMOTE_ACCESS,`

            $WBEM_RIGHT_SUBSCRIBE,$WBEM_RIGHT_PUBLISH,$READ_CONTROL,$WRITE_DAC

        $WBEM_RIGHTS_STRINGS = "Enable","MethodExecute","FullWrite","PartialWrite",`

            "ProviderWrite","RemoteAccess","Subscribe","Publish","ReadSecurity","WriteSecurity"

 

        $permission = @()

        for ($i = 0; $i -lt $WBEM_RIGHTS_FLAGS.Length; $i++) {

            if (($accessMask -band $WBEM_RIGHTS_FLAGS[$i]) -gt 0) {

                $permission += $WBEM_RIGHTS_STRINGS[$i]

            }

        }

       

        $permission

    }

 

    $INHERITED_ACE_FLAG = 0x10

 

    $invokeparams = @{Namespace=$namespace;Path="__systemsecurity=@";Name="GetSecurityDescriptor";ComputerName=$computer}

 

    if ($credential -eq $null) {

        $credparams = @{}

    } else {

        $credparams = @{Credential=$credential}

    }

 

    $output = Invoke-WmiMethod @invokeparams @credparams

    if ($output.ReturnValue -ne 0) {

        throw "GetSecurityDescriptor failed: $($output.ReturnValue)"

    }

   

    $acl = $output.Descriptor

    foreach ($ace in $acl.DACL) {

        $user = New-Object System.Management.Automation.PSObject

        $user | Add-Member -MemberType NoteProperty -Name "Name" `

            -Value "$($ace.Trustee.Domain)\$($ace.Trustee.Name)"

        $user | Add-Member -MemberType NoteProperty -Name "Permission" `

            -Value (Get-PermissionFromAccessMask($ace.AccessMask))

        $user | Add-Member -MemberType NoteProperty -Name "Inherited" `

            -Value (($ace.AceFlags -band $INHERITED_ACE_FLAG) -gt 0)

        $user

    }

}

 

Internally, all OS Security Descriptors, ACLs, ACEs, etc… are just represented by numbers.  I would not expect that an admin would remember that the Remote Enable permission has the numerical value of 0x20 (decimal 32).  So the internal function Get-PermssionFromAccessMask was created to translate the “magic numbers” into human readable form.

Here I store all the numerical values into descriptive variables.  If you’re wondering where I got these values from, they are from wbemcli.h and winnt.h (for the last two related to permissions for reading and writing to the ACL).

    Function Get-PermissionFromAccessMask($accessMask) {

        $WBEM_ENABLE            = 1

        $WBEM_METHOD_EXECUTE         = 2

        $WBEM_FULL_WRITE_REP           = 4

        $WBEM_PARTIAL_WRITE_REP     = 8

        $WBEM_WRITE_PROVIDER          = 0x10

        $WBEM_REMOTE_ACCESS            = 0x20

        $READ_CONTROL = 0x20000

        $WRITE_DAC = 0x40000    

 

Next, I store the values and text representations in arrays.  Each element maps to the corresponding index of the other.  I can then simply see which permissions are included in the access mask and create a new array that contains the text of the permission.

        $WBEM_RIGHTS_FLAGS = $WBEM_ENABLE,$WBEM_METHOD_EXECUTE,$WBEM_FULL_WRITE_REP,`

            $WBEM_PARTIAL_WRITE_REP,$WBEM_WRITE_PROVIDER,$WBEM_REMOTE_ACCESS,`

            $READ_CONTROL,$WRITE_DAC

        $WBEM_RIGHTS_STRINGS = "Enable","MethodExecute","FullWrite","PartialWrite",`

            "ProviderWrite","RemoteAccess","ReadSecurity","WriteSecurity"

 

        $permission = @()

        for ($i = 0; $i -lt $WBEM_RIGHTS_FLAGS.Length; $i++) {

            if (($accessMask -band $WBEM_RIGHTS_FLAGS[$i]) -gt 0) {

                $permission += $WBEM_RIGHTS_STRINGS[$i]

            }

        }

 

The next section takes advantage of Powershell 2.0 splatting so I can store commonly used parameters in a single variable.  In this script, I don’t reuse the parameters, but I tend to make it a habit.  If credentials were not specified I don’t want the credential prompt to come up since this script can be used locally or remotely. 

    $invokeparams = @{Namespace=$namespace;Path="__systemsecurity=@";Name="GetSecurityDescriptor";ComputerName=$computer}

 

    if ($credential -eq $null) {

        $credparams = @{}

    } else {

        $credparams = @{Credential=$credential}

    }

 

Finally, I want to output the result as an object, not simply as text so that it can be piped to another cmdlet for further processing.  So I do the actual call to __SystemSecurity::GetSecurityDescriptor() and get back a Win32_SecurityDescriptor (checking the ReturnValue first).  I walk through each ACE (Access Control Entry) and store the username, permission set, and whether it’s inherited into my new object, then I send that object to the pipeline.

    $output = Invoke-WmiMethod @invokeparams @credparams

    if ($output.ReturnValue -ne 0) {

        throw "GetSecurityDescriptor failed: $($output.ReturnValue)"

    }

   

    $acl = $output.Descriptor

    foreach ($ace in $acl.DACL) {

        $user = New-Object System.Management.Automation.PSObject

        $user | Add-Member -MemberType NoteProperty -Name "Name" `

            -Value "$($ace.Trustee.Domain)\$($ace.Trustee.Name)"

        $user | Add-Member -MemberType NoteProperty -Name "Permission" `

            -Value (Get-PermissionFromAccessMask($ace.AccessMask))

        $user | Add-Member -MemberType NoteProperty -Name "Inherited" `

            -Value (($ace.AceFlags -band $INHERITED_ACE_FLAG) -gt 0)

        $user

    }

 

Next time, we’ll look at setting namespace security which is a bit more complicated.

Steve Lee
Senior Test Manager
Microsoft