<# .SYNOPSIS Misst Hotspots INNERHALB der ProcessADObjects-Funktion (Process-ADObjects-Work-.ps1) zeilenweise mit PSProfiler. .DESCRIPTION Problemstellung: Measure-Script instrumentiert nur die UEBERGEBENE Datei, nicht die zur Laufzeit dot-gesourcten. Misst man das Rahmenskript, erscheint die gesamte ProcessADObjects- Zeit nur als eine Zeile (der Aufruf), ohne Aufschluesselung innerhalb R125. Loesung: 1. Das ECHTE Rahmenskript wird mit ProcessDAW=$false ausgefuehrt. Dadurch laufen alle Imports und prepareADObjectsByType-Aufrufe (voller Scope: $ADObjects*, $FI, alle Pfad-Variablen, alle Funktionen), aber der try-Block mit den ProcessADObjects-Aufrufen wird uebersprungen. Wir "dot-sourcen" das Rahmenskript, damit sein kompletter Scope in DIESER Session erhalten bleibt. 2. Von der Instituts-Work-Datei (R125) wird eine temporaere Kopie erzeugt, an deren Ende die eigentlichen ProcessADObjects-Aufrufe angehaengt werden. 3. Diese temporaere Kopie wird an Measure-Script uebergeben. Da die Ausfuehrung der Funktion jetzt INNERHALB der gemessenen Datei stattfindet, wird der Funktions- koerper zeilenweise instrumentiert. Der geteilte Scope aus Schritt 1 liefert die noetigen Variablen. Hinweis zu den Zeilennummern: Die temporaere Kopie ist bis zum Original-Ende zeilengleich mit R125. Die angehaengten Aufrufe stehen DAHINTER, stoeren die Zuordnung der Funktionszeilen also nicht. .PARAMETER Sparkasse Institut. Default R125. .PARAMETER ScriptRoot Verzeichnis der Skripte. Default: aktuelles Verzeichnis. .PARAMETER Types Welche ADObject-Typen gemessen werden. Default nur die teuersten (Organisation, Role). Moeglich: Organisation, AppAccess, FilesystemAccess, AppConfiguration, Security, Role. .PARAMETER OutputDir Zielverzeichnis fuer die Ergebnis-XML. Default: Verzeichnis dieses Skripts. .EXAMPLE ./Measure-R125Direct.ps1 -Sparkasse R125 -ScriptRoot /Users/edmond/Develop/PowerShell/sparkurs -Types Organisation #> [CmdletBinding()] param( [string]$Sparkasse = "R125", [string]$ScriptRoot = (Get-Location).Path, [ValidateSet('Organisation','AppAccess','FilesystemAccess','AppConfiguration','Security','Role')] [string[]]$Types = @('Organisation','Role'), [string]$OutputDir = $PSScriptRoot + "/Data/" + $Sparkasse + "/Log" ) $ErrorActionPreference = "Stop" if (-not (Get-Command Measure-Script -ErrorAction SilentlyContinue)) { throw "PSProfiler (Measure-Script) nicht verfuegbar. Bitte 'Import-Module PSProfiler'." } $ScriptRoot = (Resolve-Path $ScriptRoot).Path $mainScript = Join-Path $ScriptRoot "Process-ADObjects.ps1" $workScript = Join-Path $ScriptRoot ("Process-ADObjects-Work-{0}.ps1" -f $Sparkasse) foreach ($p in @($mainScript, $workScript)) { if (-not (Test-Path $p)) { throw "Nicht gefunden: $p" } } if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null } # Mapping Typ -> ($ADObjects*-Variablenname, $FI.TypeTag*-Property) $typeMap = @{ Organisation = @{ Var = 'ADObjectsOrganisation'; Tag = 'TypeTagOrganisation' } AppAccess = @{ Var = 'ADObjectsAppAccess'; Tag = 'TypeTagAppAccess' } FilesystemAccess = @{ Var = 'ADObjectsFilesystemAccess'; Tag = 'TypeTagFilesystemAccess' } AppConfiguration = @{ Var = 'ADObjectsAppConfiguration'; Tag = 'TypeTagAppConfiguration' } Security = @{ Var = 'ADObjectsSecurity'; Tag = 'TypeTagSecurity' } Role = @{ Var = 'ADObjectsRole'; Tag = 'TypeTagRole' } } # --- Temporaeres Local-Override: vollen Vorlauf, aber ProcessDAW=$false ----- $localOverridePath = Join-Path $ScriptRoot "Process-ADObjects-Configuration-Local.ps1" $createdOverride = $false if (Test-Path $localOverridePath) { Write-Host "HINWEIS: Vorhandenes Configuration-Local-Override wird NICHT angetastet." -ForegroundColor Yellow Write-Host " Sicherstellen: DefaultInstitute=$Sparkasse, ProcessDAW=`$false, Verbose=`$false, Analyze=`$false." -ForegroundColor Yellow } else { $ov = @" # === AUTO-GENERATED by Measure-R125Direct.ps1 - wird automatisch geloescht === # Voller Import/Prepare-Vorlauf, aber KEINE ProcessADObjects-Aufrufe im Rahmen # (die uebernimmt der Mess-Treiber selbst auf der temporaeren R125-Kopie). `$Config.DefaultInstitute = "$Sparkasse" `$Config.ProcessDAW = `$false `$Config.WriteKURS = `$false `$Config.TransferDAW = `$false `$Config.PostProcess = `$false `$Config.Analyze = `$false `$Config.Verbose = `$false `$Config.TestMode = 0 "@ Set-Content -Path $localOverridePath -Value $ov -Encoding UTF8 $createdOverride = $true Write-Host "Temporaeres Configuration-Local-Override angelegt (ProcessDAW=false)." -ForegroundColor DarkGray } $tmpWork = $null try { Push-Location $ScriptRoot try { # --- Schritt 1: Rahmenskript dot-sourcen -> voller Scope in dieser Session # ProcessDAW=$false ueberspringt den try-Block; Import + prepare laufen komplett. Write-Host "Baue vollen Scope auf (dot-source Rahmenskript, ProcessDAW=false) ..." -ForegroundColor Cyan # Variablen, die VOR dem Dot-Sourcing schon existieren, merken - alles, was # danach NEU hinzukommt, stammt aus dem Rahmenskript und muss gespiegelt werden. $before = @{} Get-Variable -Scope Local | ForEach-Object { $before[$_.Name] = $true } . $mainScript # --- Scope-Bruecke: Measure-Script fuehrt die Zieldatei in einem EIGENEN # Scope aus, in dem die hier per Dot-Sourcing aufgebauten lokalen Variablen # NICHT sichtbar sind. Loesung: alle vom Rahmenskript erzeugten Variablen # ins GLOBALE Scope spiegeln - global ist auch aus dem Measure-Lauf sichtbar. # PowerShell-Automatic-Variablen und die treibereigenen werden ausgelassen. $automatic = @( 'args','input','PSBoundParameters','MyInvocation','PSCmdlet','PSItem','_', '?','^','$','null','true','false','PSScriptRoot','PSCommandPath','this', 'before','automatic','t','vn','val','cnt','mainScript','workScript','tmpWork', 'typeMap','Types','Sparkasse','ScriptRoot','OutputDir','localOverridePath', 'createdOverride','ov','result','callLines','tag','timestamp','xmlPath','v', 'mirrored','ErrorActionPreference','ExecutionContext','HOME','PID','PWD','ShellId' ) $mirrored = 0 foreach ($v in (Get-Variable -Scope Local)) { if ($before.ContainsKey($v.Name)) { continue } if ($automatic -contains $v.Name) { continue } if ([string]::IsNullOrEmpty($v.Name)) { continue } Set-Variable -Name $v.Name -Value $v.Value -Scope Global -Force -ErrorAction SilentlyContinue $mirrored++ } Write-Host (" {0} Rahmen-Variablen ins globale Scope gespiegelt." -f $mirrored) -ForegroundColor DarkGray # Sanity-Check: sind die vorbereiteten Objekte da (jetzt global)? foreach ($t in $Types) { $vn = $typeMap[$t].Var $val = Get-Variable -Name $vn -ValueOnly -Scope Global -ErrorAction SilentlyContinue $cnt = if ($val) { @($val).Count } else { 0 } Write-Host (" {0,-16} = {1} Eintraege" -f $t, $cnt) -ForegroundColor DarkGray if ($cnt -eq 0) { Write-Host (" WARNUNG: {0} ist leer/null - Messung wuerde fehlschlagen." -f $vn) -ForegroundColor Yellow } } # --- Schritt 2: temporaere Mess-Datei aufbauen # Sie muss ALLES enthalten, was ProcessADObjects zur Laufzeit braucht: # a) die Hilfsfunktionen (Utility/Base/Analyze/Work) - werden hier per # Dot-Source geladen, da der isolierte Measure-Scope die in der # Treiber-Session definierten Funktionen nicht sieht. # b) die ProcessADObjects-Definition selbst (R125) - ebenfalls als R125 # direkt eingebettet, damit ihre Zeilen instrumentiert werden. # c) die eigentlichen Aufrufe. # Die Variablen (FI, Config, *FilePath, ADObjects*) kommen ueber das globale # Scope (siehe Spiegelung oben). # # Damit die Zeilennummern der Funktion 1:1 zu R125 bleiben, wird R125 NICHT # kopiert-und-erweitert, sondern als eigene Datei per Dot-Source eingebunden; # Measure-Script instrumentiert dot-gesourcte Dateien jedoch nicht. Deshalb # betten wir R125 INLINE ein und haengen die Aufrufe an. Die Hilfs-Sources # stehen davor und verschieben die Funktionszeilen um einen festen Offset, # den der Analyzer nicht braucht (Code-Text steht pro Zeile dabei). $tmpWork = Join-Path $ScriptRoot ("Process-ADObjects-Work-{0}.measure-tmp.ps1" -f $Sparkasse) $header = @" # === AUTO-GENERATED by Measure-R125Direct.ps1 - wird automatisch geloescht === # Hilfsfunktionen laden (im isolierten Measure-Scope nicht vererbt): . "`$PSScriptRoot/Process-ADObjects-Utility.ps1" . "`$PSScriptRoot/Process-ADObjects-Base.ps1" . "`$PSScriptRoot/Process-ADObjects-Analyze.ps1" . "`$PSScriptRoot/Process-ADObjects-Work.ps1" # === ab hier folgt der unveraenderte Inhalt von Process-ADObjects-Work-$Sparkasse.ps1 === "@ $r125Body = Get-Content -Path $workScript -Raw $callLines = @('', '# === appended calls by Measure-R125Direct.ps1 ===') foreach ($t in $Types) { $vn = $typeMap[$t].Var $tag = $typeMap[$t].Tag # Aufruf exakt wie im Rahmenskript (try-Block, Zeile 211ff) $callLines += ('ProcessADObjects $global:{0} $global:FI.{1}' -f $vn, $tag) } $fullContent = $header + $r125Body + [Environment]::NewLine + ($callLines -join [Environment]::NewLine) Set-Content -Path $tmpWork -Value $fullContent -Encoding UTF8 # Header-Zeilen zaehlen, damit der Analyzer-Output korrekt interpretiert werden # kann (Funktionszeile in R125 = gemessene Zeile - $headerOffset). $headerOffset = ($header -split "`n").Count - 1 Write-Host (" Zeilen-Offset Header -> R125: {0} (R125-Zeile = gemessene Zeile - {0})" -f $headerOffset) -ForegroundColor DarkGray # --- Schritt 3: temporaere Kopie messen (instrumentiert R125 zeilenweise) Write-Host "" Write-Host ("Messe ProcessADObjects fuer Typen: {0}" -f ($Types -join ', ')) -ForegroundColor Cyan $result = Measure-Script -Path $tmpWork } finally { Pop-Location } # --- Ergebnis sichern -------------------------------------------------- $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" $xmlPath = Join-Path $OutputDir ("Profile-WorkScript-{0}-{1}.xml" -f $Sparkasse, $timestamp) $result | Export-Clixml -Path $xmlPath -Depth 6 # Offset als Sidecar ablegen, damit der Analyzer die gemessenen Zeilennummern # automatisch auf die Original-R125-Zeilen zurueckrechnen kann. $sidecar = [pscustomobject]@{ SourceFile = Split-Path $workScript -Leaf HeaderOffset = $headerOffset Types = $Types Sparkasse = $Sparkasse } $sidecar | Export-Clixml -Path ($xmlPath -replace '\.xml$', '.meta.xml') Write-Host "" Write-Host "Ergebnis gespeichert: $xmlPath" -ForegroundColor Green Write-Host "" Write-Host "Auswertung:" -ForegroundColor Cyan Write-Host " ./Analyze-ProfileResult.ps1 -XmlPath '$xmlPath' -Top 40" -ForegroundColor Gray Write-Host "" Write-Host ("HINWEIS: Gemessene Zeilennummern sind um {0} (Header) verschoben." -f $headerOffset) -ForegroundColor DarkGray Write-Host (" R125-Originalzeile = gemessene Zeile - {0}. Der Analyzer liest" -f $headerOffset) -ForegroundColor DarkGray Write-Host " den Offset automatisch aus der .meta.xml-Sidecar-Datei." -ForegroundColor DarkGray } finally { if ($tmpWork -and (Test-Path $tmpWork)) { Remove-Item -Path $tmpWork -ErrorAction SilentlyContinue Write-Host "Temporaere R125-Mess-Kopie entfernt." -ForegroundColor DarkGray } if ($createdOverride -and (Test-Path $localOverridePath)) { Remove-Item -Path $localOverridePath -ErrorAction SilentlyContinue Write-Host "Temporaeres Configuration-Local-Override entfernt." -ForegroundColor DarkGray } }