Private/Core/Start-OrphanedSidSession.ps1
|
function Start-OrphanedSidSession { <# .SYNOPSIS Prepares module-scope state for one command invocation. .DESCRIPTION Every public command runs on top of the same per-run state: the ActiveDirectory module, the logging system with its correlation ID and level, the configuration object, the memory manager and the statistics counters. This function sets all of it up in one place so Find-OrphanedSid, Remove-OrphanedSid and Restore-OrphanedSidBackup cannot drift apart in how they start. The ActiveDirectory module is checked here rather than declared in the manifest. It is Windows-only RSAT and not on the Gallery, so declaring it would make the module unimportable on any host without RSAT, including the test runners. The log level follows -Verbose and -Debug on the calling command when -LogLevel was not given explicitly, because an operator who asked for verbose console output expects the same detail in the file. .PARAMETER CommandName Name of the public command starting the run. Used for the default log file name and the first log line. .PARAMETER ConfigPath Optional JSON configuration file; see ScriptConfiguration.LoadFromFile for the keys read. .PARAMETER LogPath Log file path. When omitted the log goes to .\Logs\<CommandName>_<yyyyMMdd_HHmmss>.log under the current directory. .PARAMETER MaxMemoryUsageMB Memory threshold handed to the memory manager. .PARAMETER CorrelationId Correlation identifier for the run. Generated when omitted. .PARAMETER LogLevel File log level. Overrides the -Verbose/-Debug inference when bound. .PARAMETER LogLevelBound True when the caller bound -LogLevel explicitly. Without it the level is inferred from the caller's $VerbosePreference and $DebugPreference. .PARAMETER SuppressConsoleOutput Stops console output; the log file is still written. .PARAMETER RequireActiveDirectory Fail when the ActiveDirectory module cannot be imported. Every public command sets this; it exists as a switch so tests can exercise the session without RSAT. .OUTPUTS [hashtable] The initialization results from Initialize-ScriptExecution: Config, MemoryManager, Statistics, LogPath and CorrelationId. .EXAMPLE PS> $session = Start-OrphanedSidSession -CommandName 'Find-OrphanedSid' -CorrelationId $id -RequireActiveDirectory DESCRIPTION: Sets up logging, configuration and the memory manager for a discovery run OUTPUT: The initialization hashtable; module-scope variables are populated as a side effect USE CASE: Called from the begin block of every public command .NOTES Author: Jeffrey Stuhr Blog: https://www.techbyjeff.net LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/ TROUBLESHOOTING: - Module not found: .\Troubleshooting\Common\Common-Issues.md - Logging problems: .\Troubleshooting\Security\Security-Logging-Issues.md #> [CmdletBinding()] [OutputType([hashtable])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Sets module-scope run state; the log folder write is pinned -WhatIf:$false by design')] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$CommandName, [Parameter()] [string]$ConfigPath, [Parameter()] [string]$LogPath, [Parameter()] [ValidateRange(100, 16384)] [int]$MaxMemoryUsageMB = 1024, [Parameter()] [string]$CorrelationId = [System.Guid]::NewGuid().ToString(), [Parameter()] [ValidateSet('Critical', 'Error', 'Warning', 'Information', 'Debug', 'Verbose')] [string]$LogLevel = 'Information', [Parameter()] [bool]$LogLevelBound = $false, [Parameter()] [switch]$SuppressConsoleOutput, [Parameter()] [switch]$RequireActiveDirectory ) if ($RequireActiveDirectory) { if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) { $message = 'The ActiveDirectory PowerShell module is not available. Install RSAT: Active Directory ' + 'PowerShell (Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0) ' + 'and run the command again.' Write-Error $message -ErrorAction Stop } if (-not (Get-Module -Name ActiveDirectory)) { Import-Module -Name ActiveDirectory -ErrorAction Stop } } # Shared logging state. These are read by Write-StructuredLogEntry and the security loggers. $script:CorrelationId = $CorrelationId $script:SuppressConsoleOutput = [bool]$SuppressConsoleOutput if (-not $LogLevelBound) { if ($VerbosePreference -eq 'Continue') { $LogLevel = 'Verbose' } elseif ($DebugPreference -eq 'Continue') { $LogLevel = 'Debug' } } $script:LogLevel = $LogLevel if (-not $LogPath) { $logDirectory = Join-Path -Path (Get-Location).Path -ChildPath 'Logs' $LogPath = Join-Path -Path $logDirectory -ChildPath "${CommandName}_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" } # Reset per-run state so one command's results never leak into the next in the same session $script:StreamingResults = $null $script:RemovalResults = @() $script:CurrentDomainSID = $null $initParams = @{ ConfigPath = $ConfigPath LogPath = $LogPath MaxMemoryUsageMB = $MaxMemoryUsageMB CorrelationId = $CorrelationId LogLevel = $LogLevel SuppressConsoleOutput = $SuppressConsoleOutput } $initResults = Initialize-ScriptExecution @initParams $script:Config = $initResults.Config $script:MemoryManager = $initResults.MemoryManager $script:Statistics = $initResults.Statistics $script:LogPath = $initResults.LogPath Write-StructuredLog "$CommandName starting (CorrelationId: $CorrelationId)" -Level Information -Component 'Main' -CorrelationId $CorrelationId return $initResults } |