Scripting WMI Namespace Security (part 2 of 3)


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

Comments (14)

  1. Stefan says:

    Hi, sorry, I'm no expert, just trying to setup WMI monitoring… when I execute the script with:

    .Get-WmiNamespaceSecurity.ps1 -NameSpace "rootCIMV2"

    I get error:

    Missing expression after unary operator '-'.

    At C:Usersadmin147Get-WmiNamespaceSecurity.ps1:117 char:14

    +             – <<<< Value "$($ace.Trustee.Domain)$($ace.Trustee.Name)"

       + CategoryInfo          : ParserError: (-:String) [], ParseException

       + FullyQualifiedErrorId : MissingExpressionAfterOperator

    Have tried all combinations… is there an error in the script?

    Regards,

    Stefan

  2. SteveLee says:

    What OS and version of PowerShell are you using?  Did you cut and paste the script directly into ISE?

  3. g says:

    I'm having the same excact error as Stefan.

    Windows Server 2008 R2

    Copy/pasted the script directly to notepad and then tried running it with powershell.

  4. SteveLee says:

    I'll install W2k8R2 and will try this out.  There may have been changes in PowerShell that enable it to work on newer versions that I tested against.

  5. SteveLee says:

    Ok, I found a W2k8R2 VHD and I copied and pasted the text from above to 'test.ps1' and ran it in PowerShell and it worked for me.  $PSVersionTable.PSVersion tells me I'm running PowerShell 2.0.  What version of PowerShell are you running?

  6. AK says:

    PS v4 has some issues with this script. Add-Member needs to be modified to something like Add-Member -NotePropertyName "Name" -NotePropertyValue "$($ace.Trustee.Domain)$($ace.Trustee.Name)"

    Then  $WBEM_RIGHT_SUBSCRIBE $WBEM_RIGHT_PUBLISH need to be dropped.

  7. AK says:

    $WBEM_RIGHT_SUBSCRIBE $WBEM_RIGHT_PUBLISH have nothing to do with PS version. They need to be modified based on OS version.

  8. rakesh kumar says:

    hi am getting this error, am missing something?

    Invoke-WmiMethod : Invalid namespace

    At line:32 char:31

    +     $output = Invoke-WmiMethod <<<<  @invokeparams @credparams

       + CategoryInfo          : InvalidOperation: (:) [Invoke-WmiMethod], ManagementException

       + FullyQualifiedErrorId : InvokeWMIManagementException,Microsoft.PowerShell.Commands.InvokeWmiMethod

  9. SteveLee says:

    Which namespace did you provide?

  10. Jon G says:

    Steve, These scripts (Get & Set) works perfectly locally on a server; however I'm getting errors when attempting to use this script to get (and, i assume Set) the security descriptor from a remote system (Testing from 2008R2 to 2008R2). The script works fine when run locally on each of the test boxes, but not when running from one to the other.

    I am getting "Access Denied", even when providing valid credentials.

    $computer="test2k8_2";

    $namespace="rootcimv2TerminalServices";

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

    Invoke-WmiMethod @invokeparams -Credential $cred;

    Invoke-WmiMethod : Access denied

    At line:1 char:1

    + Invoke-WmiMethod @invokeparams -Credential $cred

    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

       + CategoryInfo          : InvalidOperation: (:) [Invoke-WmiMethod], ManagementException

       + FullyQualifiedErrorId : InvokeWMIManagementException,Microsoft.PowerShell.Commands.InvokeWmiMethod

    When adding the "Authentication" level to 6 (PacketPrivacy), I get an "Unexpected Error" (the exceptions appear to be particularly devoid of useful troubleshooting information).

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

    Invoke-WmiMethod @invokeparams -Credential $cred

    Invoke-WmiMethod : Unexpected error

    At line:1 char:1

    + Invoke-WmiMethod @invokeparams -Credential $cred

    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

       + CategoryInfo          : InvalidOperation: (:) [Invoke-WmiMethod], ManagementException

       + FullyQualifiedErrorId : InvokeWMIManagementException,Microsoft.PowerShell.Commands.InvokeWmiMethod

    The credential running the script as is the same as in the PSCredential $cred variable, which is a domain user and in the local admin group on both servers.

    Any advice on how to get this working would be extremely helpful!

  11. SteveLee says:

    @Jon, sounds like you have general WMI remoting issues unrelated to this specific script?  Did you try the troubleshooting steps already?  msdn.microsoft.com/…/aa394603(v=vs.85).aspx

    Steve [MSFT]

  12. Paul says:

    I think you've missed something when updating this post. In the first block you add 10 values to the $WBEM_RIGHTS_FLAGS, but only define 8 actual flags. This will produce an error due to the 2 missing variable definitions.

    In the later block you define 8 values in $WBEM_RIGHTS_FLAGS and 8 flags. This block will work.

  13. SandyG says:

    @Steve  Works fine on Windows 2008 and 2012 R2.

    NOT working on Windows 2008 R2. GetSecurityDescriptor method reports error 0x8004101d: Unexpected error namespace "rootcimv2"

  14. SteveLee says:

    Since there seems to be continued interest in this, let me see about revisiting the code and updating it.  No ETA at this time however.