RIW_CreateUser_LocalExecution_v1.3.ps1

<< 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