Set up non-admin account to access WMI and performance data remotely with PowerShell

I am working in a project that want to get performance data from remote servers, this with WMI, the servers belong to an Active Directory but the user collecting is not allowed to be an administrator (As an Administrator this is easy because then you already are in control and in the right groups)

So How do I fix this with preferably PowerShell, Scripting Guy has made a post about how to add users to local groups and Steve Lee on the MSDN WMI Blog has made a post how to add permissions on WMI  so I have combined them and got the solution I want.

The groups I am interested in on the remote computer are  “Distributed COM Users”,”Performance Monitor Users”, to be able to connect and get monitoring data

To get it from the WMI interface I need some permissions there, I only grant read permissions on the root/CIMv2 subtree.

And here is the Powershell that does it, In the screendump you can see that I can use a foreach loop to set the permissions on several computers

# Create Permissions for non-admin user on remote computers
#
# Niklas Akerlund / 2012-08-22
Param ([switch]$add,
	[switch]$remove,
    $ComputerName = "vc",
    $UserName = "olle",
    $DomainName = "vniklas")

# add functions to call
. .\Set-UserLocalGroup.ps1
. .\Set-WmiNamespaceSecurity.ps1

$LocalGroups = "Distributed COM Users","Performance Monitor Users"

if ($add){
	$LocalGroups | %{Set-UserLocalGroup -Computer $ComputerName -Group $_ -Domain $DomainName -User $UserName -add}
	Set-WMINamespaceSecurity root/CIMv2 add "$DomainName\$UserName" Enable,MethodExecute,ReadSecurity,RemoteAccess -computer $ComputerName
} elseif($remove) {
	$LocalGroups | %{Set-UserLocalGroup -Computer $ComputerName -Group $_ -Domain $DomainName -User $UserName -remove}
	Set-WMINamespaceSecurity root/cimv2 delete "$DomainName\$UserName" -computer $ComputerName
}

To check that I can get the performance data I can use the powershell cmdlet Get-WMIObject , as you can see in the screendump, in the first test I do not have the permissions set but in the second it works..

Here are the functions

Set-UserLocalGroup

# Add/Remove user from local group 		
#
# Niklas Akerlund/Most code from Scriptingguy Blog http://blogs.technet.com/b/heyscriptingguy/archive/2010/08/19/use-powershell-to-add-domain-users-to-a-local-group.aspx

Function Set-UserLocalGroup 
{ 
	[cmdletBinding()] 
	Param( 
	[Parameter(Mandatory=$True)] 
	[string]$Computer, 
	[Parameter(Mandatory=$True)] 
	[string]$Group, 
	[Parameter(Mandatory=$True)] 
	[string]$Domain, 
	[Parameter(Mandatory=$True)] 
	[string]$User,
	[switch]$add,
	[switch]$remove 
	)
	
	 
	$de = [ADSI]"WinNT://$Computer/$Group,group" 
	if($add){
		$de.psbase.Invoke("Add",([ADSI]"WinNT://$Domain/$User").path) 
	} elseif ($remove){
		$de.psbase.Invoke("Remove",([ADSI]"WinNT://$Domain/$User").path)
	}
	
} 

Set-WmiNamespaceSecurity, there was an error in the original code that I have corrected based on the comments, this error caused the function to always run on the local computer instead of the remote when using the -computer parameter.

# Copyright (c) Microsoft Corporation.  All rights reserved. 
# For personal use only.  Provided AS IS and WITH ALL FAULTS.
 
# Set-WmiNamespaceSecurity.ps1
# Example: Set-WmiNamespaceSecurity root/cimv2 add steve Enable,RemoteAccess
Function Set-WmiNamespaceSecurity {
 
Param ( [parameter(Mandatory=$true,Position=0)][string] $namespace,
    [parameter(Mandatory=$true,Position=1)][string] $operation,
    [parameter(Mandatory=$true,Position=2)][string] $account,
    [parameter(Position=3)][string[]] $permissions = $null,
    [bool] $allowInherit = $false,
    [bool] $deny = $false,
    [string] $computer = ".",
    [System.Management.Automation.PSCredential] $credential = $null)
   
Process {
    $ErrorActionPreference = "Stop"
 
    Function Get-AccessMaskFromPermission($permissions) {
        $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
                $WBEM_RIGHT_SUBSCRIBE = 0x40
                $WBEM_RIGHT_PUBLISH      = 0x80
        	$READ_CONTROL = 0x20000
        	$WRITE_DAC = 0x40000
       
        $WBEM_RIGHTS_FLAGS = $WBEM_ENABLE,$WBEM_METHOD_EXECUTE,$WBEM_FULL_WRITE_REP,<code>
            $WBEM_PARTIAL_WRITE_REP,$WBEM_WRITE_PROVIDER,$WBEM_REMOTE_ACCESS,</code>
            $READ_CONTROL,$WRITE_DAC
        $WBEM_RIGHTS_STRINGS = "Enable","MethodExecute","FullWrite","PartialWrite",<code>
            "ProviderWrite","RemoteAccess","ReadSecurity","WriteSecurity"
 
        $permissionTable = @{}
 
        for ($i = 0; $i -lt $WBEM_RIGHTS_FLAGS.Length; $i++) {
            $permissionTable.Add($WBEM_RIGHTS_STRINGS[$i].ToLower(), $WBEM_RIGHTS_FLAGS[$i])
        }
       
        $accessMask = 0
 
        foreach ($permission in $permissions) {
            if (-not $permissionTable.ContainsKey($permission.ToLower())) {
                throw "Unknown permission: $permission</code>nValid permissions: $($permissionTable.Keys)"
            }
            $accessMask += $permissionTable[$permission.ToLower()]
        }
       
        $accessMask
    }
 
    if ($PSBoundParameters.ContainsKey("Credential")) {
        $remoteparams = @{ComputerName=$computer;Credential=$credential}
    } else {
        $remoteparams = @{ComputerName=$computerName}
    }
       
    $invokeparams = @{Namespace=$namespace;Path="__systemsecurity=@"} + $remoteParams
 
    $output = Invoke-WmiMethod @invokeparams -Name GetSecurityDescriptor
    if ($output.ReturnValue -ne 0) {
        throw "GetSecurityDescriptor failed: $($output.ReturnValue)"
    }
 
    $acl = $output.Descriptor
    $OBJECT_INHERIT_ACE_FLAG = 0x1
    $CONTAINER_INHERIT_ACE_FLAG = 0x2
 
    $computerName = (Get-WmiObject @remoteparams Win32_ComputerSystem).Name
   
    if ($account.Contains('\')) {
        $domainaccount = $account.Split('\')
        $domain = $domainaccount[0]
        if (($domain -eq ".") -or ($domain -eq "BUILTIN")) {
            $domain = $computerName
        }
        $accountname = $domainaccount[1]
    } elseif ($account.Contains('@')) {
        $domainaccount = $account.Split('@')
        $domain = $domainaccount[1].Split('.')[0]
        $accountname = $domainaccount[0]
    } else {
        $domain = $computerName
        $accountname = $account
    }
 
    $getparams = @{Class="Win32_Account";Filter="Domain='$domain' and Name='$accountname'"}
 
    $win32account = Get-WmiObject @getparams
 
    if ($win32account -eq $null) {
        throw "Account was not found: $account"
    }
 
    switch ($operation) {
        "add" {
            if ($permissions -eq $null) {
                throw "-Permissions must be specified for an add operation"
            }
            $accessMask = Get-AccessMaskFromPermission($permissions)
   
            $ace = (New-Object System.Management.ManagementClass("win32_Ace")).CreateInstance()
            $ace.AccessMask = $accessMask
            if ($allowInherit) {
                $ace.AceFlags = $OBJECT_INHERIT_ACE_FLAG + $CONTAINER_INHERIT_ACE_FLAG
            } else {
                $ace.AceFlags = 0
            }
                       
            $trustee = (New-Object System.Management.ManagementClass("win32_Trustee")).CreateInstance()
            $trustee.SidString = $win32account.Sid
            $ace.Trustee = $trustee
           
            $ACCESS_ALLOWED_ACE_TYPE = 0x0
            $ACCESS_DENIED_ACE_TYPE = 0x1
 
            if ($deny) {
                $ace.AceType = $ACCESS_DENIED_ACE_TYPE
            } else {
                $ace.AceType = $ACCESS_ALLOWED_ACE_TYPE
            }
 
            $acl.DACL += $ace.psobject.immediateBaseObject
	    
        }
       
        "delete" {
            if ($permissions -ne $null) {
                throw "Permissions cannot be specified for a delete operation"
            }
       
            [System.Management.ManagementBaseObject[]]$newDACL = @()
            foreach ($ace in $acl.DACL) {
                if ($ace.Trustee.SidString -ne $win32account.Sid) {
                    $newDACL += $ace.psobject.immediateBaseObject
                }
            }
 
            $acl.DACL = $newDACL.psobject.immediateBaseObject
        }
       
        default {
            throw "Unknown operation: $operation`nAllowed operations: add delete"
        }
    }
 
    $setparams = @{Name="SetSecurityDescriptor";ArgumentList=$acl.psobject.immediateBaseObject} + $invokeParams
 
    $output = Invoke-WmiMethod @setparams
    if ($output.ReturnValue -ne 0) {
        throw "SetSecurityDescriptor failed: $($output.ReturnValue)"
    }
}
}

 

Comments

James
Reply

Hello, and great script.
Question:
How would I add permissions to “This namespace and subnamespaces”?
From what I can tell this will only add permissions to the parent namespace given as the param.

Thanks in advance
James

Jim
Reply

Nice! I want to try this out, but it’s a pain to copy paste, since it includes all 158 line numbers. Is there a downloadable file for that script?

Tad
Reply

Jim (or others w/the same issue), if you mouse over the code, a button appears in the upper-right that allows you to copy the code without line numbers.

Tad
Reply

@Jim
Jim (or others w/the same issue), if you mouse over the code, a button appears in the upper-right that allows you to copy the code without line numbers.

ESkarke
Reply

Thanks. I came up with a rewrite that suites my needs pretty well.


# Create Permissions for non-admin user on remote computers
#
# Niklas Akerlund / 2012-08-22
# http://vniklas.djungeln.se/2012/08/22/set-up-non-admin-account-to-access-wmi-and-performance-data-remotely-with-powershell/
# Edward Skarke modifications 2014-11-29

Param (
[switch]$add,
[switch]$remove,
$ComputerName = $env:Computername,
$UserName = (Write-Error "UserName is required")
)
# Add/Remove user from local group
#
# Niklas Akerlund/Most code from Scriptingguy Blog http://blogs.technet.com/b/heyscriptingguy/archive/2010/08/19/use-powershell-to-add-domain-users-to-a-local-group.aspx

if (-not $add -and -not $remove)
{
Write-Error "-add or -remove is required."
}

$ScriptPath = Split-Path $MyInvocation.MyCommand.Definition

# add functions to call
. "$ScriptPath\Function_Set-UserLocalGroup.ps1"
. "$ScriptPath\Function_Set-WmiNamespaceSecurity.ps1"

$UsernameArr = $Username -split ',(?=(?:[^"]|"[^"]*")*$)' -split ';(?=(?:[^"]|"[^"]*")*$)'
$UsernameArr | %{
if ($Username -match "\\")
{
$Username = ($_ -split "\\")[1]
$DomainName = ($_ -split "\\")[0]
}
elseif ($Username -match "@")
{
$Username = ($_ -split "@")[0]
$DomainName = ($_ -split "@")[1]
}
else
{
$Username = $_
$DomainName = $env:Computername
}
$LocalGroups = "Distributed COM Users","Performance Monitor Users"

if ($add){
$LocalGroups | %{Set-UserLocalGroup -Computer $ComputerName -Group $_ -Domain $DomainName -User $UserName -add}
Set-WMINamespaceSecurity root/CIMv2 add "$DomainName\$UserName" Enable,MethodExecute,ReadSecurity,RemoteAccess -computer $ComputerName
} elseif($remove) {
$LocalGroups | %{Set-UserLocalGroup -Computer $ComputerName -Group $_ -Domain $DomainName -User $UserName -remove}
Set-WMINamespaceSecurity root/cimv2 delete "$DomainName\$UserName" -computer $ComputerName
}
}

Lowman
Reply

I have the same question as James.

Hello, and great script.
Question:
How would I add permissions to “This namespace and subnamespaces”?
From what I can tell this will only add permissions to the parent namespace given as the param.
Thanks in advance
James

Tim Pillow
Reply

I am a newbie at powershell, when I run your script I get the following message…Any thoughts

WARNING: Exception calling “add” with “1” argument(s): “The specified account name is already a member of the group.

I am sure the account I am adding has never been added to that machine

Thanks inadvance

Tim Pillow
Reply

BTW, This is on Justin Grote’s script

Thanks

Alex Sz
Reply

There is a bug in row 059 of Set-WmiNamespaceSecurity
$remoteparams = @{ComputerName=$computerName} should be $remoteparams = @{ComputerName=$computer}

Markus Szumovski
Reply

To add the permission to “This namespace and subnamespaces”, just set the second bit flag (value 0x2) in the ACE flags of the DACL and windows will automatically add the permissions to all subnamespaces as well. In this script it is added if the allowInherit parameter is set to true. However, this script will then also set the first bit (value 0x1) in the ACE flags which will result in an error when I test it, so I assume this is a bug. Line 109 of this script should only read
$ace.AceFlags = $CONTAINER_INHERIT_ACE_FLAG
and the automatic inheritance will work 😉

Leave a comment

name*

email* (not published)

website