# TOOLBOX-TILE: {"title": "Active Directory Group Policy Html Report", "desc": "Richtlinienergebnissatz (RSoP) als moderner HTML-Bericht — GPOs mit Filtergrund, wirksame Einstellungen je Erweiterung, Sicherheitsgruppen, Ereignisprotokoll; erzeugt HTML + XML. Windows PowerShell 5.1, keine Abhängigkeiten.", "icon": "📋", "type": "download", "os": ["windows"], "order": 28} # ============================================================================ # Active-Directory-Group-Policy-Html-Report.ps1 # Moderner HTML-Bericht des Richtlinienergebnissatzes (RSoP) — erzeugt HTML + XML. # # Schnellstart: # # # 0) Skript ausführbar machen — einmalig, je nach Situation: # Unblock-File .\Active-Directory-Group-Policy-Html-Report.ps1 # falls aus dem Internet geladen # Set-ExecutionPolicy -Scope CurrentUser RemoteSigned # dauerhaft für den Benutzer # powershell -ExecutionPolicy Bypass -File .\Active-Directory-Group-Policy-Html-Report.ps1 # oder nur für diesen Aufruf # # # 1) RSoP erheben und Bericht öffnen (HTML + XML landen in $HOME): # .\Active-Directory-Group-Policy-Html-Report.ps1 -Show # # # 2) Bericht aus einer vorhandenen XML-Datei rendern: # .\Active-Directory-Group-Policy-Html-Report.ps1 -InputXml .\XMLReport.xml -Path .\Bericht.html # # # 3) Ablageorte für HTML und XML selbst bestimmen: # .\Active-Directory-Group-Policy-Html-Report.ps1 -Path C:\Temp\RSoP.html -XmlPath C:\Temp\RSoP.xml # # # 4) Nur HTML, ohne XML-Rohdaten: # .\Active-Directory-Group-Policy-Html-Report.ps1 -NoXml # # # Hinweis: Computerrichtlinien erfordern eine als Administrator gestartete # # PowerShell — ohne Adminrechte enthält der Bericht nur den Benutzerbereich. # ============================================================================ <# .SYNOPSIS Erzeugt einen modernen, eigenständigen HTML-Bericht des Richtlinienergebnissatzes (RSoP). .DESCRIPTION Sammelt die RSoP-Daten als XML (Get-GPResultantSetOfPolicy, sonst gpresult /X) oder liest eine vorhandene XML-Datei ein und rendert daraus einen HTML-Bericht: dunkle Navigations-Leiste, helle Lesefläche in voller Breite, Hell/Dunkel-Modus, Inline-SVG-Icons, druckbar — alles in einer Datei, ohne externe Abhängigkeiten. Benötigt nur Windows PowerShell 5.1 (Bordmittel). Das GroupPolicy-Modul (z. B. auf einem Domänencontroller) wird genutzt, wenn vorhanden — sonst fällt das Skript auf gpresult.exe zurück. .PARAMETER InputXml Vorhandene RSoP-XML-Datei (aus "gpresult /X" oder "Get-GPResultantSetOfPolicy -ReportType Xml"). Ohne Angabe werden die Daten des lokalen Rechners/angemeldeten Benutzers frisch erhoben. .PARAMETER Path Zieldatei des HTML-Berichts. Standard: $HOME\GPResult-Report.html .PARAMETER Show Öffnet den fertigen Bericht im Standardbrowser. .PARAMETER XmlPath Ablageort für die erhobenen RSoP-Rohdaten (XML). Standard: wie -Path, nur mit Endung .xml — wie beim klassischen gpresult entstehen also HTML und XML. Wirkt nur, wenn das Skript die Daten selbst erhebt (ohne -InputXml). .PARAMETER NoXml Verwirft die erhobenen XML-Rohdaten nach dem Rendern (nur das HTML bleibt). .EXAMPLE .\Active-Directory-Group-Policy-Html-Report.ps1 -Show Erhebt die RSoP-Daten des lokalen Systems, schreibt HTML + XML und öffnet den Bericht. .EXAMPLE .\Active-Directory-Group-Policy-Html-Report.ps1 -InputXml .\XMLReport.xml -Path .\GPResult-Report.html Rendert einen Bericht aus einer vorhandenen XML-Datei. .NOTES Ohne Administratorrechte enthält der Ergebnissatz nur den Benutzerbereich; der Computerbereich erfordert eine erhöhte Sitzung. #> [CmdletBinding()] param( [string]$InputXml, [string]$Path = (Join-Path $HOME 'GPResult-Report.html'), [switch]$Show, [string]$XmlPath, [switch]$NoXml ) $ErrorActionPreference = 'Stop' # ============================================================================ #region Hilfsfunktionen # ============================================================================ function esc([string]$s) { if ($null -eq $s) { return '' } return [System.Net.WebUtility]::HtmlEncode($s) } function txt($node) { # Liefert den Text eines XML-Knotens — egal ob PowerShell ihn als String # oder (bei Attributen/Namespaces) als XmlElement adaptiert hat. if ($null -eq $node) { return '' } if ($node -is [System.Xml.XmlElement]) { return $node.InnerText } return [string]$node } function AsArray($x) { if ($null -eq $x) { return @() } return @($x) } function boolDe([string]$v) { switch -Regex ($v) { '^(?i)true$' { 'Ja'; break } '^(?i)false$' { 'Nein'; break } default { $v } } } function ParseIsoDate([string]$s) { if ([string]::IsNullOrWhiteSpace($s)) { return $null } try { return [datetime]::Parse($s, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::RoundtripKind) } catch { return $null } } function FmtDate($dt) { if ($null -eq $dt) { return '—' } if ($dt.Kind -eq [System.DateTimeKind]::Utc) { $dt = $dt.ToLocalTime() } return $dt.ToString('dd.MM.yyyy HH:mm:ss') } function badge([string]$kind, [string]$label) { return '' + (esc $label) + '' } #endregion # ============================================================================ #region Icons (Inline-SVG, erben die Textfarbe via currentColor) # ============================================================================ $Icons = @{ user = '' users = '' pc = '' shield = '' layers = '' target = '' gear = '' clock = '' list = '' globe = '' bolt = '' sliders = '' home = '' alert = '' printer = '' sun = '' link = '' } function ic([string]$name) { $body = $Icons[$name] if (-not $body) { $body = $Icons['list'] } return '' } #endregion # ============================================================================ #region Statisches CSS und JS (Design „Boards & Ice“ — volle Breite) # ============================================================================ $Css = @' :root{ --bg:#EAF1F6; --rail:#0C1E2E; --rail-2:#0A1826; --rail-ink:#DEE9F1; --rail-muted:#90A7B9; --rail-line:#1E3346; --pane:#F3F7FA; --surface:#FFFFFF; --surface-2:#EEF4F8; --ink:#122A3C; --ink-2:#3A5468; --muted:#6E869A; --line:#D4E0E9; --accent:#0E76AC; --accent-strong:#0B5D89; --accent-tint:#E2EFF7; --accent-ink:#FFFFFF; --red:#B23A32; --red-t:#F6E6E4; --amber:#9B6A1E; --amber-t:#F5ECDA; --green:#2A7D57; --green-t:#E2F0EA; --shadow:0 1px 2px rgba(16,40,60,.06), 0 10px 30px rgba(16,40,60,.07); --sans:'Segoe UI',system-ui,-apple-system,'Inter',Roboto,'Helvetica Neue',Arial,sans-serif; --mono:ui-monospace,'Cascadia Code','JetBrains Mono','SF Mono',Menlo,Consolas,monospace; } :root[data-theme="dark"]{ --bg:#060E17; --rail:#07121D; --rail-2:#050D16; --rail-ink:#CFDEE9; --rail-muted:#7E95A7; --rail-line:#172A39; --pane:#0C1D2B; --surface:#122A3B; --surface-2:#0F2333; --ink:#DCE7EF; --ink-2:#A7BDCC; --muted:#7E96A8; --line:#20384A; --accent:#41A9DD; --accent-strong:#79C4EA; --accent-tint:#123044; --accent-ink:#06131D; --red:#E5837A; --red-t:#2C1917; --amber:#D5A24E; --amber-t:#292112; --green:#5FBB8D; --green-t:#112619; --shadow:0 1px 2px rgba(0,0,0,.45), 0 12px 34px rgba(0,0,0,.4); } *{box-sizing:border-box} html,body{margin:0;height:100%} body{font-family:var(--sans);background:var(--bg);color:var(--ink); -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility} a{color:inherit} svg.i{width:1em;height:1em;stroke:currentColor;fill:none;stroke-width:1.6; stroke-linecap:round;stroke-linejoin:round;vertical-align:-.13em} .shell{display:grid;grid-template-columns:308px 1fr;height:100vh;overflow:hidden} /* ---------- Rail (Navigation) ---------- */ .rail{background:linear-gradient(180deg,var(--rail),var(--rail-2));color:var(--rail-ink); border-right:1px solid var(--rail-line);overflow-y:auto;overflow-x:hidden;padding:0 0 40px} .rail::-webkit-scrollbar{width:9px}.rail::-webkit-scrollbar-thumb{background:var(--rail-line);border-radius:9px} .brand{display:flex;align-items:center;gap:11px;padding:22px 22px 18px;position:sticky;top:0; background:linear-gradient(180deg,var(--rail),rgba(12,30,46,.86));backdrop-filter:blur(6px); border-bottom:1px solid var(--rail-line);cursor:pointer;z-index:2} .logo{width:26px;height:26px;border-radius:6px;flex:0 0 auto;position:relative; background:radial-gradient(circle at 50% 42%,var(--accent),var(--accent-strong));box-shadow:0 0 0 3px rgba(65,169,221,.15)} .logo::after{content:"";position:absolute;inset:8px;border:1.6px solid rgba(255,255,255,.85);border-radius:50%} .brand b{font-size:.94rem;letter-spacing:.2px;line-height:1.1} .brand small{display:block;font-family:var(--mono);font-size:.62rem;letter-spacing:.16em; text-transform:uppercase;color:var(--rail-muted);margin-bottom:2px} .overview{display:flex;gap:10px;align-items:center;margin:14px 14px 4px;padding:10px 12px;border-radius:9px; font-family:var(--mono);font-size:.72rem;letter-spacing:.06em;text-transform:uppercase;color:var(--rail-muted); cursor:pointer;border:1px solid transparent;text-decoration:none} .overview:hover{background:rgba(255,255,255,.04);color:var(--rail-ink)} .overview.active{color:var(--accent-strong);border-color:var(--rail-line);background:rgba(65,169,221,.08)} .cat{display:flex;align-items:center;gap:8px;margin:20px 22px 8px;font-family:var(--mono);font-size:.63rem; letter-spacing:.16em;text-transform:uppercase;color:var(--rail-muted)} .tick{width:8px;height:8px;border-radius:2px;flex:0 0 auto} .tick.t-user{background:var(--accent)} .tick.t-comp{background:var(--green)} .navitem{display:flex;gap:12px;align-items:flex-start;margin:2px 12px;padding:9px 12px;border-radius:9px; text-decoration:none;color:var(--rail-ink);border-left:2px solid transparent;transition:background .15s} .navitem:hover{background:rgba(255,255,255,.05)} .navitem .code{font-family:var(--mono);font-size:.74rem;color:var(--rail-muted);padding-top:1px;min-width:1.7em} .ntitle{display:block;font-size:.9rem;font-weight:600;line-height:1.28} .ntag{display:block;font-size:.73rem;color:var(--rail-muted);margin-top:1px} .navitem.active{background:rgba(65,169,221,.12);border-left-color:var(--accent)} .navitem.active .code,.navitem.active .ntag{color:var(--accent-strong)} .railfoot{margin:22px 14px 0;padding-top:16px;border-top:1px solid var(--rail-line);display:flex;flex-direction:column;gap:8px} .rlink{display:flex;align-items:flex-start;gap:10px;padding:9px 12px;border-radius:8px; color:var(--rail-muted);font-size:.78rem;border:1px solid var(--rail-line);background:rgba(255,255,255,.02);line-height:1.4} .rlink .g{flex:0 0 auto;padding-top:1px;color:var(--rail-muted)} .rlink .rt{flex:1;min-width:0;overflow-wrap:anywhere} .railctl{display:flex;gap:8px;margin:10px 14px 0} .railctl button{flex:1;display:flex;align-items:center;justify-content:center;gap:7px; font-family:var(--mono);font-size:.72rem;letter-spacing:.04em;color:var(--rail-muted); background:rgba(255,255,255,.03);border:1px solid var(--rail-line);border-radius:8px;padding:9px;cursor:pointer} .railctl button:hover{color:var(--rail-ink);border-color:var(--accent)} .raillinks{display:flex;gap:8px;margin:8px 14px 0} .raillinks a{flex:1;display:flex;align-items:center;justify-content:center;gap:7px; font-family:var(--mono);font-size:.68rem;letter-spacing:.03em;color:var(--rail-muted); border:1px solid var(--rail-line);border-radius:8px;padding:8px 6px;text-decoration:none; background:rgba(255,255,255,.02);white-space:nowrap} .raillinks a:hover{color:var(--rail-ink);border-color:var(--accent)} /* ---------- Lesefläche ---------- */ .reading{background:var(--pane);overflow-y:auto;overflow-x:hidden;position:relative;scroll-behavior:smooth} .progress{position:sticky;top:0;height:3px;background:transparent;z-index:5} .progress i{display:block;height:100%;width:0;background:linear-gradient(90deg,var(--accent),var(--accent-strong))} .reading-inner{max-width:none;margin:0;padding:clamp(24px,3vw,48px) clamp(18px,3vw,52px) 130px} .sec{scroll-margin-top:24px} .sec h2{display:flex;align-items:center;gap:11px;font-size:1.42rem;font-weight:750;letter-spacing:-.01em; margin:2.2em 0 .35em;padding-top:.8em;border-top:1px solid var(--line);color:var(--ink)} .sec h2 .sic{display:inline-flex;color:var(--accent);font-size:1.15em} .sec h2 .count{font-family:var(--mono);font-size:.62em;font-weight:600;color:var(--accent-strong); background:var(--accent-tint);border:1px solid color-mix(in srgb,var(--accent) 30%,var(--line)); border-radius:999px;padding:2px 10px;margin-left:2px} .sec .sub{font-size:.92rem;color:var(--muted);margin:0 0 1.2em} .sec h3{font-size:1.08rem;font-weight:700;margin:1.7em 0 .6em;color:var(--ink)} .sec p{font-size:1rem;line-height:1.66;color:var(--ink-2)} code{font-family:var(--mono);font-size:.85em;background:var(--surface-2);border:1px solid var(--line); border-radius:5px;padding:.08em .4em;color:var(--ink)} /* Kennzahl-Kacheln */ .kvgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(215px,1fr));gap:12px;margin:14px 0} .kv{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;border-radius:12px; background:var(--surface);border:1px solid var(--line);box-shadow:var(--shadow)} .kv .kic{color:var(--accent);font-size:1.25rem;padding-top:2px} .kv>span:last-child{flex:1;min-width:0} .kv .kl{display:block;font-family:var(--mono);font-size:.64rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:3px} .kv .kw{display:block;font-size:.98rem;font-weight:650;color:var(--ink);line-height:1.35;overflow-wrap:anywhere} .kv .kw small{display:block;font-weight:400;font-size:.78rem;color:var(--muted);margin-top:1px} /* Badges */ .badge{display:inline-block;font-family:var(--mono);font-size:.68rem;letter-spacing:.05em; padding:3px 9px;border-radius:999px;border:1px solid var(--line);white-space:nowrap} .b-ok{color:var(--green);background:var(--green-t);border-color:color-mix(in srgb,var(--green) 34%,var(--line))} .b-warn{color:var(--amber);background:var(--amber-t);border-color:color-mix(in srgb,var(--amber) 38%,var(--line))} .b-err{color:var(--red);background:var(--red-t);border-color:color-mix(in srgb,var(--red) 38%,var(--line))} .b-mut{color:var(--muted);background:var(--surface-2)} /* Tabellen */ .tablewrap{overflow-x:auto;margin:1.1em 0 1.6em;border:1px solid var(--line);border-radius:12px; box-shadow:var(--shadow);background:var(--surface)} table{border-collapse:collapse;width:100%;font-size:.85rem;background:var(--surface)} thead th{background:var(--surface-2);text-align:left;font-weight:700;color:var(--ink); font-size:.78rem;letter-spacing:.01em;padding:9px 12px;border-bottom:1px solid var(--line);white-space:nowrap} tbody td{padding:9px 12px;border-bottom:1px solid var(--line);color:var(--ink-2);vertical-align:top; line-height:1.5;overflow-wrap:anywhere} tbody tr:last-child td{border-bottom:0} tbody tr:nth-child(even) td{background:color-mix(in srgb,var(--surface-2) 55%,transparent)} td.strong{color:var(--ink);font-weight:600} td.num{font-family:var(--mono);white-space:nowrap} td .mono,.mono{font-family:var(--mono);font-size:.86em;color:var(--muted)} td .sett{display:block;color:var(--ink);font-weight:600} td .vline{display:block;margin:1px 0} td .vline b{color:var(--muted);font-weight:500;font-family:var(--mono);font-size:.82em} /* Hinweis-Box */ .note{margin:1.3em 0;padding:14px 20px;background:var(--accent-tint);display:flex;gap:12px;align-items:flex-start; border:1px solid color-mix(in srgb,var(--accent) 28%,var(--line));border-left:4px solid var(--accent); border-radius:0 12px 12px 0;box-shadow:var(--shadow);color:var(--ink);line-height:1.55} .note .nic{color:var(--accent);font-size:1.15rem;padding-top:2px} .note.warn{background:var(--amber-t);border-color:color-mix(in srgb,var(--amber) 36%,var(--line));border-left-color:var(--amber)} .note.warn .nic{color:var(--amber)} /* Ereignis-Akkordeon */ details.evt{margin:1.1em 0;border:1px solid var(--line);border-radius:12px;background:var(--surface);box-shadow:var(--shadow)} details.evt>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:10px; padding:13px 18px;font-weight:650;color:var(--ink);border-radius:12px} details.evt>summary::-webkit-details-marker{display:none} details.evt>summary .chev{margin-left:auto;font-family:var(--mono);color:var(--muted);transition:transform .2s} details.evt[open]>summary .chev{transform:rotate(90deg)} details.evt>summary:hover{background:color-mix(in srgb,var(--surface-2) 60%,transparent)} details.evt .tablewrap{margin:0;border:0;border-top:1px solid var(--line);border-radius:0 0 12px 12px;box-shadow:none} td.desc{white-space:pre-line} /* ---------- Deckblatt ---------- */ .cover{position:relative;padding-top:8px} .cover-motif{position:absolute;top:-40px;right:-60px;width:520px;height:520px;pointer-events:none;opacity:.5; background: radial-gradient(circle,transparent 0 148px,color-mix(in srgb,var(--accent) 32%,transparent) 148px 150px,transparent 151px), radial-gradient(circle,transparent 0 92px,color-mix(in srgb,var(--accent) 22%,transparent) 92px 93px,transparent 94px), radial-gradient(circle,color-mix(in srgb,var(--accent) 16%,transparent) 0 5px,transparent 6px); -webkit-mask-image:linear-gradient(200deg,#000,transparent 72%);mask-image:linear-gradient(200deg,#000,transparent 72%)} .eyebrow{font-family:var(--mono);font-size:.74rem;letter-spacing:.14em;text-transform:uppercase;color:var(--accent-strong);margin:6px 0 18px} .cover-h1{font-size:clamp(2.1rem,4.6vw,3.4rem);font-weight:820;letter-spacing:-.03em;line-height:1.04;margin:0 0 .34em;color:var(--ink)} .thesis{font-size:clamp(1.02rem,1.7vw,1.22rem);line-height:1.55;color:var(--ink-2);max-width:52em;margin:0 0 34px;font-weight:450} .thesis b{color:var(--ink)} .docmeta{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin:0 0 26px} .dc{font-family:var(--mono);font-size:.72rem;letter-spacing:.1em;color:var(--muted)} .dcat{font-family:var(--mono);font-size:.66rem;letter-spacing:.09em;text-transform:uppercase; padding:3px 9px;border-radius:999px;border:1px solid var(--line);color:var(--accent-strong)} .cardgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(225px,1fr));gap:14px;margin:8px 0 34px} .card{position:relative;display:flex;flex-direction:column;gap:2px;padding:18px 18px 20px;border-radius:14px; background:var(--surface);border:1px solid var(--line);text-decoration:none;box-shadow:var(--shadow); transition:transform .16s,border-color .16s;overflow:hidden} .card::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--muted)} .card.cat-user::before{background:var(--accent)} .card.cat-comp::before{background:var(--green)} .card.cat-proc::before{background:var(--amber)} .card:hover{transform:translateY(-3px);border-color:color-mix(in srgb,var(--accent) 45%,var(--line))} .card-ic{position:absolute;top:16px;right:16px;color:var(--muted);opacity:.6;font-size:1.3rem} .card-code{font-family:var(--mono);font-size:1.5rem;font-weight:600;color:var(--ink);letter-spacing:-.01em} .card-cat{font-family:var(--mono);font-size:.62rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin:2px 0 8px} .card-title{font-size:1.02rem;font-weight:700;color:var(--ink);line-height:1.25} .card-tag{font-size:.83rem;color:var(--ink-2);margin-top:3px;overflow-wrap:anywhere} .disclaimer{font-size:.82rem;color:var(--muted);line-height:1.55;border-top:1px solid var(--line);padding-top:18px;max-width:60em} .foot{margin-top:64px;padding-top:22px;border-top:1px solid var(--line);font-size:.8rem;color:var(--muted); font-family:var(--mono);line-height:1.6} /* ---------- Topbar (mobil) ---------- */ .topbar{display:none;align-items:center;gap:12px;padding:11px 14px;background:var(--rail);color:var(--rail-ink); border-bottom:1px solid var(--rail-line);position:sticky;top:0;z-index:20} .burger{width:38px;height:34px;border-radius:8px;border:1px solid var(--rail-line);background:transparent; color:var(--rail-ink);cursor:pointer;font-size:1.1rem} .topbar b{font-size:.9rem} .scrim{display:none} @media (max-width:900px){ .shell{grid-template-columns:1fr;height:calc(100vh - 57px)} .topbar{display:flex} .rail{position:fixed;inset:0 auto 0 0;width:300px;z-index:40;transform:translateX(-102%);transition:transform .26s ease} .shell.open .rail{transform:none} .shell.open .scrim{display:block;position:fixed;inset:0;background:rgba(4,10,16,.5);z-index:35} } @media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important;scroll-behavior:auto!important}} /* ---------- Druck ---------- */ @media print{ .rail,.topbar,.progress,.railctl,.scrim{display:none!important} .shell{display:block;height:auto}.reading{overflow:visible;height:auto;background:#fff} .reading-inner{max-width:none;padding:0} details.evt{display:none} .tablewrap,.kv,.card,.note{box-shadow:none;break-inside:avoid} .sec h2{break-after:avoid} body{background:#fff} } '@ $Js = @' (function(){ var shell=document.getElementById('shell'); var reading=document.getElementById('reading'); var bar=document.getElementById('bar'); var over=document.getElementById('overview'); var links=[].slice.call(document.querySelectorAll('.navitem')); var secs=links.map(function(a){return document.getElementById(a.getAttribute('href').slice(1));}).filter(Boolean); function onScroll(){ var st=reading.scrollTop,h=reading.scrollHeight-reading.clientHeight; if(bar)bar.style.width=(h>0?(st/h*100):0)+'%'; var cur=null; for(var i=0;i 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 += '
' + $rows + '
' + (esc $friendly) + 'WertQuell-GPORang
' } } 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 += '
' $body += New-GpoRows $info.Applied $true $body += '
RangGruppenrichtlinienobjektVerknüpft mitVersion (AD/Sysvol)ErzwungenStatus
' } else { $body += '
' + (ic 'layers') + 'Es wurden keine Gruppenrichtlinienobjekte angewendet.
' } if ($info.Denied.Count -gt 0) { $body += '

' + (ic 'alert') + ' Nicht angewendet (' + $info.Denied.Count + ')

' $body += '
' $body += New-GpoRows $info.Denied $false $body += '
GruppenrichtlinienobjektVerknüpft mitVersion (AD/Sysvol)Grund
' } $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 = '
' + $rows + '
#GruppeSID
' $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 = '
' + $rows + '
ReihenfolgePfadTypBlockiert VererbungBlockiertModus
' $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 += '
' + $rows + '
ErweiterungZeitraumProtokollstatusErgebnis
' } $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 += '
' + $rows + '
ErweiterungDauerVerarbeitet am
' } 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' + '
' + $rows + '
#ZeitEreignis-IDBeschreibung
' } 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 GPOsvon ' + $uInfo.Gpos.Count + ' verknüpften · ' + (esc $uInfo.Name) + '' } if ($cInfo) { $cards += '' + (ic 'layers') + '' + '' + $cInfo.Applied.Count + 'Computer' + 'Angewendete GPOsvon ' + $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 + ' msVerarbeitung' + 'GesamtdauerBenutzer- 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' + 'GruppenmitgliedschaftenBenutzer / 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

' + '

Richtlinien­ergebnissatz

' + '

' + $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" + '' + "`n" + '
' + '
' + $cover + $uPart.Html + $cPart.Html + '
Erzeugt am ' + $genStamp + ' mit ' + (esc $ScriptName) + ' · Datenquelle: ' + (esc $xmlLeaf) + ' (' + (esc $dataType) + ') · Erhoben am ' + (FmtDate $readTime) + '
' + '
' + "`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