GPO-Anzeigename (für die Quellenauflösung in den Einstellungen)
$script:GpoNames = @{}
#endregion
# ============================================================================
#region Scope-Daten extrahieren (Benutzer / Computer)
# ============================================================================
function Get-ScopeInfo($node, [string]$prefix, [string]$label) {
if ($null -eq $node) { return $null }
$gpos = @()
foreach ($g in (AsArray $node.GPO)) {
$guid = txt $g.Path.Identifier
# Ein GPO kann an mehreren Containern verknüpft sein -> alle Links auswerten
$links = AsArray $g.Link
$appliedOrder = 0
$linkEnabled = ''; $noOverride = ''; $somPaths = @(); $linkOrder = ''
foreach ($l in $links) {
$v = 0; [void][int]::TryParse((txt $l.AppliedOrder), [ref]$v)
if ($v -gt $appliedOrder) { $appliedOrder = $v }
$somPaths += (txt $l.SOMPath)
if (-not $linkOrder) { $linkOrder = txt $l.LinkOrder }
if (-not $linkEnabled) { $linkEnabled = txt $l.Enabled }
if ((txt $l.NoOverride) -match '(?i)true') { $noOverride = 'true' }
}
$o = [pscustomobject]@{
Name = txt $g.Name
Guid = $guid
VerAD = txt $g.VersionDirectory
VerSysvol = txt $g.VersionSysvol
Enabled = txt $g.Enabled
IsValid = txt $g.IsValid
FilterAllowed= txt $g.FilterAllowed
AccessDenied = txt $g.AccessDenied
SOMPath = ($somPaths -join '; ')
LinkOrder = $linkOrder
AppliedOrder = $appliedOrder
LinkEnabled = $linkEnabled
NoOverride = $noOverride
SecFilter = ((AsArray $g.SecurityFilter) | ForEach-Object { txt $_ }) -join '; '
Extensions = ((AsArray $g.ExtensionName) | ForEach-Object { txt $_ }) -join ', '
}
# Anzeigename je GUID merken — ein aufgeloester Name schlaegt eine blosse GUID
if ($guid) {
$existing = $script:GpoNames[$guid]
if (-not $existing -or $existing -eq $guid) { $script:GpoNames[$guid] = $o.Name }
}
$gpos += $o
}
$groups = @()
foreach ($sg in (AsArray $node.SecurityGroup)) {
$groups += [pscustomobject]@{ Name = txt $sg.Name; SID = txt $sg.SID }
}
$soms = @()
foreach ($s in (AsArray $node.SearchedSOM)) {
$soms += [pscustomobject]@{
Path = txt $s.Path
Type = txt $s.Type
Order = [int]('0' + (txt $s.Order))
Blocks = txt $s.BlocksInheritance
Blocked= txt $s.Blocked
Reason = txt $s.Reason
}
}
$extStatus = @()
foreach ($e in (AsArray $node.ExtensionStatus)) {
$extStatus += [pscustomobject]@{
Name = txt $e.Name
Guid = txt $e.Identifier
Begin = ParseIsoDate (txt $e.BeginTime)
End = ParseIsoDate (txt $e.EndTime)
Status = txt $e.LoggingStatus
Error = txt $e.Error
}
}
# Ereignisdetails (ein SinglePass pro Erhebung)
$spe = $null
foreach ($ed in (AsArray $node.EventsDetails)) {
foreach ($p in (AsArray $ed.SinglePassEventsDetails)) { if ($null -eq $spe) { $spe = $p } }
}
$events = @()
$extTimes = @()
$attrs = @{}
if ($spe) {
foreach ($a in $spe.Attributes) { $attrs[$a.Name] = $a.Value }
foreach ($er in (AsArray $spe.EventRecord)) {
$raw = txt $er.EventXml
$id = ''
$time = $null
$m = [regex]::Match($raw, '(\d+) ')
if ($m.Success) { $id = $m.Groups[1].Value }
$m = [regex]::Match($raw, "SystemTime='([^']+)'")
if ($m.Success) { $time = ParseIsoDate $m.Groups[1].Value }
$events += [pscustomobject]@{
Id = $id
Time = $time
Desc = (txt $er.EventDescription).Trim()
}
}
foreach ($et in (AsArray $spe.ExtensionProcessingTime)) {
$extTimes += [pscustomobject]@{
Name = txt $et.ExtensionName
Guid = txt $et.ExtensionGuid
Ms = txt $et.ElapsedTimeInMilliseconds
At = ParseIsoDate (txt $et.ProcessedTimeStamp)
}
}
}
$extData = @()
foreach ($ed in (AsArray $node.ExtensionData)) {
$extEl = $null; $extName = ''
foreach ($c in $ed.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
if ($c.LocalName -eq 'Extension') { $extEl = $c }
elseif ($c.LocalName -eq 'Name') { $extName = $c.InnerText }
}
if ($extEl) { $extData += [pscustomobject]@{ El = $extEl; Title = $extName } }
}
$applied = @($gpos | Where-Object { $_.AppliedOrder -gt 0 } | Sort-Object AppliedOrder)
$denied = @($gpos | Where-Object { $_.AppliedOrder -le 0 })
return [pscustomobject]@{
Prefix = $prefix
Label = $label
Name = txt $node.Name
Domain = txt $node.Domain
Som = txt $node.SOM
Site = txt $node.Site
SlowLink = txt $node.SlowLink
Version = txt $node.Version
Gpos = $gpos
Applied = $applied
Denied = $denied
Groups = $groups
Soms = @($soms | Sort-Object Order)
ExtStatus = $extStatus
ExtTimes = $extTimes
Events = $events
Attrs = $attrs
ExtData = $extData
}
}
$uInfo = Get-ScopeInfo $rsop.UserResults 'u' 'Benutzer'
$cInfo = Get-ScopeInfo $rsop.ComputerResults 'c' 'Computer'
if (-not $uInfo -and -not $cInfo) {
throw 'Die XML-Datei enthält weder Benutzer- noch Computerergebnisse.'
}
# Nachtrag: GPOs, deren Name nur die GUID ist, bekommen den in einem anderen
# Bereich aufgelösten Anzeigenamen (z. B. kennt der Computerbereich den Namen,
# der Benutzerbereich nur die GUID).
foreach ($s in @($uInfo, $cInfo)) {
if (-not $s) { continue }
foreach ($g in $s.Gpos) {
if ($g.Name -match '^\{[0-9A-Fa-f-]+\}$' -and $g.Guid) {
$better = $script:GpoNames[$g.Guid]
if ($better -and $better -ne $g.Name) { $g.Name = $better }
}
}
}
#endregion
# ============================================================================
#region HTML-Bausteine
# ============================================================================
function New-Kv([string]$icon, [string]$label, [string]$value, [string]$sub) {
$s = ''
if ($sub) { $s = '' + (esc $sub) + ' ' }
return '' + (ic $icon) + ' ' +
(esc $label) + ' ' + (esc $value) + $s + '
'
}
function Get-DeniedReason($g) {
if ($g.AccessDenied -match '(?i)true') { return (badge 'err' 'Zugriff verweigert') }
if ($g.IsValid -match '(?i)false') { return (badge 'mut' 'Nicht angewendet (leer)') }
if ($g.FilterAllowed -match '(?i)false') { return (badge 'warn' 'Durch Filter verweigert') }
if ($g.LinkEnabled -match '(?i)false') { return (badge 'warn' 'Verknüpfung deaktiviert') }
if ($g.Enabled -match '(?i)false') { return (badge 'warn' 'GPO deaktiviert') }
return (badge 'mut' 'Nicht angewendet (leer)')
}
function New-GpoRows($list, [bool]$appliedView) {
$rows = ''
foreach ($g in $list) {
$nameCell = '' + (esc $g.Name) + ' '
if ($g.Guid -and $g.Guid -ne $g.Name -and $g.Guid -ne 'LocalGPO') {
$nameCell += '' + (esc $g.Guid) + ' '
}
if ($g.SecFilter) {
$nameCell += 'Sicherheitsfilter: ' + (esc $g.SecFilter) + ' '
}
if ($appliedView -and $g.Extensions) {
$nameCell += 'Erweiterungen: ' + (esc $g.Extensions) + ' '
}
$ver = (esc $g.VerAD) + ' / ' + (esc $g.VerSysvol)
$enforced = if ($g.NoOverride -match '(?i)true') { badge 'warn' 'Erzwungen' } else { 'Nein' }
if ($appliedView) {
$status = badge 'ok' 'Angewendet'
$rows += '' + $g.AppliedOrder + ' ' + $nameCell + ' ' +
(esc $g.SOMPath) + ' ' + $ver + ' ' + $enforced +
' ' + $status + ' '
} else {
$rows += '' + $nameCell + ' ' + (esc $g.SOMPath) + ' ' +
$ver + ' ' + (Get-DeniedReason $g) + ' '
}
}
return $rows
}
function clip([string]$s) {
# Kürzt Monsterwerte (z. B. Zertifikats-Blobs), damit die Tabellen lesbar bleiben.
if ($null -eq $s) { return '' }
if ($s.Length -gt 96) { return $s.Substring(0, 96) + ' …' }
return $s
}
function Get-LeafLines($el, $skipNode) {
# Sammelt lesbare "Name: Wert"-Zeilen aus einem Einstellungs-Element —
# generisch, damit alle Erweiterungstypen (Security, Registry, ...) funktionieren.
$lines = @()
foreach ($c in $el.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
if ($c.LocalName -in @('GPO','Precedence')) { continue }
if ($skipNode -and [object]::ReferenceEquals($c, $skipNode)) { continue }
$hasChildEl = $false
foreach ($cc in $c.ChildNodes) {
if ($cc.NodeType -eq [System.Xml.XmlNodeType]::Element) { $hasChildEl = $true; break }
}
if (-not $hasChildEl) {
$lines += ('' + (esc $c.LocalName) + ': ' + (esc (clip (boolDe $c.InnerText))) + ' ')
} else {
$parts = @()
foreach ($d in $c.SelectNodes('.//*')) {
$leaf = $true
foreach ($dd in $d.ChildNodes) {
if ($dd.NodeType -eq [System.Xml.XmlNodeType]::Element) { $leaf = $false; break }
}
if ($leaf -and $d.InnerText -ne '') { $parts += (esc (clip (boolDe $d.InnerText))) }
}
$lines += ('' + (esc $c.LocalName) + ': ' + ($parts -join ' · ') + ' ')
}
}
return ($lines -join '')
}
function Get-SettingName($el) {
foreach ($c in $el.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.LocalName -eq 'Name') {
$hasChildEl = $false
foreach ($cc in $c.ChildNodes) {
if ($cc.NodeType -eq [System.Xml.XmlNodeType]::Element) { $hasChildEl = $true; break }
}
if (-not $hasChildEl) { return @{ Text = $c.InnerText; Node = $c } }
}
}
foreach ($cand in @('Display/Name','KeyName','Path')) {
$n = $el.SelectSingleNode(('./*[local-name()="{0}"]' -f ($cand -replace '/.*','')))
if ($cand -eq 'Display/Name') {
$disp = $null
foreach ($c in $el.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.LocalName -eq 'Display') { $disp = $c; break }
}
if ($disp) {
foreach ($c in $disp.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.LocalName -eq 'Name' -and $c.InnerText) {
return @{ Text = $c.InnerText; Node = $null }
}
}
}
} elseif ($n -and $n.InnerText) {
return @{ Text = $n.InnerText; Node = $n }
}
}
return @{ Text = ''; Node = $null }
}
$ExtTypeNames = @{
'SecuritySettings' = 'Sicherheitseinstellungen'
'RegistrySettings' = 'Registrierung / Administrative Vorlagen'
'ScriptsSettings' = 'Skripte'
'PublicKeySettings' = 'Richtlinien für öffentliche Schlüssel'
'AuditSettings' = 'Erweiterte Überwachungsrichtlinie'
'FolderRedirectionSettings' = 'Ordnerumleitung'
'SoftwareInstallationSettings' = 'Softwareinstallation'
'DriveMapSettings' = 'Laufwerkzuordnungen'
'EFSSettings' = 'EFS-Einstellungen'
'WLanSvcSettings' = 'WLAN-Richtlinien'
}
$GroupNames = @{
'Account' = 'Kontorichtlinien'
'SecurityOptions' = 'Sicherheitsoptionen'
'UserRightsAssignment' = 'Zuweisen von Benutzerrechten'
'EventLog' = 'Ereignisprotokoll'
'RestrictedGroups' = 'Eingeschränkte Gruppen'
'SystemServices' = 'Systemdienste'
'Registry' = 'Registrierungs-ACLs'
'File' = 'Dateisystem-ACLs'
'Policy' = 'Richtlinien'
'RegistrySetting' = 'Registrierungseinstellungen'
'Auditing' = 'Überwachung'
'Script' = 'Skripte'
}
function New-SettingsHtml($info) {
if (-not $info.ExtData -or $info.ExtData.Count -eq 0) {
return '' + (ic 'sliders') + ' ' +
'Für diesen Bereich enthält der Ergebnissatz keine Detaileinstellungen.
'
}
$h = ''
foreach ($item in $info.ExtData) {
$ext = $item.El
$typeRaw = $ext.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
$type = ($typeRaw -replace '^.*:', '')
$typeName = $ExtTypeNames[$type]
if (-not $typeName) { $typeName = $type }
$foreignAuthority = ($item.Title -match 'different authority')
# Einstellungen nach Elementnamen gruppieren; "Blocked" ist Meta-Information
$groups = [ordered]@{}
$blocked = ''
foreach ($c in $ext.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
if ($c.LocalName -eq 'Blocked') { $blocked = $c.InnerText; continue }
if (-not $groups.Contains($c.LocalName)) { $groups[$c.LocalName] = New-Object System.Collections.ArrayList }
$null = $groups[$c.LocalName].Add($c)
}
if ($groups.Count -eq 0) {
# Kein Inhalt: nur erwähnen, wenn der Bereich anderweitig gesteuert wird
if ($foreignAuthority -or $blocked -match '(?i)true') {
$h += '' + (ic 'alert') + ' ' +
(esc $typeName) + ': Dieser Bereich wird von einer anderen Autorität gesteuert ' +
'(z. B. MDM/Intune) — klassische GPO-Einstellungen sind hier blockiert.
'
}
continue
}
$blockedBadge = ''
if ($blocked -match '(?i)true') { $blockedBadge = ' ' + (badge 'warn' 'Blockiert') }
$h += '' + (ic 'sliders') + ' ' + (esc $typeName) + $blockedBadge + ' '
foreach ($gName in $groups.Keys) {
$friendly = $GroupNames[$gName]
if (-not $friendly) { $friendly = $gName }
$rows = ''
foreach ($setting in $groups[$gName]) {
$nameInfo = Get-SettingName $setting
$sName = $nameInfo.Text
if (-not $sName) { $sName = $gName }
$vals = Get-LeafLines $setting $nameInfo.Node
if (-not $vals) { $vals = '— ' }
$srcGuid = ''
foreach ($c in $setting.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.LocalName -eq 'GPO') {
$srcGuid = $c.InnerText.Trim(); break
}
}
# GPO-Kind enthält Identifier+Domain hintereinander — Identifier isolieren
$mm = [regex]::Match($srcGuid, '\{[0-9A-Fa-f-]+\}|LocalGPO')
if ($mm.Success) { $srcGuid = $mm.Value }
$srcName = $script:GpoNames[$srcGuid]
if (-not $srcName) { $srcName = $srcGuid }
if (-not $srcName) { $srcName = '—' }
$prec = ''
foreach ($c in $setting.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.LocalName -eq 'Precedence') {
$prec = $c.InnerText; break
}
}
if (-not $prec) { $prec = '—' }
$rows += '' + (esc $sName) + ' ' + $vals + ' ' +
(esc $srcName) + ' ' + (esc $prec) + ' '
}
$h += '' + (esc $friendly) +
' Wert Quell-GPO Rang ' +
$rows + '
'
}
}
if (-not $h) {
$h = '' + (ic 'sliders') + ' ' +
'Für diesen Bereich enthält der Ergebnissatz keine Detaileinstellungen.
'
}
return $h
}
function New-Section([string]$id, [string]$icon, [string]$title, [string]$countBadge, [string]$sub, [string]$body) {
$cb = ''
if ($countBadge) { $cb = '' + (esc $countBadge) + ' ' }
$sb = ''
if ($sub) { $sb = '' + (esc $sub) + '
' }
return '' + (ic $icon) + ' ' +
(esc $title) + $cb + ' ' + $sb + $body + ' '
}
function New-ScopeHtml($info) {
if ($null -eq $info) { return @{ Html = ''; Nav = @() } }
$p = $info.Prefix
$nav = @()
$H = New-Object System.Text.StringBuilder
# --- Zusammenfassung -----------------------------------------------------
$slow = if ($info.SlowLink -match '(?i)true') { 'Ja (langsame Verbindung)' } else { 'Nein' }
$kv = ''
$kv += New-Kv 'user' 'Konto' $info.Name ''
$kv += New-Kv 'globe' 'Domäne' $info.Domain ''
$kv += New-Kv 'target' 'Container (SOM)' $info.Som ''
$kv += New-Kv 'home' 'AD-Standort' $info.Site ''
$kv += New-Kv 'bolt' 'Langsame Verbindung' $slow ''
if ($info.Attrs['DomainControllerName']) {
$kv += New-Kv 'pc' 'Domänencontroller' $info.Attrs['DomainControllerName'] $info.Attrs['DomainControllerIPAddress']
}
if ($info.Attrs['PolicyElapsedTimeInMilliseconds']) {
$kv += New-Kv 'clock' 'Verarbeitungsdauer' ($info.Attrs['PolicyElapsedTimeInMilliseconds'] + ' ms') $info.Attrs['ProcessingTrigger']
}
$errW = ''
if ($info.Attrs.ContainsKey('ErrorCount')) {
$errW = $info.Attrs['ErrorCount'] + ' Fehler · ' + $info.Attrs['WarningCount'] + ' Warnungen'
$kv += New-Kv 'alert' 'Fehler / Warnungen' $errW ''
}
$kv += '
'
$null = $H.Append((New-Section "$p-sum" ($(if ($p -eq 'u') { 'user' } else { 'pc' })) "$($info.Label) — Zusammenfassung" '' 'Identität, Kontext und Verarbeitungskennzahlen dieses Bereichs.' $kv))
$nav += @{ Code = "$($p.ToUpper())1"; Title = 'Zusammenfassung'; Tag = $info.Name; Id = "$p-sum" }
# --- GPOs ----------------------------------------------------------------
$body = ''
if ($info.Applied.Count -gt 0) {
$body += '' + (ic 'shield') + ' Angewendet (' + $info.Applied.Count + ') '
$body += 'Rang Gruppenrichtlinienobjekt Verknüpft mit Version (AD/Sysvol) Erzwungen Status '
$body += New-GpoRows $info.Applied $true
$body += '
'
} else {
$body += '' + (ic 'layers') + ' Es wurden keine Gruppenrichtlinienobjekte angewendet.
'
}
if ($info.Denied.Count -gt 0) {
$body += '' + (ic 'alert') + ' Nicht angewendet (' + $info.Denied.Count + ') '
$body += 'Gruppenrichtlinienobjekt Verknüpft mit Version (AD/Sysvol) Grund '
$body += New-GpoRows $info.Denied $false
$body += '
'
}
$null = $H.Append((New-Section "$p-gpo" 'layers' 'Gruppenrichtlinienobjekte' ("{0} / {1}" -f $info.Applied.Count, $info.Gpos.Count) 'Angewendete und verworfene GPOs samt Verknüpfungsort, Version und Filterstatus.' $body))
$nav += @{ Code = "$($p.ToUpper())2"; Title = 'Richtlinienobjekte'; Tag = ("{0} angewendet · {1} verworfen" -f $info.Applied.Count, $info.Denied.Count); Id = "$p-gpo" }
# --- Einstellungen -------------------------------------------------------
$null = $H.Append((New-Section "$p-set" 'sliders' 'Richtlinieneinstellungen' '' 'Die tatsächlich wirksamen Einstellungen je Client-Erweiterung, mit Quell-GPO und Rang.' (New-SettingsHtml $info)))
$nav += @{ Code = "$($p.ToUpper())3"; Title = 'Einstellungen'; Tag = 'wirksame Werte je Erweiterung'; Id = "$p-set" }
# --- Sicherheitsgruppen --------------------------------------------------
$rows = ''
$i = 0
foreach ($g in $info.Groups) {
$i++
$rows += '' + $i + ' ' + (esc $g.Name) +
' ' + (esc $g.SID) + ' '
}
$body = ''
$null = $H.Append((New-Section "$p-grp" 'users' 'Sicherheitsgruppen' ([string]$info.Groups.Count) 'Gruppenmitgliedschaften, die bei der Richtlinienauswertung wirksam waren.' $body))
$nav += @{ Code = "$($p.ToUpper())4"; Title = 'Sicherheitsgruppen'; Tag = ("{0} Mitgliedschaften" -f $info.Groups.Count); Id = "$p-grp" }
# --- Durchsuchte Container ----------------------------------------------
$rows = ''
foreach ($s in $info.Soms) {
$blocks = if ($s.Blocks -match '(?i)true') { badge 'warn' 'Ja' } else { 'Nein' }
$blocked = if ($s.Blocked -match '(?i)true') { badge 'err' 'Ja' } else { 'Nein' }
$rows += '' + $s.Order + ' ' + (esc $s.Path) +
' ' + (esc $s.Type) + ' ' + $blocks + ' ' + $blocked +
' ' + (esc $s.Reason) + ' '
}
$body = 'Reihenfolge Pfad Typ Blockiert Vererbung Blockiert Modus ' + $rows + '
'
$null = $H.Append((New-Section "$p-som" 'target' 'Durchsuchte Container (SOM)' ([string]$info.Soms.Count) 'Verwaltungsbereiche (Lokal, Standort, Domäne, OU), die nach verknüpften GPOs durchsucht wurden.' $body))
$nav += @{ Code = "$($p.ToUpper())5"; Title = 'Container (SOM)'; Tag = ("{0} durchsucht" -f $info.Soms.Count); Id = "$p-som" }
# --- Komponenten / Verarbeitung -----------------------------------------
$body = ''
$rows = ''
foreach ($e in $info.ExtStatus) {
$span = FmtDate $e.Begin
if ($e.End) {
if ($e.Begin -and $e.End.Date -eq $e.Begin.Date) { $span += ' → ' + $e.End.ToString('HH:mm:ss') }
else { $span += ' → ' + (FmtDate $e.End) }
}
$st = if ($e.Status -eq 'Complete') { badge 'ok' 'Vollständig' } else { badge 'warn' $e.Status }
$err = if ($e.Error -eq '0') { badge 'ok' 'Kein Fehler' } else { badge 'err' ("Fehler " + $e.Error) }
$rows += '' + (esc $e.Name) + '' + (esc $e.Guid) +
' ' + $span + ' ' + $st + ' ' + $err + ' '
}
if ($rows) {
$body += '' + (ic 'gear') + ' Client-Erweiterungen (Status) '
$body += 'Erweiterung Zeitraum Protokollstatus Ergebnis ' + $rows + '
'
}
$rows = ''
foreach ($t in $info.ExtTimes) {
$rows += '' + (esc $t.Name) + '' + (esc $t.Guid) +
' ' + (esc $t.Ms) + ' ms ' + (FmtDate $t.At) + ' '
}
if ($rows) {
$body += '' + (ic 'clock') + ' Verarbeitungszeiten (letzter Durchlauf) '
$body += 'Erweiterung Dauer Verarbeitet am ' + $rows + '
'
}
if ($info.Attrs.Count -gt 0) {
$a = $info.Attrs
$kv = '' + (ic 'bolt') + ' Letzter Verarbeitungslauf '
if ($a['ProcessingTrigger']) { $kv += New-Kv 'bolt' 'Auslöser' $a['ProcessingTrigger'] $a['ProcessingAppMode'] }
if ($a['LinkSpeedInKbps']) { $kv += New-Kv 'globe' 'Verbindung' ($a['LinkSpeedInKbps'] + ' KBit/s') ('Schwellenwert ' + $a['SlowLinkThresholdInKbps'] + ' KBit/s') }
if ($a['PolicyProcessingMode']) { $kv += New-Kv 'gear' 'Loopback-Modus' $a['PolicyProcessingMode'] '' }
if ($a.ContainsKey('ErrorCount')) { $kv += New-Kv 'alert' 'Fehler / Warnungen' ($a['ErrorCount'] + ' / ' + $a['WarningCount']) '' }
$kv += '
'
$body = $kv + $body
}
if (-not $body) {
$body = '' + (ic 'gear') + ' Keine Verarbeitungsdaten im Ergebnissatz enthalten.
'
}
$null = $H.Append((New-Section "$p-proc" 'gear' 'Verarbeitung & Komponenten' '' 'Status und Laufzeiten der Gruppenrichtlinien-Clienterweiterungen.' $body))
$nav += @{ Code = "$($p.ToUpper())6"; Title = 'Verarbeitung'; Tag = ("{0} Erweiterungen" -f $info.ExtStatus.Count); Id = "$p-proc" }
# --- Ereignisse ----------------------------------------------------------
$body = ''
if ($info.Events.Count -gt 0) {
$rows = ''
$i = 0
foreach ($ev in $info.Events) {
$i++
$t = if ($ev.Time) { $ev.Time.ToLocalTime().ToString('HH:mm:ss.fff') } else { '—' }
$rows += '' + $i + ' ' + $t + ' ' +
(esc $ev.Id) + ' ' + (esc $ev.Desc) + ' '
}
$body = '' + (ic 'list') + ' ' + $info.Events.Count +
' Ereignisse des Verarbeitungslaufs anzeigen› ' +
'# Zeit Ereignis-ID Beschreibung ' +
$rows + '
'
} else {
$body = '' + (ic 'list') + ' Keine Ereignisse im Ergebnissatz enthalten.
'
}
$null = $H.Append((New-Section "$p-evt" 'list' 'Ereignisprotokoll' ([string]$info.Events.Count) 'Operatives Protokoll (Microsoft-Windows-GroupPolicy) des letzten Verarbeitungslaufs.' $body))
$nav += @{ Code = "$($p.ToUpper())7"; Title = 'Ereignisse'; Tag = ("{0} Einträge" -f $info.Events.Count); Id = "$p-evt" }
return @{ Html = $H.ToString(); Nav = $nav }
}
#endregion
# ============================================================================
#region Seite zusammensetzen
# ============================================================================
$uPart = New-ScopeHtml $uInfo
$cPart = New-ScopeHtml $cInfo
$hostName = ''
if ($cInfo) { $hostName = ($cInfo.Name -replace '\$$','' -replace '^.*\\','') }
elseif ($uInfo -and $uInfo.Attrs['DomainControllerName']) { $hostName = $uInfo.Attrs['DomainControllerName'] }
if (-not $hostName -and $env:COMPUTERNAME) { $hostName = $env:COMPUTERNAME }
$domain = if ($cInfo) { $cInfo.Domain } elseif ($uInfo) { $uInfo.Domain } else { '' }
function NavItems($items) {
$h = ''
foreach ($n in $items) {
$h += '' + $n.Code +
' ' + (esc $n.Title) + ' ' +
(esc $n.Tag) + ' '
}
return $h
}
$railNav = ''
if ($uInfo) {
$railNav += ' Benutzer
' + (NavItems $uPart.Nav)
}
if ($cInfo) {
$railNav += ' Computer
' + (NavItems $cPart.Nav)
}
# --- Deckblatt-Kacheln
$cards = ''
if ($uInfo) {
$cards += '' + (ic 'layers') + ' ' +
'' + $uInfo.Applied.Count + ' Benutzer ' +
'Angewendete GPOs von ' + $uInfo.Gpos.Count +
' verknüpften · ' + (esc $uInfo.Name) + ' '
}
if ($cInfo) {
$cards += '' + (ic 'layers') + ' ' +
'' + $cInfo.Applied.Count + ' Computer ' +
'Angewendete GPOs von ' + $cInfo.Gpos.Count +
' verknüpften · ' + (esc $cInfo.Name) + ' '
}
$errSum = 0; $warnSum = 0; $msSum = 0
foreach ($s in @($uInfo, $cInfo)) {
if ($s -and $s.Attrs.ContainsKey('ErrorCount')) { $errSum += [int]$s.Attrs['ErrorCount'] }
if ($s -and $s.Attrs.ContainsKey('WarningCount')) { $warnSum += [int]$s.Attrs['WarningCount'] }
if ($s -and $s.Attrs.ContainsKey('PolicyElapsedTimeInMilliseconds')) { $msSum += [int]$s.Attrs['PolicyElapsedTimeInMilliseconds'] }
}
$procTarget = if ($uInfo) { '#u-proc' } else { '#c-proc' }
$cards += '' + (ic 'alert') + ' ' +
'' + $errSum + ' / ' + $warnSum + ' Verarbeitung ' +
'Fehler / Warnungen über alle Verarbeitungsläufe '
$cards += '' + (ic 'clock') + ' ' +
'' + $msSum + ' ms Verarbeitung ' +
'Gesamtdauer Benutzer- und Computerrichtlinien '
$dcName = ''
foreach ($s in @($cInfo, $uInfo)) { if (-not $dcName -and $s -and $s.Attrs['DomainControllerName']) { $dcName = $s.Attrs['DomainControllerName'] } }
if ($dcName) {
$dcShort = ($dcName -split '\.')[0]
$grpTarget = if ($uInfo) { '#u-grp' } else { '#c-grp' }
$cards += '' + (ic 'globe') + ' ' +
'' + (esc $dcShort) + ' Infrastruktur ' +
'Domänencontroller ' + (esc $dcName) + ' '
$gCounts = @()
if ($uInfo) { $gCounts += [string]$uInfo.Groups.Count }
if ($cInfo) { $gCounts += [string]$cInfo.Groups.Count }
$cards += '' + (ic 'users') + ' ' +
'' + ($gCounts -join ' / ') + ' Sicherheit ' +
'Gruppenmitgliedschaften Benutzer / Computer '
}
$who = @()
if ($uInfo) { $who += 'Benutzer ' + (esc $uInfo.Name) + ' ' }
if ($cInfo) { $who += 'Computer ' + (esc $cInfo.Name) + ' ' }
$isLocal = (-not $domain -or $domain -eq 'Local')
$whereText = if ($isLocal) { ' (eigenständiges System, lokale Richtlinien)' }
else { ' in der Domäne ' + (esc $domain) + ' ' }
$thesis = 'Richtlinienergebnissatz für ' + ($who -join ' und ') + $whereText +
' — erhoben am ' + (FmtDate $readTime) + ' .'
$missing = ''
if (-not $cInfo) {
$missing = '' + (ic 'alert') + ' ' +
'Der Ergebnissatz enthält keine Computerrichtlinien — dafür muss die Erhebung ' +
'in einer erhöhten Sitzung (Administrator) erfolgen.
'
} elseif (-not $uInfo) {
$missing = '' + (ic 'alert') + ' ' +
'Der Ergebnissatz enthält keine Benutzerrichtlinien .
'
}
$xmlLeaf = Split-Path -Leaf $xmlFull
$genStamp = (Get-Date).ToString('dd.MM.yyyy HH:mm:ss')
$cover = '
' +
'Active Directory · Gruppenrichtlinien · gpresult
' +
'Richtlinienergebnissatz ' +
'' + $thesis + '
' +
'RSoP · ' + (esc $dataType) + ' ' +
'' + (esc $xmlLeaf) + ' ' + (esc $hostName) + '
' +
'' + $cards + '
' + $missing +
'Dieser Bericht wurde automatisch aus dem Richtlinienergebnissatz (Resultant Set of ' +
'Policy) erzeugt. Er zeigt die zum Erhebungszeitpunkt tatsächlich wirksamen Gruppenrichtlinien — ' +
'einschließlich verworfener Objekte samt Grund, Sicherheitsgruppen, durchsuchter Container und des ' +
'operativen Ereignisprotokolls.
'
$title = 'RSoP-Bericht — ' + $hostName + ' · ' + $(if ($isLocal) { 'Lokal' } else { $domain })
$html = '' + "`n" +
'' + "`n" +
'' + "`n" +
' ' + "`n" +
' ' + "`n" +
'' + (esc $title) + ' ' + "`n" +
'' + "`n" +
'' + "`n" +
'' + "`n" +
'' + "`n" +
'☰ ' + (esc $title) + '
' + "`n" +
'' + "`n" +
'
' +
'gpresult · RSoP ' +
(esc $hostName) + ' · ' + (esc $(if ($isLocal) { 'Lokal' } else { $domain })) + '
' +
'' + (ic 'home') + ' Übersicht ' +
$railNav +
'' +
'' + (ic 'sun') + ' Thema ' +
'' + (ic 'printer') + ' Drucken
' +
'' +
' ' + "`n" +
'
' +
'' +
$cover + $uPart.Html + $cPart.Html +
'' +
'
' + "`n" +
'
' + "`n" +
'
' + "`n" +
'' + "`n" +
'' + "`n" + ''
#endregion
# ============================================================================
#region Schreiben und abschließen
# ============================================================================
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($outFull, $html, $enc)
$sizeKb = [math]::Round((Get-Item -LiteralPath $outFull).Length / 1KB)
Write-Host ''
Write-Host "Bericht erstellt: $outFull ($sizeKb KB)" -ForegroundColor Green
if ($savedXml) { Write-Host "RSoP-XML: $savedXml" -ForegroundColor Green }
if ($uInfo) { Write-Host (" Benutzer: {0} — {1} von {2} GPOs angewendet" -f $uInfo.Name, $uInfo.Applied.Count, $uInfo.Gpos.Count) }
if ($cInfo) { Write-Host (" Computer: {0} — {1} von {2} GPOs angewendet" -f $cInfo.Name, $cInfo.Applied.Count, $cInfo.Gpos.Count) }
if ($tempXml) {
Remove-Item -LiteralPath $tempXml -ErrorAction SilentlyContinue
}
if ($Show) { Start-Process $outFull }
#endregion