<# .SYNOPSIS This script is used to give an account/group the required permissions for windows scanning with Lansweeper. .DESCRIPTION This script is used to give an account/group the required permissions for windows scanning with Lansweeper. .PARAMETER AccountName This is the account/group name to give permissions for Lansweeper scanning. .PARAMETER LogFile This specifies the location where the logfile will be created. .PARAMETER LogLevel This specifies the amount of logging. 0 = No Logging 1 = Basic Logging 2 = Verbose Logging .PARAMETER NoEventLog If this flag is specified, the script will not log to event log. .PARAMETER NoWindowsScanning If this flag is specified, the script will not configure Windows scanning permissions. .PARAMETER NoSQLScanning If this flag is specified, the script will not configure SQL scanning permissions. .EXAMPLE This will give the account "domain.local\LansweeperScanner" permissions to scan the computer. It will log basic information to eventlog (Application) and "C:\Windows\Temp\LansweeperScanningSetupLog.txt" Configure-WindowsScanning.ps1 'domain.local\LansweeperScanner" .EXAMPLE This will give the account "domain.local\LansweeperScanner" permissions to scan the computer. It will log all information to "C:\CustomLogLocation.txt" and nothing to event log. Configure-WindowsScanning.ps1 -AccountName "domain.local\LansweeperScanner" -LogFile 'C:\CustomLogLocation.txt' -LogLevel 2 -NoEventLog .OUTPUTS / .NOTES Highest EventID number = 37 #> param ( [Parameter(Position = 0, Mandatory = $True)] [string]$accountName, [string]$logFile = "C:\Windows\Temp\LansweeperScanningSetupLog.txt", [ValidateSet(0, 1, 2)] [int16]$logLevel = 1, [switch]$noEventLog ) # Script configuration # WMI permissions needed for scanning $global:wmiPermissions = 'Enable', 'MethodExecute', 'ReadSecurity', 'RemoteAccess' # WMI classes on which permissions are applied $global:wmiNamespaces = 'Root\CIMV2', 'Root\DEFAULT', 'Root\CIMV2\Security\MicrosoftTpm', 'Root\WMI', 'Root\CIMV2\Security\MicrosoftVolumeEncryption', 'Root\securitycenter', 'Root\SecurityCenter2', 'Root\MSCluster', 'Root\Microsoft\sqlserver', #the script will enumerate through all sub classes (per sql version) 'Root\MSCluster', 'Root\ccm', 'Root\ccm\ClientSDK', 'Root\SMS', #the script will enumerate through all sub classes (per ccm site) 'Root\virtualization', 'Root\virtualization\v2' # appIds on which launch permissions are needed for scanning $global:appIds = @( '{752073A2-23F2-4396-85F0-8FDB879ED0ED}' #Trusted installer ) ### Functions ### # Function that initializes logging to a logfile function Set-LogFile { try { If (Test-Path -Path $logFile) { Remove-Item -Path $logFile -ErrorAction 'Stop' -Force | Out-Null } New-Item $logFile -ErrorAction 'Stop' | Out-Null } catch { Write-Error 'Something went wrong whilst creating the log file.' throw $_ } Write-Log -data "Logging to logfile `"$logFile`" started." -type 'Basic' } # Function that handles logging function Write-Log { param ( [string]$data, [Parameter(Mandatory = $True)] [ValidateSet('Basic', 'Verbose')] [string]$type, [int16]$eventID, [string]$eventType = 'Information' ) # Parse parameters if ($script:eventLog -and $eventID) { $writeToEventLog = $true } # Basic logging if ($logLevel -eq 1 -and $type -eq 'Basic') { Write-Host $data Add-Content -Path $logFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [BASIC] $data" if ($eventType -eq 'Error') { $data = "$data`nSee `"$logFile`" for more information." } if ($writeToEventLog) { Write-EventLog -LogName $script:eventLog -Source $script:eventSource -eventID $eventID -Message $data -EntryType $eventType -Category 0 } return } # Verbose logging if ($logLevel -eq 2) { if ($type -eq 'Basic') { Write-Host $data Add-Content -Path $logFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [BASIC] $data" if ($eventType -eq 'Error') { $data = "$data`nSee `"$logFile`" for more information." } if ($writeToEventLog) { Write-EventLog -LogName $script:eventLog -Source $script:eventSource -eventID $eventID -Message $data -EntryType $eventType -Category 0 } return } if ($type -eq 'Verbose') { Write-Host $data Add-Content -Path $logFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [VERBOSE] $data" if ($eventType -eq 'Error') { $data = "$data`nSee `"$logFile`" for more information." } if ($writeToEventLog) { Write-EventLog -LogName $script:eventLog -Source $script:eventSource -eventID $eventID -Message $data -EntryType $eventType -Category 0 } return } } } # Function that initializes logging to event log function Set-EventLog { $script:eventLog = 'Application' $script:eventSource = 'Lansweeper Scanning Setup' # Create the event log source if it doesn't exist yet $sourceExistsInEventLog = [System.Diagnostics.EventLog]::LogNameFromSourceName($script:eventSource, $env:COMPUTERNAME) if (-not $sourceExistsInEventLog) { Write-Log -data "Creating event log source `"$script:eventSource`" in event log `"$script:eventLog`"..." -type 'Basic' try { New-EventLog -LogName $script:eventLog -Source $script:eventSource -ErrorAction 'Stop' } catch { Write-Log -data "An error occurred whilst creating the event log source `"$script:eventSource`" in event log `"$script:eventLog`". Skipping event logging for this run." -type 'Basic' $script:eventLog = $null $script:eventSource = $null } } # Recreate the event log source if it exists in another event log (reboot required!) elseif ($sourceExistsInEventLog -ne $script:eventLog) { try { Write-Log -data "The event log source `"$script:eventSource`" already exists in event log `"$sourceExistsInEventLog`". Removing this source..." -type 'Verbose' Remove-EventLog -Source $script:eventSource -ErrorAction 'Stop' Write-Log -data "Creating event log source `"$script:eventSource`" in event log `"$script:eventLog`"..." -type 'Verbose' New-EventLog -LogName $script:eventLog -Source $script:eventSource -ErrorAction 'Stop' Write-Log -data "Because the event log source changed from event log location `"$sourceExistsInEventLog`" to `"$script:eventLog`", a reboot is required to see logs in event viewer. Skipping event logging for this run." -type 'Basic' } catch { Write-Log -data "An error occurred whilst recreating the event log source in `"$script:eventSource`". Error:`n$_" -type 'Basic' } $script:eventLog = $null $script:eventSource = $null } if ($script:eventLog) { Write-Log -data "Logging to event log `"$script:eventLog`" started." -type 'Basic' } } # Function that reformats an account name to SAMAccountName function Format-AccountName { param ( [Parameter(Mandatory = $True)] [String]$accountName ) Write-Log -data "Parsing account `"$accountName`"" -type 'Verbose' # Parse SAMAccountName format if ($accountName.Contains('\')) { $splittedAccountName = $accountName.Split('\') $domain = $splittedAccountName[0] if (($domain -eq '.') -or ($domain -eq 'BUILTIN')) { $domain = $env:COMPUTERNAME } $formattedAccountName = "$domain\$($splittedAccountName[1])" Write-Log -data "Reformatted account name to `"$formattedAccountName`"" -type 'Basic' return $formattedAccountName } # Parse local user format $formattedAccountName = "$($env:COMPUTERNAME)\$accountName" Write-Log -data "Reformatted account name to `"$formattedAccountName`"" -type 'Basic' return $formattedAccountName } # Function that retrieves the SID corresponding to the given account function Get-AccountSID { param ( [Parameter(Mandatory = $True)] [String]$accountName ) $user = New-Object System.Security.Principal.NTAccount($accountName) $sid = $user.Translate([System.Security.Principal.SecurityIdentifier]) Write-Log -data "Retrieved SID `"$($sid.Value)`" for object `"$accountName`"." -Type 'Basic' return $sid.Value } # Function that adds an object to an Active Directory group function Add-ObjectToADGroup { param ( [parameter(Mandatory = $true, Position = 0)] [string]$objectName, [parameter(Mandatory = $true, Position = 1)] [string]$groupName ) # Get the AD group members try { $adGroupMembers = (Get-ADGroupMember -Identity $groupName | Where-Object { $_.Name -eq $objectName }) } catch { Write-Log -data "Get-ADGroupMember failed on group `"$groupName`". This happens if the group contains users of a one-way incoming trust. You should manually add `"$objectName`" to the `"$groupName`" Active Directory group if this isn't done already. Skipping this group membership check..." -type 'Basic' -eventType 'Warning' -eventID 2 return } if (-not $adGroupMembers) { Write-Log -data "Adding `"$objectName`" to the `"$groupName`" Active Directory group..." -type 'Basic' -eventType 'Information' -eventID 3 try { Add-ADGroupMember -Identity $groupName -Members $objectName } catch { Write-Log -data "An error occured whilst adding `"$objectName`" to Active Directory group `"$groupName`". If `"$objectName`" exists in another domain, you'll need to do this manually. Error message:`n$_" -type 'Basic' -eventType 'Information' -eventID 4 } } else { Write-Log -data "`"$objectName`" was already in the `"$groupName`" group, skipping." -type 'Basic' -eventType 'Information' -eventID 5 } } # Function that adds an object to a local group function Add-ObjectToLocalGroup { param ( [parameter(Mandatory = $true, Position = 0)] [string]$objectName, [parameter(Mandatory = $true, Position = 1)] [string]$groupName ) # Add the object if it isn't already in the group if (-not (Get-LocalGroupMember -Group $groupName | Where-Object { $_.Name -eq $objectName })) { Write-Log -data "Adding `"$objectName`" to the `"$groupName`" group..." -type 'Basic' -eventType 'Information' -eventID 6 try { Add-LocalGroupMember -Group $groupName -Member $objectName } catch { Write-Log -data "An error occured whilst adding `"$objectName`" to local group `"$groupName`". Error message:`n$_" -type 'Basic' -eventType 'Information' -eventID 7 } } else { Write-Log -data "`"$objectName`" was already in the `"$groupName`" group, skipping." -type 'Basic' -eventType 'Information' -eventID 8 } } # Function that maps the WMI permissions input to the corresponding AccessMask integer code function Get-AccessMaskFromWMIPermissions { param ( [parameter(Mandatory = $true, Position = 0)] [String[]]$permissions ) Write-Log -data 'Determining requested WMI access mask...' -type 'Verbose' $accessMask = 0 foreach ($permission in $permissions) { Write-Log -data "Parsing WMI permission `"$permission`"..." -type 'Verbose' switch ($permission) { 'Enable' { $accessMask += 1 } 'MethodExecute' { $accessMask += 2 } 'FullWrite' { $accessMask += 4 } 'PartialWrite' { $accessMask += 8 } 'ProviderWrite' { $accessMask += 0x10 } 'RemoteAccess' { $accessMask += 0x20 } 'ReadSecurity' { $accessMask += 0x20000 } 'WriteSecurity' { $accessMask += 0x40000 } default { Write-Log -data "Unknown WMI permission: `"$permission`"`n, skipping." -type 'Basic' -eventType 'Warning' -eventID 9 } } Write-Log -data "New WMI access mask value: $accessMask" -type 'Verbose' } Write-Log -data "Final WMI access mask value: $accessMask" -type 'Verbose' return $accessMask } # Function that checks if a WMI namespace exists function Find-WMINamespace { param ( [parameter(Mandatory = $true, Position = 0)] [String]$namespace, [parameter(Position = 1)] [Int16]$index = 0 ) $splitNamespace = $namespace.Split('\') # Check index 0 (must be "root") if ($index -eq 0 -and $splitNamespace[0] -ne 'root') { Write-Log -data "The path of a WMI namespace must always begin with `"root\`". Entered path: `"$namespace`"" -type 'Basic' -eventType 'Error' -eventID 10 return $false } # Check intermediate indices elseif (($index -ne 0) ` -and ($index -lt $splitNamespace.Length) ` -and (-not (Get-WmiObject -Class '__Namespace' -Namespace ($splitNamespace[0..($index - 1)] -join '\') | Where-Object { $_.Name -eq $splitNamespace[$index] }))) { return $false # This is the case when the child namespace is not found in the list of namespaces on the parent } # $index > split namespace array means that the last child object is successfully found elseif ($index -ge $splitNamespace.Length) { return $true } return Find-WMINamespace -namespace $namespace -index (++$index) } # Function that adds permissions to a WMI namespace function Add-WMIPermissions { param ( [parameter(Mandatory = $true, Position = 0)] [string]$namespace, [parameter(Mandatory = $true, Position = 1)] [string]$accountName, [parameter(Mandatory = $true, Position = 2)] [System.Security.Principal.SecurityIdentifier]$accountSid, [parameter(Mandatory = $true, Position = 3)] [string[]]$permissions, [switch]$enableInherit, [switch]$deny ) # Check if the requested namespace exists if (-not (Find-WMINamespace -namespace $namespace)) { Write-Log -data "WMI Namespace `"$namespace`" not found. Skipping permissions change." -type 'Basic' -eventType 'Information' -eventID 11 return } # Get the security descriptor of the WMI namespace Write-Log -data "Retrieving current security descriptor from WMI namespace `"$namespace`"..." -type 'Verbose' try { $getSDOutput = Invoke-WmiMethod -Namespace $namespace -Path '__systemsecurity=@' -Name GetSecurityDescriptor -ErrorAction 'Stop' } catch { Write-Log -data "An error occured whilst retrieving the WMI permissions on namespace `"$namespace`". Error message:`n$_" -type 'Basic' } if ($getSDOutput.ReturnValue -ne 0) { Write-Log -data "WMI Method `"GetSecurityDescriptor`" on namespace `"$namespace`" failed, return code: $($output.ReturnValue)" -type 'Basic' -eventType 'Error' -eventID 12 exit 2 } # Put the security descriptor in a seperate object $sd = $getSDOutput.Descriptor # Check if the account already has permissions defined Write-Log -data "Checking if `"$accountName`" already has the requested permissions on WMI namespace `"$namespace`"..." -type 'Verbose' $requestedAccessMask = Get-AccessMaskFromWMIPermissions -permissions $permissions if ($sd.DACL | Where-Object { $_.Trustee.SidString -eq $accountSid -and $_.AccessMask -eq $requestedAccessMask }) { Write-Log -data "`"$accountName`" already has the requested permissions on WMI namespace `"$namespace`", skipping." -type 'Basic' -eventType 'Information' -eventID 13 return } # Create the ACE Write-Log -data "Creating the ACE for `"$accountName`"..." -type 'Verbose' Write-Log -data "Permissions that this ACE will contain: $permissions" -type 'Verbose' $ace = (New-Object System.Management.ManagementClass('win32_Ace')).CreateInstance() $ace.AccessMask = $requestedAccessMask if ($enableInherit) { $ace.AceFlags = 0x2 } else { $ace.AceFlags = 0 } Write-Log -data "New ACE's AceFlags: $($ace.AceFlags)" -type 'Verbose' $trustee = (New-Object System.Management.ManagementClass('win32_Trustee')).CreateInstance() $trustee.SidString = $accountSid $ace.Trustee = $trustee if ($deny) { $ace.AceType = 0x1 } else { $ace.AceType = 0x0 } Write-Log -data "New ACE's AceType: $($ace.AceType)" -type 'Verbose' # Add the ACE to the DACL $sd.DACL += $ace.psobject.immediateBaseObject # Set the new Security Descriptor to the namespace Write-Log -data "Setting the new security descriptor to WMI namespace `"$namespace`"..." -type 'Verbose' try { $setSDOutput = Invoke-WmiMethod -Namespace $namespace -Path '__systemsecurity=@' -ArgumentList $sd.psobject.immediateBaseObject -Name 'SetSecurityDescriptor' -ErrorAction 'Stop' } catch { Write-Log -data "An error occured whilst setting the updated WMI permissions on namespace `"$namespace`". Error message:`n$_" -type 'Basic' -eventType 'Error' -eventID 14 } if ($setSDOutput.ReturnValue -ne 0) { Write-Log -data "WMI Method `"SetSecurityDescriptor`" on namespace `"$namespace`" failed, return code: $($setSDOutput.ReturnValue)" -type 'Basic' exit 3 } Write-Log -data "Updated security descriptor on WMI namespace `"$namespace`". Added permissions: $permissions" -type 'Basic' -eventType 'Information' -eventID 15 } # Function that gives permissions to change ownership on a registry key function Set-RegistryKeyOwnershipPrivileges { param( [parameter(Mandatory = $true, Position = 0)] [string]$rootKey, [parameter(Mandatory = $true, Position = 1)] [string]$key ) switch -regex ($rootKey) { 'HKCU|HKEY_CURRENT_USER' { $rootKey = 'CurrentUser' } 'HKLM|HKEY_LOCAL_MACHINE' { $rootKey = 'LocalMachine' } 'HKCR|HKEY_CLASSES_ROOT' { $rootKey = 'ClassesRoot' } 'HKCC|HKEY_CURRENT_CONFIG' { $rootKey = 'CurrentConfig' } 'HKU|HKEY_USERS' { $rootKey = 'Users' } } # Give ourself the privileges to take ownership Write-Log -data "Taking privileges to change ownership of registry key `"$rootKey\$key`"..." -type 'Verbose' $import = '[DllImport("ntdll.dll")] public static extern int RtlAdjustPrivilege(ulong a, bool b, bool c, ref bool d);' $privileges = @{ SeTakeOwnership = 9; SeBackup = 17; SeRestore = 18 } foreach ($value in $privileges.Values) { $null = (Add-Type -Member $import -Name NtDll -PassThru)::RtlAdjustPrivilege($value, 1, 0, [ref]0) } } # Function that sets full control permissions on registry keys and takes ownership (recursively with -recurse) function Set-RegistryKeyOwnerPermissions { param( [parameter(Mandatory = $true, Position = 0)] [string]$rootKey, [parameter(Mandatory = $true, Position = 1)] [string]$key, [parameter(Mandatory = $true, Position = 2)] [string]$accountName, [parameter(Mandatory = $true, Position = 3)] [System.Security.Principal.SecurityIdentifier]$accountSid, [switch]$recurse ) switch -regex ($rootKey) { 'HKCU|HKEY_CURRENT_USER' { $rootKey = 'CurrentUser' } 'HKLM|HKEY_LOCAL_MACHINE' { $rootKey = 'LocalMachine' } 'HKCR|HKEY_CLASSES_ROOT' { $rootKey = 'ClassesRoot' } 'HKCC|HKEY_CURRENT_CONFIG' { $rootKey = 'CurrentConfig' } 'HKU|HKEY_USERS' { $rootKey = 'Users' } } # Retrieve the current ACL of the registry key $regKey = [Microsoft.Win32.Registry]::$rootKey.OpenSubKey($key, 'ReadWriteSubTree', 'TakeOwnership') $acl = New-Object System.Security.AccessControl.RegistrySecurity $acl.SetOwner($accountSid) $regKey.SetAccessControl($acl) $acl.SetAccessRuleProtection($false, $false) # Enables inheritance $identityReferenceInAcls = $acl.Owner # This is used to transform the SID to the identity reference used in ACL's # Check if the user already has ownership of the key Write-Log -data "Checking if `"$accountName`" is already owner of registry key `"$rootKey\$key`"..." -type 'Verbose' if ($regKey.GetAccessControl().Owner -eq $identityReferenceInAcls) { Write-Log -data "`"$accountName`" is already owner of registry key `"$rootKey\$key`", skipping." -type 'Basic' -eventType 'Information' -eventID 16 } else { # Set ownership on key Write-Log -data "Changing ownership of registry key `"$rootKey\$key`" to `"$accountName`"..." -type 'Basic' -eventType 'Information' -eventID 17 $regKey.SetAccessControl($acl) } # Check if the user already has Full Control permissions on the key Write-Log -data "Checking if `"$accountName`" already has full control permissions on registry key `"$rootKey\$key`"..." -type 'Verbose' $regKey = [Microsoft.Win32.Registry]::$rootKey.OpenSubKey($key, 'ReadWriteSubTree', 'ChangePermissions') $ExistingAcl = $regkey.GetAccessControl().Access | Where-Object { $_.IdentityReference -eq $identityReferenceInAcls -and $_.RegistryRights -eq 'FullControl' -and $_.AccessControlType -eq 'Allow' } if ($ExistingAcl) { Write-Log -data "`"$accountName`" already has full control permissions on registry key `"$rootKey\$key`", skipping." -type 'Basic' -eventType 'Information' -eventID 18 } else { Write-Log -data "`Setting full control permissions for `"$accountName`" on registry key `"$rootKey\$key`"..." -type 'Basic' -eventType 'Information' -eventID 19 $regKey = [Microsoft.Win32.Registry]::$rootKey.OpenSubKey($key, 'ReadWriteSubTree', 'ChangePermissions') $rule = New-Object System.Security.AccessControl.RegistryAccessRule($sid, 'FullControl', 'ContainerInherit', 'None', 'Allow') $acl.ResetAccessRule($rule) $regKey.SetAccessControl($acl) } # Recursively repeat if ($recurse) { foreach ($subKey in $regKey.OpenSubKey('').GetSubKeyNames()) { Set-RegistryKeyFullControlPermissions -rootKey $rootKey -key ($key + '\' + $subKey) -accountName $accountName -accountSid $accountSid -recurse:$recurse } } } # Function that maps the DCOM access permissions to the corresponding AccessMask integer code function Get-AccessMaskFromDCOMAccessPermission { param ( [parameter(Mandatory = $true, Position = 0)] [String[]]$permissions ) Write-Log -data 'Determining requesting DCOM access mask...' -type 'Verbose' $accessMask = 1 foreach ($permission in $permissions) { Write-Log -data "Parsing DCOM permission `"$permission`"..." -type 'Verbose' switch ($permission) { 'LocalAccess' { $accessMask += 2 } 'RemoteAccess' { $accessMask += 4 } default { Write-Log -data "Unknown DCOM access permission: `"$permission`"`n, skipping." -type 'Basic' -eventType 'Warning' -eventID 20 } } Write-Log -data "New DCOM access mask value: $accessMask" -type 'Verbose' } Write-Log -data "Final DCOM access mask value: $accessMask" -type 'Verbose' return $accessMask } # Function that maps the DCOM launch permissions to the corresponding AccessMask integer code function Get-AccessMaskFromDCOMLaunchPermission { param ( [parameter(Mandatory = $true, Position = 0)] [String[]]$permissions ) Write-Log -data 'Determining requested DCOM launch mask...' -type 'Verbose' $accessMask = 1 foreach ($permission in $permissions) { Write-Log -data "Parsing DCOM permission `"$permission`"..." -type 'Verbose' switch ($permission) { 'LocalLaunch' { $accessMask += 2 } 'RemoteLaunch' { $accessMask += 4 } 'LocalActivation' { $accessMask += 8 } 'RemoteActivation' { $accessMask += 16 } default { Write-Log -data "Unknown DCOM launch permission: `"$permission`"`n, skipping." -type 'Basic' -eventType 'Information' -eventID 21 } } Write-Log -data "New DCOM launch mask value: $accessMask" -type 'Verbose' } Write-Log -data "Final DCOM launch mask value: $accessMask" -type 'Verbose' return $accessMask } # Function that adds permissions on a DCOM component function Add-DCOMPermissions { param ( [parameter(Mandatory = $true, Position = 0)] [string]$dcomAppId, [parameter(Mandatory = $true, Position = 1)] [string]$accountName, [parameter(Mandatory = $true, Position = 2)] [System.Security.Principal.SecurityIdentifier]$accountSid, [string[]]$accessPermissions, [string[]]$launchPermissions ) # Input validation (make sure -accessPermissions or -launchPermissions is specified) if (-not $accessPermissions -and -not $launchPermissions) { Write-Log -data 'You have to either specify -accessPermissions or -launchPermissions permissions.' -type 'Basic' return } # Get the app object Write-Log -data "Retrieving the DCOM app with App ID `"$dcomAppId`"..." -type 'Verbose' $app = Get-WmiObject -query ("SELECT * FROM Win32_DCOMApplicationSetting WHERE AppId = `"$dcomAppId`"") -EnableAllPrivileges if (-not $app) { Write-Log -data "The DCOM app with app ID `"$dcomAppId`" could not be retrieved." -type 'Basic' -eventType 'Error' -eventID 22 return } if ($accessPermissions) { # Retrieve the current Security Descriptor $sd = $app.GetAccessSecurityDescriptor().Descriptor # Check if the account already has the requested permissions on the DCOM app Write-Log -data "Checking if `"$accountName`" already has the requested access permissions on the DCOM app with app ID `"$dcomAppId`"..." -type 'Verbose' $requestedAccessAccessMask = Get-AccessMaskFromDCOMAccessPermission -permissions $accessPermissions if ($sd.DACL | Where-Object { $_.Trustee.SidString -eq $accountSid -and $_.AccessMask -eq $requestedAccessAccessMask }) { Write-Log -data "`"$accountName`" already has the requested access permissions on the DCOM app with app ID `"$dcomAppId`", skipping." -type 'Basic' -eventType 'Information' -eventID 23 } else { # Create the ACE Write-Log -data "Creating the requested access permissions ACE for user `"$accountName`"..." -type 'Verbose' $ace = (New-Object System.Management.ManagementClass('win32_Ace')).CreateInstance() $ace.AccessMask = $requestedAccessAccessMask $ace.AceFlags = 0 $trustee = (New-Object System.Management.ManagementClass('win32_Trustee')).CreateInstance() $trustee.SidString = $accountSid $ace.Trustee = $trustee $ace.AceType = 0x0 # Add the ACE to the DACL $sd.DACL += $ace.psobject.immediateBaseObject # Set the modified access security descriptor to the DCOM App Write-Log -data "Setting the modified access security descriptor on the DCOM app with app ID `"$dcomAppId`"..." -type 'Verbose' $output = $app.SetAccessSecurityDescriptor($sd.psobject.immediateBaseObject) if ($output.ReturnValue -ne 0) { Write-Log -data "The SetAccessSecurityDescriptor method on the DCOM app with app ID `"$dcomAppId`" failed, return code: $($output.ReturnValue)" -type 'Basic' -eventType 'Error' -eventID 24 } else { Write-Log -data "Successfully set the modified access security descriptor on DCOM app with app ID `"$dcomAppId`"" -type 'Verbose' } } } if ($launchPermissions) { # Retrieve the current Security Descriptor $sd = $app.GetLaunchSecurityDescriptor().Descriptor # Check if the account already has the requested permissions on the DCOM app Write-Log -data "Checking if `"$accountName`" already has the requested launch permissions on the DCOM app with app ID `"$dcomAppId`"..." -type 'Verbose' $requestedLaunchAccessMask = Get-AccessMaskFromDCOMLaunchPermission -permissions $launchPermissions if ($sd.DACL | Where-Object { $_.Trustee.SidString -eq $accountSid -and $_.AccessMask -eq $requestedLaunchAccessMask }) { Write-Log -data "`"$accountName`" already has the requested launch permissions on the DCOM app with app ID `"$dcomAppId`", skipping." -type 'Basic' -eventType 'Information' -eventID 25 } else { # Create the ACE Write-Log -data "Creating the requested launch permissions ACE for user `"$accountName`"..." -type 'Verbose' $ace = (New-Object System.Management.ManagementClass('win32_Ace')).CreateInstance() $ace.AccessMask = $requestedLaunchAccessMask $ace.AceFlags = 0 $trustee = (New-Object System.Management.ManagementClass('win32_Trustee')).CreateInstance() $trustee.SidString = $accountSid $ace.Trustee = $trustee $ace.AceType = 0x0 # Add the ACE to the DACL $sd.DACL += $ace.psobject.immediateBaseObject # Set the modified launch security descriptor to the DCOM App Write-Log -data "Setting the modified launch security descriptor on the DCOM app with app ID `"$dcomAppId`"..." -type 'Verbose' $output = $app.SetLaunchSecurityDescriptor($sd.psobject.immediateBaseObject) if ($output.ReturnValue -ne 0) { Write-Log -data "The SetLaunchSecurityDescriptor method on the DCOM app with app ID `"$dcomAppId`" failed, return code: $($output.ReturnValue)" -type 'Basic' -eventType 'Error' -eventID 26 } else { Write-Log -data "Successfully set the modified launch security descriptor on DCOM app with app ID `"$dcomAppId`"" -type 'Verbose' } } } } # Function that adds a read clause to the security descriptor of a service function Add-SDReadClause { param ( [parameter(Mandatory = $true, Position = 0)] [string]$service, [parameter(Mandatory = $true, Position = 1)] [string]$accountName, [parameter(Mandatory = $true, Position = 2)] [System.Security.Principal.SecurityIdentifier]$accountSid ) # Get the current security descriptor Write-Log -data "Getting current security descriptor of service `"$service`"" -type 'Verbose' $sd = & sc.exe sdshow $service if ($sd[1].substring(0, 3) -ne 'D:(') { Write-Log -data "The security descriptor for service `"$service`" could not be retrieved." -type 'Basic' -eventType 'Error' -eventID 27 return } # Check if the object already has permissions defined Write-Log -data "Checking if the account already has permissions defined on service `"$service`"..." -type 'Verbose' if ($sd[1].contains($accountSid)) { Write-Log -data "Account `"$accountSid`" already has permissions defined on service `"$service`", skipping." -type 'Basic' -eventType 'Information' -eventID 28 Write-Log -data "Current SDDL: $($sd[1])" -type 'Verbose' return } # Add the read clause to the current SDDL Write-Log -data "Current security descriptor for service `"$service`": $($sd[1])" -type 'Verbose' Write-Log -data "Adding read permission for `"$accountName`" to the security descriptor of the `"$service`" service..." -type 'Basic' $sd = "$($sd[1].substring(0,2))(A;;CCLCRPRC;;;$($accountSid))$($sd[1].substring(2,$sd[1].length-2))" Write-Log "New security descriptor for service `"$service`": $($sd)" -type 'Verbose' # Set the changed SDDL Write-Log -data "Setting the modified security descriptor on service `"$service`"..." -type 'Verbose' $sdSetOutput = sc.exe sdset $service "$sd" if ($sdSetOutput.Contains('SUCCESS')) { Write-Log -data "Successfully modified the security descriptor for service `"$service`"" -type 'Basic' -eventType 'Information' -eventID 29 } else { Write-Log -data "An error occurred whilst setting the security descriptor for service `"$service`", sdset output: $sdSetOutput" -type 'Basic' -eventType 'Error' -eventID 30 } } # Function that adds/updates a property in registry function Set-RegistryProperty { param ( [parameter(Mandatory = $true, Position = 0)] [string]$path, [parameter(Mandatory = $true, Position = 1)] [string]$name, [parameter(Mandatory = $true, Position = 2)] [String]$type, [parameter(Mandatory = $true, Position = 3)] [String]$value ) # Create the parent key if it doesn't exist Write-Log -data "Checking if registry key `"$path`" exists..." -type 'Verbose' if (-not (Test-Path $path)) { try { Write-Log -data "Creating registry key `"$path`"..." -type 'Basic' New-Item -Path $path -Force -ErrorAction 'Stop' | Out-Null } catch { Write-Log -data "An error occured whilst creating the registry key `"$path`". Error:`n$_" -type 'Basic' -eventType 'Error' -eventID 31 return } } # Check if the property already exists Write-Log -data "Checking if registry property `"$name`" in `"$path`" already has value `"$value`"..." -type 'Verbose' if ((Get-Item -LiteralPath $path).GetValue($name, $null) -eq $value) { Write-Log -data "Registry property `"$name`" in `"$path`" already has value `"$value`", skipping." -type 'Basic' -eventType 'Information' -eventID 32 return } # Add/update the property Write-Log -data "Creating/Updating registry property `"$name`" in `"$path`" with value `"$value`"..." -type 'Basic' try { New-ItemProperty -Path $path -Name $name -Value $value -PropertyType $type -Force -ErrorAction 'Stop' | Out-Null } catch { Write-Log -data "An error occured whilst creating the registry property `"$name`" in `"$path`". Error:`n$_" -type 'Basic' -eventType 'Error' -eventID 33 } } # Function that configures Windows scanning function Add-WindowsScanning { param ( [parameter(Mandatory = $true, Position = 0)] [string]$accountName, [parameter(Mandatory = $true, Position = 1)] [System.Security.Principal.SecurityIdentifier]$accountSid ) # Check if the computer is a domain controller. If this is the case, the user should be added to the corresponding BUILTIN AD groups if ((Get-WmiObject -Class Win32_ComputerSystem | Select-Object -ExpandProperty DomainRole) -in (4, 5)) { # Add-ADGroupMember uses the samAccountName to identify the object in AD. $objectSamAccountName = $accountName.Split('\')[1] # Add the account to the AD "Distributed COM Users" group (needed for access to DCOM on the DC's) Add-ObjectToADGroup -objectName $objectSamAccountName -groupName 'Distributed COM Users' # Add the account to the AD "Event Log Readers" group (needed for event log scanning on the DC's) Add-ObjectToADGroup -objectName $objectSamAccountName -groupName 'Event Log Readers' } else { # Add the account to the local "Distributed COM Users" group (needed for access to DCOM on the local computer) Add-ObjectToLocalGroup -objectName $accountName -groupName 'Distributed COM Users' # Add the account to the local "Event Log Readers" group (needed for event log scanning on the local computer) Add-ObjectToLocalGroup -objectName $accountName -groupName 'Event Log Readers' } # Give the user access to the required WMI namespaces (needed for access to WMI classes used for basic windows scanning) foreach ($wmiNamespace in $wmiNamespaces) { if ($wmiNamespace -eq 'Root\SMS') { Add-SccmScanning -accountName $formattedAccountName -accountSid $accountSid } elseif ($wmiNamespace -eq 'Root\Microsoft\sqlserver') { Add-SQLScanning -accountName $formattedAccountName -accountSid $accountSid } else { Add-WMIPermissions -namespace $wmiNamespace -accountName $formattedAccountName -accountSid $accountSid -permissions $wmiPermissions -enableInherit } } # Give the user full control permissions on the DCOM components in registry (needed for changing DCOM access & launch permissions) foreach ($appId in $appIds) { Set-RegistryKeyOwnershipPrivileges -rootKey 'HKCR' -key "AppID\$appId" Set-RegistryKeyOwnerPermissions -rootKey 'HKCR' -key "AppID\$appId" -accountName 'BUILTIN\Administrators' -accountSid 'S-1-5-32-544' -recurse # Give the user LocalAccess, LocalLaunch and LocalActivation permissions on the DCOM component (needed for scanning features) Add-DCOMPermissions -dcomAppId $appId -accountName $formattedAccountName -accountSid $accountSid -accessPermissions 'LocalAccess' -launchPermissions 'LocalLaunch', 'LocalActivation' } # Give the user permissions to read services (needed for scanning services) Add-SDReadClause -service 'scmanager' -accountName $formattedAccountName -accountSid $accountSid # Allow access to netBIOS from other networks (required if your scanning server is in another subnet) Set-RegistryProperty -path 'HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' -name 'AllowNBToInternet' -type 'DWORD' -value 34 # Enable WMI/DCOM through firewall Write-Log -data 'Checking if WMI/DCOM is allowed through the firewall...' -type 'Verbose' if (Get-NetFirewallRule -DisplayGroup 'Windows Management Instrumentation (WMI)' | Where-Object { $_.Enabled -ne 'True' }) { Write-Log -data 'WMI/DCOM is not allowed through the firewall, enabling the rules...' -type 'Basic' -eventType 'Information' -eventID 35 Set-NetFirewallRule -DisplayGroup 'Windows Management Instrumentation (WMI)' -Enabled True } else { Write-Log -data 'WMI/DCOM is allowed through the firewall, skipping.' -type 'Basic' -eventType 'Information' -eventID 36 } } # Function that configures SQL scanning function Add-SQLScanning { param ( [parameter(Mandatory = $true, Position = 0)] [string]$accountName, [parameter(Mandatory = $true, Position = 1)] [System.Security.Principal.SecurityIdentifier]$accountSid ) $sqlWmiNamespaces = @('Root\Microsoft\SqlServer') # Check if the parent namespace exists Write-Log -data 'Checking if the WMI namespace for SQL server ("Root\Microsoft\SqlServer") exists...' -type 'Verbose' if (-not (Find-WMINamespace -namespace 'Root\Microsoft\SqlServer')) { Write-Log -data 'No WMI namespaces for SQL could be found, skipping SQL scanning permissions.' -type 'Verbose' -eventType 'Information' -eventID 37 return } Write-Log -data 'SQL WMI namespace "Root\Microsoft\SqlServer" found.' -type 'Verbose' # Get SQL service namespaces $sqlWmiNamespaces += Get-WmiObject -Class '__Namespace' -Namespace 'Root\Microsoft\SqlServer' | Where-Object { $_.Name -match 'ComputerManagement[0-9]{1,2}' } | ForEach-Object { "Root\Microsoft\SqlServer\$($_.Name)" } foreach ($wmiNamespace in $sqlWmiNamespaces) { Add-WMIPermissions -namespace $wmiNamespace -accountName $accountName -accountSid $accountSid -permissions $wmiPermissions -enableInherit } } function Add-SccmScanning { param ( [parameter(Mandatory = $true, Position = 0)] [string]$accountName, [parameter(Mandatory = $true, Position = 1)] [System.Security.Principal.SecurityIdentifier]$accountSid ) $sccmWmiNamespaces = @('Root\SMS') # Check if the parent namespace exists Write-Log -data 'Checking if the WMI namespace for SCCM ("Root\SMS") exists...' -type 'Verbose' if (-not (Find-WMINamespace -namespace 'Root\SMS')) { Write-Log -data 'No WMI namespaces for SCCM could be found, skipping SMS scanning permissions.' -type 'Verbose' -eventType 'Information' -eventID 37 return } Write-Log -data 'SCCM WMI namespace "Root\SMS" found.' -type 'Verbose' # Get SCCM site namespaces $sccmWmiNamespaces += Get-WmiObject -Class '__Namespace' -Namespace 'Root\SMS' | Where-Object { $_.Name -match 'site_' } | ForEach-Object { "Root\SMS\$($_.Name)" } foreach ($wmiNamespace in $sccmWmiNamespaces) { Add-WMIPermissions -namespace $wmiNamespace -accountName $accountName -accountSid $accountSid -permissions $wmiPermissions -enableInherit } } ### Initialization ### $ErrorActionPreference = 'Continue' # Initialize log file if ($logLevel -ne 0) { Set-LogFile } if (-not $noEventLog) { Set-EventLog } # For later comparison of accounts, use SAMAccountName $formattedAccountName = Format-AccountName -accountName $accountName # Get the SID of the account $accountSid = Get-AccountSID -AccountName $formattedAccountName ### Windows scanning ### Add-WindowsScanning -accountName $formattedAccountName -accountSid $accountSid