<< Click to Display Table of Contents >> RayVentory Scan Engine > 12.6 u4 > User Guide > Appendix I: Prerequisites Inventory Methods > Zero-Touch/Remote Inventory for Windows (RIW) > Script Template to Grant Permissions for a Zero-Touch Inventory of Windows Devices RIW_CreateUser_LocalExecution_v1.3.ps1 |
<#
.SYNOPSIS
This script is used to fully configure an User on the target Client for RayVentory ZeroTouch Inventory Method.
Requires Powershell 2.0 or higher in order to work.
#
.DESCRIPTION
This script performs a set of actions on a target system to make sure that ZeroTouch inventory method will work for specified credentials.
Local or AD user credentials can be used during script execution.
Following changes are made to the system by default:
- User being added to specific local groups if those are existing on a target platform.
(Performance Monitor Users, Distributed COM Users, Remote Management Users)
- User is being granted read/execute permissions on specific WMI namespaces.
(root/cimv2,root/virtualization,root/MSCluster,root/Microsoft/SqlServer,root/MicrosoftSqlServer)
Following changes are made to the system if specific flag is set:
- Creation of a local user with provided name/password (flag "-CreateUser"). Required if provided user credentials are not existing.
- Grant user service read permissions (flag "-SetServicePermissions"). Required to read a list of services using ZeroTouch.
- Set WMI firewall rules (flag "-SetWmiFirewallRules"). Enables windows integrated firewall rules for WMI.
(netsh.exe advfirewall firewall set rule group="windows management instrumentation (wmi)" new enable=yes)
- Restart required services (flag "-RestartService"). Will restart WMI service and it's dependencies. Without this flag a reboot will be required.
.PARAMETER CreateUser
Set to create a new user locally. Will be ignored if user already exists.
.PARAMETER UserName
Enter Username here.
.PARAMETER UserPassword
Enter user password here. If "-CreateUser" is set then a password is required.
.PARAMETER Domain
Enter User-Domain here. Leave empty or "." if you want to use local machine.
.PARAMETER SID
Enter SID of the User here. If empty, the SID will be searched for provided domain\user combination.
.PARAMETER LogPath
The path to a directory where the log file will be written (default: "c:\temp\")
.PARAMETER SetServicePermissions
Flag. Defines if service permissions will be set for a user.
.PARAMETER SetWmiFirewallRules
Flag. Defines if firwall rules will be enabled for a user.
.PARAMETER RestartService
Flag. Defines if firwall rules will be enabled for a user
.EXAMPLE
Example with local user creation:
.\RIW_CreateUser_LocalExecution.ps1 -UserName "riwUser" -UserPassword "Password123" -Domain "." -CreateUser -SetServicePermissions -SetWmiFirewallRules -RestartService
Example for existing AD user:
RIW_CreateUser_LocalExecution.ps1 -UserName "m.mustermann" -Domain "ORGANIZATION" -SetServicePermissions
.NOTES
General notes
1.0 - Original script release
1.1 - Domain value support for both DNS and NETBIOS names
1.2 - Change NET LOCALGROUP sequence to respect Domain and failover for conflicts with localized group authorities
- Start WMI Service including dependend services, if WMI Service is not running (mandatory)
1.3 - Multilanguage group names parsing added
.LIMITATIONS
- If user and computer domain are different, then a domain trust must exist, otherwise permissions cannot be set
#>
[CmdletBinding()]
param (
[string]
$UserName = "UserName",
[string]
$Domain = "Domain",
[string]
$SID = $null,
[switch]
$CreateUser = $false,
[string]
$UserPassword = $null,
[string]
$LogPath = "c:\Temp\",
[switch]
$SetServicePermissions = $false,
[switch]
$SetWmiFirewallRules = $false,
[switch]
$RestartService = $false
)
### Global variables
# The version of a script
[string]$Global:ScriptVersion = "1.3"
# Supported log levels: "ERROR","WARN","INFO","DEBUG","VERBOSE"
# This level defines the serity message need to have in order to be written into a log file
[string]$Global:LogLevel = "INFO"
# Script global variables. Set during execution of the script.
# Please don't change values here.
[System.Collections.Generic.List[string]]$Global:UserGroups
[System.Collections.Generic.List[string]]$Global:WmiNamespaces
[string]$Global:ComputerName
[string]$Global:WorkingDomain
[string]$Global:UserSID
[string]$Global:LogFile
# Logging configuration. Please change "LogLevel" to manipulate log output.
$currentLogLevel = $null
switch ($Global:LogLevel)
{
"INFO" { $currentLogLevel = @("ERROR","WARN","INFO") }
"WARN" { $currentLogLevel = @("ERROR","WARN") }
"DEBUG" { $currentLogLevel = @("ERROR","WARN","INFO","DEBUG") }
"ERROR" { $currentLogLevel = @("ERROR") }
"VERBOSE" { $currentLogLevel = @("ERROR","WARN","INFO","DEBUG","VERBOSE") }
Default {}
}
################################################################
# Writing messages to console and file with different severities.
# As it is visible from tha validation parameter, following log levels are supported:
# 'INFO', 'WARN', 'DEBUG', 'ERROR', 'VERBOSE'
# Example: Log -message "Text" -severity INFO
# Default severity: INFO
################################################################
function Log {
param (
[string]$message,
[ValidateSet('INFO', 'WARN', 'DEBUG', 'ERROR', 'VERBOSE')]
[string]$severity = 'INFO'
)
if($currentLogLevel -contains $severity)
{
$dateTime = Get-Date -Format "yyyy.MM.dd HH:mm:ss"
$output = $dateTime + " [" + $severity + "]" + ": " + $message
switch ($severity) {
"INFO" { Write-Host $output }
"WARN" { Write-Warning $output }
"DEBUG" { Write-Debug $output }
"ERROR" { Write-Error $output }
"VERBOSE" { Write-Verbose $output }
Default { Write-Host $output}
}
$output | Out-File -FilePath $Global:LogFile -Encoding utf8 -Append
}
}
################################################################
# Exiting the script should always be done using this function.
# Takes single parameter - exit code that will be returned.
################################################################
function ExitScript {
param ([int]$exitCode = 0)
$exitMessage = "Exiting script with exit code $exitCode"
if($exitCode -ne 0)
{
Log -severity WARN -message "$exitMessage"
}
else {
Log $exitMessage
}
Log "********************* Footer *********************`n"
exit $exitCode
}
################################################################
# Used to execute script steps with nice output wrapping.
################################################################
function WrapStepBlock {
param (
[Parameter(Mandatory=$true)]
[scriptblock]$stepBody,
[Parameter(Mandatory=$true)]
[string]$stepName)
Log "--------------------------------------------------"
$stepLength = $stepName.Length
[int]$leftLength = (48 - $stepLength) / 2
if($stepLength%2 -eq 1)
{
$leftLength -=1
$sufix = $('#' * $leftLength) + "#"
}
else {
$sufix = $('#' * $leftLength)
}
$prefix = $('#' * $leftLength)
Log "$prefix $stepName $sufix"
try {
if($currentLogLevel -contains 'DEBUG')
{
$out = Invoke-Command $stepBody
log -severity DEBUG -message $out
}
else
{
$stepBody.Invoke()
}
}
finally {
Log "###################### Done ######################"
}
}
################################################################
# Required by "SetWmiPersmission" method. Converts human friendly
# names for permissions into access mask flags.
################################################################
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
}
################################################################
# Adds or deletes passed WMI permissions from passed WMI namespase.
# If "allowInherit" is set to "true" permissions will be propagated to subcontainers.
################################################################
Function SetWmiSecurity
{
Param ( [parameter(Mandatory=$true,Position=0)][string] $namespace,
[ValidateSet("add","delete")]
[parameter(Mandatory=$true,Position=1)][string] $operation,
[ValidateSet("Enable","MethodExecute","FullWrite","PartialWrite","ProviderWrite","RemoteAccess","ReadSecurity","WriteSecurity")]
[parameter(Position=3)][string[]] $permissions = $null,
[bool] $allowInherit = $false,
[bool] $deny = $false)
#$ErrorActionPreference = "Stop"
$computerName = "."
$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
switch ($operation) {
"add" {
if ($null -eq $permissions) {
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 = $CONTAINER_INHERIT_ACE_FLAG
} else {
$ace.AceFlags = 0
}
$trustee = (New-Object System.Management.ManagementClass("win32_Trustee")).CreateInstance()
$trustee.SidString = $Global:UserSID
$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 ($null -ne $permissions) {
throw "Permissions cannot be specified for a delete operation"
}
[System.Management.ManagementBaseObject[]]$newDACL = @()
foreach ($ace in $acl.DACL) {
if ($ace.Trustee.SidString -ne $Global:UserSID) {
$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)"
}
}
################################################################
# Used by "StartRequiredServices" function. Collects the list
# of all dependent services recursivly, which are not running
################################################################
function CollectStoppedDependentServices([string]$serviceName)
{
$resultCollection = New-Object System.Collections.Generic.HashSet[string]
$currentServices = Get-Service $serviceName -DependentServices
foreach($depService in $currentServices)
{
if([string]::IsNullOrEmpty($depService.Name))
{
continue
}
if($depService.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running)
{
$additional = CollectStoppedDependentServices $depService.Name
foreach ($item in $additional) {
$dummy = $resultCollection.Add($item)
}
$dummy = $resultCollection.Add($depService.Name)
}
}
return $resultCollection
}
################################################################
# Used by "RestartRequiredServices" function. Collects the list
# of all running dependent services recursively.
################################################################
function CollectRunningDependentServices([string]$serviceName)
{
$resultCollection = New-Object System.Collections.Generic.HashSet[string]
$currentServices = Get-Service $serviceName -DependentServices
foreach($depService in $currentServices)
{
if([string]::IsNullOrEmpty($depService.Name))
{
continue
}
if($depService.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Running)
{
$additional = CollectRunningDependentServices $depService.Name
foreach ($item in $additional) {
$dummy = $resultCollection.Add($item)
}
$dummy = $resultCollection.Add($depService.Name)
}
}
return $resultCollection
}
# General logic
################################################################
# Outputting log header with basic script information
################################################################
function StartScript
{
$Global:LogFile = [System.IO.Path]::Combine($LogPath,"RIW_CreateUser_$UserName.log")
try {
if(-not(Test-Path $LogPath))
{
$dummy = New-Item -ItemType Directory -Path $LogPath
}
$passwordPlaceholder = "<empty>"
if(-not [string]::IsNullOrEmpty($UserPassword))
{
$passwordPlaceholder = "<hidden. not empty>"
}
Log "********************* Header *********************"
Log "##################################################"
Log "RIW create user script v.$ScriptVersion"
Log "##################################################"
Log ""
Log " ____ __ __ __ "
Log "/\ _`\ /\ \/\ \ /\ \__ "
Log "\ \ \L\ \ __ __ __\ \ \ \ \ __ ___\ \ ,_\ ___ _ __ __ __ "
Log " \ \ , / /'__`\ /\ \/\ \\ \ \ \ \ /'__`\/' _ `\ \ \/ / __`\/\`'__\/\ \/\ \ "
Log " \ \ \\ \ /\ \L\.\_\ \ \_\ \\ \ \_/ \/\ __//\ \/\ \ \ \_/\ \L\ \ \ \/ \ \ \_\ \ "
Log " \ \_\ \_\ \__/.\_\\/`____ \\ `\___/\ \____\ \_\ \_\ \__\ \____/\ \_\ \/`____ \ "
Log " \/_/\/ /\/__/\/_/ `/___/> \`\/__/ \/____/\/_/\/_/\/__/\/___/ \/_/ `/___/> \"
Log " /\___/ /\___/"
Log " \/__/ \/__/ "
Log ""
Log "Startup arguments:"
Log "UserName: $UserName"
Log "UserPassword: $passwordPlaceholder"
Log "Domain: $Domain"
Log "SID: $SID"
Log "LogPath: $LogPath"
Log "CreateUser: '$CreateUser'"
Log "SetServicePermissions: '$SetServicePermissions'"
Log "SetWmiFirewallRules: '$SetWmiFirewallRules'"
Log "RestartService: '$RestartService'"
Log "Log level is set to $Global:LogLevel"
}
catch {
$errorMessage = $_.Exception.Message
Write-Error "Logger failed to initialize with error: $errorMessage"
ExitScript 1
}
}
#---------------------------------------------------------------
# Logic steps
#---------------------------------------------------------------
################################################################
# Additional parameter validation and potentially platform checks.
################################################################
function CheckPrerequisites {
Log -message "Checking for conditions to start the execution of logic." -severity DEBUG
if([string]::IsNullOrEmpty($UserName))
{
Log -message "User name is empty. The name is required by a script." -severity ERROR
ExitScript 1
}
if($CreateUser.IsPresent)
{
if([string]::IsNullOrEmpty($UserPassword))
{
Log -message "User password is empty. The password is required if 'CreateUser' flag is set." -severity ERROR
ExitScript 1
}
}
Log -message "Script is ready to be executed." -severity INFO
}
################################################################
# Preparing required data for a script execution:
# - resolving localized group names
# - preparing a list of WMI namespaces to be processed
# - resolving environment variables
################################################################
function PrepareWorkingParameters {
# Groups where the user will be added. Default is "Performance Monitor Users","Distributed COM Users","Remote Management Users"
# Get the name of the Groups by SID
# Performance Monitor Users : S-1-5-32-558
# Distributed COM Users: S-1-5-32-562
# Remote Management Users: S-1-5-32-580
$groupSids = @("S-1-5-32-558","S-1-5-32-562","S-1-5-32-580")
$groupNames = New-Object -TypeName System.Collections.Generic.List[string]
Log "Preparing a list of required groups"
foreach ($item in $groupSids)
{
Log -severity DEBUG -message "Processing SID " + $item
$objSID = New-Object System.Security.Principal.SecurityIdentifier($item)
try {
$objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
}
catch {
continue
}
$groupName = $objUser.Value
if($groupName.IndexOf('\') -ge 0)
{
$groupName = $groupName.Split('\')[1];
}
$dummy = $groupNames.Add($groupName)
Log -message "Resolved required group SID $item to name $groupName" -severity DEBUG
}
$Global:UserGroups = $groupNames
Log "Preparing a list of WMI namespaces"
$Global:WmiNamespaces = New-Object -TypeName System.Collections.Generic.List[string]
$Global:WmiNamespaces = @("root/cimv2","root/virtualization","root/MSCluster","root/Microsoft/SqlServer","root/MicrosoftSqlServer")
$Global:ComputerName = $env:COMPUTERNAME
Log -message "Resolved computer name: ${Global:ComputerName}"
if([string]::IsNullOrEmpty($Domain) -or "." -eq $Domain)
{
$Global:WorkingDomain = $Global:ComputerName
Log "Domain is empty. Will use ComputerName '$Global:WorkingDomain' as domain name"
}
else {
$Global:WorkingDomain = $Domain
Log "Domain is set to '$Global:WorkingDomain'"
}
}
################################################################
# Logic for creating local user account.
# By default this step is skipped.
# '-CreateUser' parameter is required to be set in order to run the logic.
################################################################
function CreateLocalUser {
if (-not $CreateUser.IsPresent -or $CreateUser -eq $false)
{
Log "'-CreateUser' flag is not present or is set to to false."
Log "Skipping this step."
}
elseif ([string]::IsNullOrEmpty($Global:WorkingDomain) -or "." -eq $Global:WorkingDomain -or $Global:ComputerName -eq $Global:WorkingDomain)
{
$isUserExists = $false
Log -severity DEBUG -message "Checking if user with given name exists"
$existingUsers = Get-WmiObject -Query "Select * from Win32_UserAccount where LocalAccount = True AND Name = '$UserName'"
$existingUser = $null
foreach ($user in $existingUsers) {
if($UserName -eq $user.Name)
{
$existingUser = $user
break
}
}
if($null -eq $existingUser)
{
Log "User is not found. Will be created with following name : $UserName"
$output = & NET @("USER", "$UserName","$UserPassword", "/ADD", "/fullname:$UserName", "/expires:never", "/Y")
$exitCode = $LASTEXITCODE
Log -severity VERBOSE -message $output
if($exitCode -ne 0)
{
throw New-Object System.ApplicationException "Adding group has failed with exit code: $exitCode"
}
}
else
{
Log "User is found. It will be reused."
$isUserExists = $true
}
}
else
{
Log -message "CreateUser flag is set but the domain name is not local. Skipping creating local user."
}
}
################################################################
# Logic for resolving the SID for provided user name/domain paramters.
################################################################
function SetUserSID
{
if([string]::IsNullOrEmpty($SID))
{
Log "The SID is empty. SID lookup will be executed."
if($Global:ComputerName -eq $Global:WorkingDomain)
{
Log -severity DEBUG -message "Local user lookup execution"
Log -severity DEBUG -message "Domain: $Global:WorkingDomain User: $UserName"
$query = "Select * from Win32_UserAccount where LocalAccount = True AND Caption = '${Global:WorkingDomain}\\$UserName'"
$existingUsers = Get-WmiObject -Query $query
if (-not ($existingUsers -is [array]))
{
$existingUsers = @($existingUsers)
}
if($existingUsers.Count -gt 0)
{
$Global:UserSID = $existingUsers[0].SID
if ($Global:UserSID.StartsWith("S-1-5-21"))
{
Log "Found local user $UserName with SID $Global:UserSID"
}
else {
Log -severity WARN -message "User and/or SID could not be found on the local machine. Exiting."
ExitScript -exitCode 2
}
}
else
{
Log -severity WARN -message "User and/or SID could not be found on the local machine. Exiting."
ExitScript -exitCode 2
}
}
else
{
Log -severity DEBUG -message "AD user lookup execution"
$existingUsers = Get-WmiObject -Query "Select * from Win32_UserAccount where Caption = '${Global:WorkingDomain}\\$UserName'"
if($null -eq $existingUsers)
{
Log -severity DEBUG -message "User/Domain combination was not found. Attemping search by UserName"
$existingUsers = Get-WmiObject -Query "Select * from Win32_UserAccount where Name = '$UserName'"
if($null -ne $userCandidates)
{
Log -severity DEBUG -message "User candidates were found"
}
}
if (-not ($existingUsers -is [array]))
{
$existingUsers = @($existingUsers)
}
if($existingUsers.Count -gt 0)
{
$Global:UserSID = $existingUsers[0].SID
$Global:WorkingDomain = $existingUsers[0].Domain
Log "Found user $Global:WorkingDomain\\$UserName with SID $Global:UserSID"
}
else {
Log -severity WARN -message "User and/or SID could not be found. Exiting."
ExitScript -exitCode 2
}
}
}
else {
Log "The SID is set from command line. Provided value will be used:"
$Global:UserSID = $SID
Log "$Global:UserSID"
}
}
################################################################
# Adding user to required local groups.
# The set of groups is defined in step "PrepareWorkingParameters"
################################################################
function AddUserToLocalGroups
{
foreach ($groupName in $Global:UserGroups)
{
$LASTEXITCODE = 0
Log "Adding user $WorkingDomain\$UserName to group $groupName"
$output = & NET @("LOCALGROUP", "$groupName", "$Global:WorkingDomain\$UserName", "/add")
$exitCode = $LASTEXITCODE
Log -severity VERBOSE -message $output
if($exitCode -ne 0)
{
if($exitCode -eq 2)
{
Log "The specified account name is already a member of the group."
}
else {
throw New-Object System.ApplicationException "Adding group has failed with exit code: $exitCode"
}
}
# fail over in case of local group name requires just the name without contect
$i = $groupname.IndexOf("\")
if ($i -gt 0)
{
$groupName = $groupname.Substring($i+1)
Log -severity VERBOSE -message $Groupname
$LASTEXITCODE = 0
Log "Adding user $WorkingDomain\$UserName to group $groupName"
$output = & NET @("LOCALGROUP", "$groupName", "$WorkingDomain\$UserName", "/add")
$exitCode = $LASTEXITCODE
Log -severity VERBOSE -message $output $exitCode
if($exitCode -ne 0)
{
throw New-Object System.ApplicationException "Adding group has failed with exit code: $exitCode"
}
}
}
}
################################################################
# Granting WMI permissions for defined namespaces.
# List of namespaces is defined in step "PrepareWorkingParameters".
# As it's visible from the command, the current list of permissions is:
# - Enable,MethodExecute,RemoteAccess,ReadSecurity
################################################################
function GrantUserWmiPermissions
{
$failedNamespaces = 0
try {
Log "Setting 'RemoteAccess' permission on Root without inheritance."
SetWmiSecurity -namespace "Root" -operation add -permissions RemoteAccess
}
catch {
$message = $_.Exception.Message
Log -message $message -severity WARN
}
foreach ($nameSpace in $Global:WmiNamespaces)
{
try {
Log "Setting 'Enable,MethodExecute,RemoteAccess,ReadSecurity' permissions on $nameSpace with inheritance."
SetWmiSecurity -namespace "$nameSpace" -operation add -permissions Enable,MethodExecute,RemoteAccess,ReadSecurity -allowInherit $true
}
catch {
$message = $_.Exception.Message
Log -message $message -severity WARN
$failedNamespaces++
}
}
if($failedNamespaces -eq $Global:WmiNamespaces.Count)
{
Log -severity ERROR -message "Granting permissions for WMI namespaces failed. Not enough permissions or WMI repository is corrupted on this machine."
throw "Grant user wmi permissions failed."
}
}
################################################################
# Setting user permissions to read (list) services.
# This step is optional and enabled by using "-SetServicePermissions" flag
################################################################
function GrantUserServicePermissions
{
if($SetServicePermissions.IsPresent -and $SetServicePermissions -eq $true)
{
# The permissions will be set here
# Set of permissions: QueryStatus, QueryConfig, Interrogate, EnumerateDependents, Start, ReadPermissions
$UserPermissionsSddl = "D:(A;CIOI;GRRCRPLCLOFRKR;;;$Global:UserSID)"
$services = Get-WmiObject -Query "Select Name from Win32_Service"
if($null -eq $services)
{
Log -severity WARN -message "Can't read services using WMI. Skipping step execution."
}
else
{
if(-not($services -is [array]))
{
$services = @($services)
}
$convertedArray = New-Object System.Collections.ArrayList(,$services)
$convertedArray.Add(@{Name="scmanager"})
foreach ($service in $convertedArray)
{
$serviceName = $service.Name
Log "Processing $serviceName"
[string]$output = & sc.exe @("sdshow", "$serviceName")
$exitCode = $LASTEXITCODE
Log -severity VERBOSE -message $output
if($exitCode -ne 0)
{
Log -severity WARN -message "Service SDDL string could not be extracted"
continue
}
$output = $output -replace [System.Environment]::NewLine, [string]::Empty
$userSidIndex = $output.IndexOf("$Global:UserSID")
if($userSidIndex -gt -1)
{
Log "User already assigned to service $serviceName"
}
else
{
$newServiceSddl = $output -replace "D:", $UserPermissionsSddl
Log "Adding user to service $serviceName"
$output = & sc.exe @("sdset", "$serviceName", "$newServiceSddl")
$exitCode = $LASTEXITCODE
Log -severity VERBOSE -message $output
if($exitCode -ne 0)
{
Log -severity WARN -message "Setting new permissions failed."
continue
}
}
}
}
}
else
{
Log "Service permissions flag '-SetServicePermissions' is not set. Skipping this step."
}
}
################################################################
# Setting firewall rules to accept WMI connections.
# This step is optional and enabled by using "-SetWmiFirewallRules" flag
################################################################
function SetWmiFirewallRules
{
if($SetWmiFirewallRules.IsPresent -and $SetWmiFirewallRules -eq $true)
{
Log "Executing 'netsh' to configure firewall for WMI"
$output = & netsh.exe @("advfirewall","firewall","set", "rule", "group=",'"@FirewallAPI.dll,-34251"',"new","enable=yes")
$exitCode = $LASTEXITCODE
Log -severity VERBOSE -message $output
if($exitCode -ne 0)
{
Log -severity WARN -message "Setting firewall failed with exit code: $exitCode"
}
}
else
{
Log "Set WMI firewall rules flag '-SetWmiFirewallRules' is not set. Skipping this step."
}
}
################################################################
# Restart WMI Service
# This step is optional and enabled by using "-RestartService" flag
################################################################
function RestartRequiredServices
{
if($RestartService.IsPresent -and $RestartService -eq $true)
{
Log -severity DEBUG -message "Getting service object and it's dependencies"
$wmiService = Get-Service Winmgmt
$dependantServices = CollectRunningDependentServices Winmgmt
if($wmiService.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Running)
{
Log "WMI service is running. Performing forced stop."
Stop-Service Winmgmt -Force
}
Log "Restarting WMI service."
Start-Service Winmgmt
Log "Restarting dependent services."
foreach ($depService in $dependantServices)
{
if([string]::IsNullOrEmpty($depService))
{
continue
}
$currentServiceState = Get-Service $depService|Select-Object -ExpandProperty Status
if($currentServiceState -ne [System.ServiceProcess.ServiceControllerStatus]::Running)
{
Log "$depService ..."
Start-Service -Name $depService
}
else
{
Log "Skipping $depService. It's already running."
}
}
}
else
{
Log "Restart services flag '-RestartService' is not set. Skipping this step."
}
}
################################################################
# Check if WMI Service is running and start it (mandatory)
################################################################
function StartRequiredServices
{
Log -severity DEBUG -message "Check if WMI service is running and start it"
$wmiService = Get-Service Winmgmt
if($wmiService.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running)
{
Log "WMI service is not running. Performing start ..."
Start-Service Winmgmt
$dependantServices = CollectStoppedDependentServices Winmgmt
foreach ($depService in $dependantServices)
{
if([string]::IsNullOrEmpty($depService))
{
continue
}
$currentServiceState = Get-Service $depService|Select-Object -ExpandProperty Status
if($currentServiceState -ne [System.ServiceProcess.ServiceControllerStatus]::Running)
{
Log "$depService ..."
Start-Service -Name $depService
}
else
{
Log "Skipping $depService. It's already running."
}
}
}
else
{
Log "WMI service already running. Skipping this step."
}
}
################################################################
# MAIN SECTION: Executing steps for setting required permissions
################################################################
TRY
{
StartScript
WrapStepBlock -stepName "Check prerequisites" -stepBody { CheckPrerequisites }
WrapStepBlock -stepName "Check WMI Service" -stepBody { StartRequiredServices }
WrapStepBlock -stepName "Prepare working parameters" -stepBody { PrepareWorkingParameters }
WrapStepBlock -stepName "Create local user" -stepBody { CreateLocalUser }
WrapStepBlock -stepName "Set user SID" -stepBody { SetUserSID }
WrapStepBlock -stepName "Add user to local groups" -stepBody { AddUserToLocalGroups }
WrapStepBlock -stepName "Grant user WMI permissions" -stepBody { GrantUserWmiPermissions }
WrapStepBlock -stepName "Grant user service permissions" -stepBody { GrantUserServicePermissions }
WrapStepBlock -stepName "Set WMI firewall rules" -stepBody { SetWmiFirewallRules }
WrapStepBlock -stepName "Restart required services" -stepBody { RestartRequiredServices }
}
catch {
$exceptionMessage = $_.Exception.Message
$line = $_.InvocationInfo.ScriptLineNumber
$offset = $_.InvocationInfo.OffsetInLine
Log -severity WARN -message "Line: $line : $offset; Message: $exceptionMessage"
ExitScript 1
}
ExitScript 0