Tools/Test-ADEHMPermission.ps1
|
#requires -Version 5.1 <# .SYNOPSIS Complete ADEHM permission diagnostic against the domain controllers. .DESCRIPTION Run from an ADMIN console (your own account), providing the service account credentials via -Credential. For each DC, the script captures AT THE SAME MOMENT: 1. The functional test, step by step (WinRM -> CIM session -> OS read -> service read), to pinpoint the failing link; 2. The actual DACL of the root\cimv2 WMI namespace for the service account and the dedicated group; 3. The denials recorded in the DC's WMI-Activity log (0x80041003 = WBEM_E_ACCESS_DENIED) with User, Namespace and Operation; 4. The Service Control Manager SDDL and whether the group is present. The output is a text report to copy-paste in full. Note: root\interop denials in the WMI-Activity log are benign, non-blocking probes of the WinRM WMI plugin - field-verified. They do not require any grant. .EXAMPLE .\Test-ADEHMPermission.ps1 -Credential (Get-Credential DOMAIN\svc-adehm) ` -GroupName 'GRP-AD-Monitoring' -ConfigPath ..\Config\ADEHM.config.psd1 #> [CmdletBinding()] param( [Parameter(Mandatory)] [System.Management.Automation.PSCredential]$Credential, [string]$GroupName, [string[]]$DomainControllers, [string]$ConfigPath ) $ErrorActionPreference = 'Continue' # --- DC list resolution ------------------------------------------------ if (-not $DomainControllers) { if (-not $ConfigPath) { throw 'Provide -DomainControllers or -ConfigPath.' } $DomainControllers = (Import-PowerShellDataFile -Path $ConfigPath).DomainControllers } # --- Resolve the SIDs to watch ------------------------------------------ $sids = @{} $acctName = $Credential.UserName -replace '^.*\\', '' try { $sids[(New-Object System.Security.Principal.NTAccount($Credential.UserName)).Translate([System.Security.Principal.SecurityIdentifier]).Value] = "Account : $($Credential.UserName)" } catch { Write-Warning "Cannot resolve account SID: $($_.Exception.Message)" } if ($GroupName) { try { $g = New-Object System.Security.Principal.NTAccount($GroupName) $sids[$g.Translate([System.Security.Principal.SecurityIdentifier]).Value] = "Group : $GroupName" } catch { Write-Warning "Cannot resolve group SID: $($_.Exception.Message)" } } $sep = '=' * 78 foreach ($dc in $DomainControllers) { Write-Host "`n$sep" -ForegroundColor Cyan Write-Host "DC : $dc ($(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))" -ForegroundColor Cyan Write-Host $sep -ForegroundColor Cyan # ================================================================== # 1. FUNCTIONAL TEST WITH THE SERVICE ACCOUNT, STEP BY STEP # ================================================================== Write-Host "`n[1] Functional test (identity: $($Credential.UserName))" -ForegroundColor Yellow # 1a. Authenticated WinRM try { $null = Test-WSMan -ComputerName $dc -Credential $Credential -Authentication Default -ErrorAction Stop Write-Host ' 1a. Authenticated WinRM .......... OK' } catch { Write-Host " 1a. Authenticated WinRM .......... FAILED: $($_.Exception.Message)" -ForegroundColor Red Write-Host ' -> Blocked BEFORE WMI (network logon right or RootSDDL). Next steps skipped.' continue } # 1b. CIM session opening $cim = $null try { $cim = New-CimSession -ComputerName $dc -Credential $Credential -ErrorAction Stop Write-Host ' 1b. CIM session .................. OK' } catch { Write-Host " 1b. CIM session .................. FAILED: $($_.Exception.Message)" -ForegroundColor Red } # 1c. Win32_OperatingSystem read if ($cim) { try { $os = Get-CimInstance -CimSession $cim -ClassName Win32_OperatingSystem -ErrorAction Stop Write-Host " 1c. Win32_OperatingSystem read ... OK ($($os.Caption))" } catch { Write-Host " 1c. Win32_OperatingSystem read ... FAILED: $($_.Exception.Message)" -ForegroundColor Red } # 1d. Win32_Service read try { $svc = Get-CimInstance -CimSession $cim -ClassName Win32_Service -Filter "Name='NTDS'" -ErrorAction Stop if ($svc -and $svc.State) { Write-Host " 1d. Win32_Service read ........... OK (NTDS = $($svc.State))" } else { Write-Host ' 1d. Win32_Service read ........... FAILED: query accepted but NTDS INVISIBLE (the service security descriptor does not grant read to the account/group)' -ForegroundColor Red } } catch { Write-Host " 1d. Win32_Service read ........... FAILED: $($_.Exception.Message)" -ForegroundColor Red } Remove-CimSession $cim -ErrorAction SilentlyContinue } # ================================================================== # 2. ACTUAL root\cimv2 DACL (same instant, via YOUR admin account) # ================================================================== Write-Host "`n[2] WMI namespace DACL (ADEHM account/group entries)" -ForegroundColor Yellow foreach ($ns in @('root/cimv2')) { try { $dacl = Invoke-Command -ComputerName $dc -ScriptBlock { param($ns) (Invoke-WmiMethod -Namespace $ns -Path '__SystemSecurity=@' -Name GetSecurityDescriptor).Descriptor.DACL | ForEach-Object { [PSCustomObject]@{ SID = $_.Trustee.SIDString Name = '{0}\{1}' -f $_.Trustee.Domain, $_.Trustee.Name AceType = $_.AceType # 0 = Allow, 1 = Deny EnableAccount = [bool]($_.AccessMask -band 0x1) RemoteEnable = [bool]($_.AccessMask -band 0x20) } } } -ArgumentList $ns -ErrorAction Stop $relevant = $dacl | Where-Object { $_.SID -in $sids.Keys } if ($relevant) { foreach ($r in $relevant) { $type = if ($r.AceType -eq 1) { 'DENY !!' } else { 'Allow' } Write-Host (" {0,-12} {1} [{2}] EnableAccount={3} RemoteEnable={4}" -f $ns, $sids[$r.SID], $type, $r.EnableAccount, $r.RemoteEnable) } } else { Write-Host " $ns NO entry for the account or the group on this DC." -ForegroundColor Red } } catch { Write-Host " $ns Cannot read: $($_.Exception.Message)" -ForegroundColor Red } } # ================================================================== # 3. DENIALS RECORDED IN THE DC WMI LOG (last 15 minutes) # ================================================================== Write-Host "`n[3] DC WMI-Activity log (0x80041003 denials, last 15 minutes)" -ForegroundColor Yellow try { $events = Invoke-Command -ComputerName $dc -ScriptBlock { param($acct) Get-WinEvent -LogName 'Microsoft-Windows-WMI-Activity/Operational' -MaxEvents 200 -ErrorAction SilentlyContinue | Where-Object { $_.TimeCreated -gt (Get-Date).AddMinutes(-15) -and ($_.Message -match '0x80041003' -or $_.Message -match [regex]::Escape($acct)) } | Select-Object -First 5 TimeCreated, Message } -ArgumentList $acctName -ErrorAction Stop if ($events) { foreach ($e in $events) { Write-Host " --- $($e.TimeCreated) ---" $compact = ($e.Message -split "`n" | Where-Object { $_ -match 'User|Namespace|Operation|ResultCode|ClientProcessId' }) -join '; ' if (-not $compact) { $compact = $e.Message.Substring(0, [math]::Min(400, $e.Message.Length)) } Write-Host " $compact" } Write-Host ' Reminder: root\interop denials are benign non-blocking probes.' } else { Write-Host ' No WMI denial recorded in the last 15 minutes.' } } catch { Write-Host " Cannot read the log: $($_.Exception.Message)" -ForegroundColor Red } # ================================================================== # 4. SERVICE CONTROL MANAGER SDDL (right to enumerate services) # ================================================================== Write-Host "`n[4] Service Control Manager SDDL" -ForegroundColor Yellow try { $scSddl = Invoke-Command -ComputerName $dc -ScriptBlock { ((& sc.exe sdshow scmanager) | Where-Object { $_ -match '\S' }) -join '' } -ErrorAction Stop $found = $false foreach ($s in $sids.Keys) { if ($scSddl -match [regex]::Escape(";;;$s)")) { Write-Host " $($sids[$s]) PRESENT in the SCM SDDL" $found = $true } } if (-not $found) { Write-Host ' Neither the account nor the group appears in the SCM SDDL.' -ForegroundColor Red } Write-Host " Raw SDDL: $scSddl" } catch { Write-Host " Cannot read: $($_.Exception.Message)" -ForegroundColor Red } } Write-Host "`n$sep" Write-Host 'Diagnostic complete. Copy-paste the FULL output above.' -ForegroundColor Green Write-Host $sep |