To accomplish this I have created an PowerShell Script.
Especially the Firewall part was tricky to set up.
Use it on your own Risk.
Initial Release: Version: 1.0.0.0 10.September.2019
Current Release: Version: 1.0.1.0 11.September.2019
Changelog:
1.0.1.0 Fixed Parameter "-WmiNamespaceInheritPermissions" was not in use.
Added Comment to UAC section
#Requires -RunAsAdministrator
<#
.Synopsis
This script is to create an local user account
which is used to query this local computer over PRTG and WMI and
open the Windows Firewall to allow WMI Remoting
.DESCRIPTION
1. Create an Local user with the iads WinNT:// system provider
2. Put Local user into the local groups (international found by SID not by name, with the help of WMI Win32_Group class)
"Distributed COM Users"
SID: S-1-5-32-562
and
"Performance Monitor Users"
SID: S-1-5-32-558
3. Grant User permissions to the target WMI Namespace
4. Enable Windows-Firewall rule to allow WMI Access
using the CMD command "netsh advfirewall firewall ..." because PowerShell commands do not support Rule-Groups.
(!!! Atention !!! this command is international translated and language dependent !!!!!)
eg. for german: netsh advfirewall firewall set rule group="Windows-Verwaltungsinstrumentation (WMI)" new enable=yes
eg. for english: netsh advfirewall firewall set rule group="windows management instrumentation (wmi)" new enable=yes
5. This Script do not touch the UAC (registry key)!
UAC can in some cases filter information through WMI so that the information is not as complete as it could be.
Some are recommend turning off UAC filtering on the target machine. It can be done by setting a registry key.
Change it on your own Risk!
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\system\LocalAccountTokenFilterPolicy = 1
See: Microsoft documentation "User Account Control and WMI" https://docs.microsoft.com/de-de/windows/win32/wmisdk/user-account-control-and-wmi?redirectedfrom=MSDN
This Script is designed to run local on the target computer who is accessed by PRTG and Windows Management Instrumentation (WMI).
If you like to run this script remote on the target Computer, run this script with Invoke-Command.
For documentation of the user account flags,
search kewords are: "win32 api iads ADS_USER_FLAG_ENUM"
The ADS_USER_FLAG_ENUM enumeration defines the flags used for setting user properties in the directory.
These flags correspond to values of the userAccountControl attribute in Active Directory when using the LDAP provider,
and the userFlags attribute when using the WinNT system provider.
See: https://docs.microsoft.com/en-us/windows/win32/api/iads/ne-iads-ads_user_flag_enum
Copyright 2019 Peter Kriegel (modified MIT License)
Permission is hereby granted, free of charge,
to any person obtaining a copy of this script and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
THIS SCRIPT IS NOT SUPPORTED UNDER ANY SUPPORT PROGRAM OR SERVICE.
THE AUTHOR FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING, WITHOUT LIMITATION,
ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR A PARTICULAR PURPOSE.
THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE
OF THIS SCRIPT AND DOCUMENTATION REMAINS WITH YOU. IN NO EVENT THE AUTHOR OR
ANYONE ELSE INVOLVED IN THE CREATION, PRODUCTION, OR DELIVERY OF THIS SOFTWARE BE
LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS
PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING
OUT OF THE USE OF OR INABILITY TO USE THIS SCRIPT OR DOCUMENTATION, EVEN IF THE AUTHOR
HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
IT IS UP TO YOU, THAT YOU HAVE READ AND UNDERSTOOD WHAT THIS SCRIPT IS GOING TO PROCESS BEFORE YOU RUN IT.
IF YOU REPUBLISH THIS SCRIPT, CREDIT THE AUTHOR AND PROVIDE A LINK TO THE ORIGIN!
End of Copyright
.NOTES
Author: Peter Kriegel
Initial Release: Version: 1.0.0.0 10.September.2019
Current Release: Version: 1.0.1.0 11.September.2019
Changelog:
1.0.1.0 Fixed Parameter "-WmiNamespaceInheritPermissions" was not in use.
Added Comment to UAC section
#>
# Start variables to set
param (
# ComputerName to add the User and to modify the Firewall
[String]$ComputerName = $Env:COMPUTERNAME,
# Username to add
[String]$UserName = 'PRTGquery',
# Password for the User; If it is leaved blank, the password is requested in interactive mode!!!
[String]$UserPassword,
# Fullname of the User (only for description)
[String]$UserFullname = 'PRTG user',
# Userflags of the new created User account; see Microsoft documentation of the "ADS_USER_FLAG_ENUM" enumeration
[Int]$UserFlags = 64 + 65536, # ADS_UF_PASSWD_CANT_CHANGE + ADS_UF_DONT_EXPIRE_PASSWD
# Description for the new created User account
[String]$UserDescription = 'Query this Computer with PRTG',
# WMI Namespace who the user is granted permissions.
[String]$WmiNamespace = 'root/cimv2',
# the User permissions to grant to the WMI-Namespace. (default here is least privilege as read only)
[ValidateSet('Enable','MethodExecute','FullWrite','PartialWrite','ProviderWrite','RemoteAccess','ReadSecurity','WriteSecurity')]
[String[]]$WmiNamespacePermissions = @('Enable','MethodExecute','ReadSecurity','RemoteAccess'),
# If true the user permissions is inherited even to sub namespaces of the given namespace, if false only the Namespace is granted for the user
[Bool]$WmiNamespaceInheritPermissions = $True,
# add / modify firewall groupname to your language! (we have MUI environment and try to use the englisch version too, so a message like "No rules match the specified criteria." is normal)
[String[]]$WindowsFirewallRuleGroupNames = @('Windows-Verwaltungsinstrumentation (WMI)','windows management instrumentation (wmi)')
)
# Query / convert Password for the User
$PwEqualOrQuit = $False
$Pwd1 = $Null
$Pwd2 = $Null
If(-Not ([String]::IsNullOrEmpty(($UserPassword.Trim())))) {
$Pwd1 = ConvertTo-SecureString -String $UserPassword -AsPlainText -Force
$UserPassword = $Null
} Else {
Do {
$Pwd1 = Read-Host "Enter password for the user $UserName (Q or q to quit)" -AsSecureString
If ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Pwd1)) -ieq 'q') {
$Pwd1 = $Null
$PwEqualOrQuit = $True
} Else {
$Pwd2 = Read-Host "Reenter password for the user $UserName (Q or q to quit)" -AsSecureString
If ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Pwd2)) -ieq 'q') {
$Pwd1 = $Null
$PwEqualOrQuit = $True
}
}
If(-Not $PwEqualOrQuit) {
If ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Pwd1)) -ceq [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Pwd2))) {
$PwEqualOrQuit = $True
} Else {
Write-Host "`nPasswords dont match!`nTry again`n"
}
}
} while (-Not $PwEqualOrQuit)
If ($Null -eq $Pwd1) {
Write-Host 'User has quitted!'
Return
} Else {
# Write-Host "Passwords matched!"
}
}
# Create new local user
Try {
$Computer = [ADSI]"WinNT://$ComputerName,Computer"
$LocalUser = $Computer.Create("User", $UserName)
$LocalUser.SetPassword(([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Pwd1))))
$LocalUser.UserFlags = $UserFlags
$LocalUser.SetInfo()
$LocalUser.Put('Fullname', $UserFullname)
$LocalUser.SetInfo()
$LocalUser.Put('Description', $UserDescription)
$LocalUser.SetInfo()
} Catch {
Write-Error $Error[0]
Return
}
# Add User to groups
<#
To access WMI and performance data remotely and
If a target computer is outside of a domain or the user is an local account,
the user has to be a member of the local "Distributed COM Users" group and
of the "Performance Monitor Users" group on the local machine.
Additionally you have to grant permissions (read) to a WIM / CIM namespace (and subtree).
For an example Namespace Root/CIMV2
If the user needs access to all the namespaces, you can set the settings at the Root level (Root).
You have to recurse the permissions to the sub-namespaces with the security setting of "This namespace and subnamespaces"!
"Distributed COM Users"
SID: S-1-5-32-562
Name: BUILTIN\Distributed COM Users
Description: An alias. A group for COM to
provide computerwide access controls that
govern access to all call, activation, or launch
requests on the computer.
"Performance Monitor Users"
SID: S-1-5-32-558
Name: BUILTIN\Performance Monitor Users
Description: An alias. Members of this group
have remote access to monitor this computer.
To set a non Domain User into the local "Administrators" group to access WMI / CIM does not worked.
#>
# Get Groups by SID
Get-WMIObject -Class 'Win32_Group' -Filter "LocalAccount='True' AND (SID='S-1-5-32-562' OR SID='S-1-5-32-558')" -ComputerName $ComputerName | ForEach-Object {
# add User to Groups
$AdsiGroup = [ADSI]"WinNT://$ComputerName/$($_.Name),group"
$AdsiGroup.psbase.Invoke('Add',([ADSI]"WinNT://$ComputerName/$UserName").path)
}
Function Set-WMINamespacePermissions {
<#
.Synopsis
A Script for modifying the current security descriptor of a WMI namespace.
.DESCRIPTION
A Script for modifying the current security descriptor of a WMI namespace.
.EXAMPLE
Set-WMINamespacePermissions.ps1 -namespace root/cimv2 -account "contoso\AD - Remote WMI Access" -operation Add -permissions Enable
.EXAMPLE
Set-WMINamespacePermissions.ps1 root/cimv2 add steve Enable,RemoteAccess
.NOTES
Original Content by Steve Lee
Blog links:
https://blogs.msdn.microsoft.com/wmi/2009/07/20/scripting-wmi-namespace-security-part-1-of-3/
https://blogs.msdn.microsoft.com/wmi/2009/07/23/scripting-wmi-namespace-security-part-2-of-3/
https://blogs.msdn.microsoft.com/wmi/2009/07/27/scripting-wmi-namespace-security-part-3-of-3/
Modified by Graeme Bray and
Peter Kriegel (converted into a Function and fixed a Bug with inheritance)
Original "Set-WMINameSpaceSecurity.ps1", taken from:
https://gallery.technet.microsoft.com/Set-WMI-NameSpace-Security-5081ad6d
Disclaimer
The sample scripts are not supported under any Microsoft standard support program or service.
The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims
all implied warranties including, without limitation, any implied warranties of merchantability
or of fitness for a particular purpose. The entire risk arising out of the use or performance
of the sample scripts and documentation remains with you. In no event shall Microsoft, its
authors, or anyone else involved in the creation, production, or delivery of the scripts be
liable for any damages whatsoever (including, without limitation, damages for loss of business
profits, business interruption, loss of business information, or other pecuniary loss) arising
out of the use of or inability to use the sample scripts or documentation, even if Microsoft
has been advised of the possibility of such damages.
#>
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] $computerName = ".",
[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,`
$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"
$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`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 <- Bug !!!!
$ace.AceFlags = $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)"
}
}
}
# Set User permissions to WMI-Namespace
Set-WMINamespacePermissions -namespace $WmiNamespace -operation 'add' -account "$ComputerName\$UserName" -permissions $WmiNamespacePermissions -allowInherit $WmiNamespaceInheritPermissions -ComputerName $ComputerName
# Set firewall rules
ForEach ($FwGroupName in $WindowsFirewallRuleGroupNames) {
& 'netsh' @('advfirewall','firewall','set','rule','group=' + $FwGroupName ,'new','enable=yes')
}
Add comment