diff --git a/Scriptmonkey_Beta.ps1 b/Scriptmonkey_Beta.ps1 index 694459c..40ad316 100644 --- a/Scriptmonkey_Beta.ps1 +++ b/Scriptmonkey_Beta.ps1 @@ -410,7 +410,285 @@ $ConfirmPreference = 'None' - #region Write-Log + #region Write-Log + + # Fallback logger used when the SVSMSP module (and its Write-Log) is not available. + # Mirrors the behaviour of the toolkit Write-Log (v1.5), including: + # - Default EventLog: "SVSMSP Events" (out of Application log) + # - Default EventSource: "SVSMSP_Module" + # - Level-based Event IDs and console colors + # - Global in-memory log cache + # - One-time Event Log/source initialization with optional auto-elevation + function Write-LogHelper { + <# + .SYNOPSIS + Standardized logging utility with console/file output and Windows Event Log support, + including one-time event source initialization and optional auto-elevated creation + of a custom log/source. (Fallback implementation for ScriptMonkey.) + + .DESCRIPTION + Mirrors the SVSMSP toolkit Write-Log so that Write-LogHybrid can safely fall back + when the module isn’t loaded. + + .NOTES + Default EventLog : SVSMSP Events + Default Source : SVSMSP_Module + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$Message, + + [ValidateSet("Info", "Warning", "Error", "Success", "General")] + [string]$Level = "Info", + + [string]$TaskCategory = "GeneralTask", + + [switch]$LogToEvent = $false, + + [string]$EventSource = "SVSMSP_Module", + + # Custom log name so you get your own node under "Applications and Services Logs" + [string]$EventLog = "SVSMSP Events", + + [int]$CustomEventID, + + [string]$LogFile, + + [switch]$PassThru + ) + + # ---------- Event ID / console color ---------- + $EventID = if ($CustomEventID) { $CustomEventID } else { + switch ($Level) { + "Info" { 1000 } + "Warning" { 2000 } + "Error" { 3000 } + "Success" { 4000 } + default { 1000 } + } + } + + $Color = switch ($Level) { + "Info" { "Cyan" } + "Warning" { "Yellow" } + "Error" { "Red" } + "Success" { "Green" } + default { "White" } + } + + $FormattedMessage = "[$Level] [$TaskCategory] $Message (Event ID: $EventID)" + Write-Host $FormattedMessage -ForegroundColor $Color + + # ---------- In-memory cache ---------- + if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) { + $Global:LogCache = [System.Collections.ArrayList]::new() + } + + $logEntry = [PSCustomObject]@{ + Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") + Level = $Level + Message = $FormattedMessage + } + [void]$Global:LogCache.Add($logEntry) + + # ---------- Optional file output ---------- + if ($LogFile) { + try { + "$($logEntry.Timestamp) $FormattedMessage" | + Out-File -FilePath $LogFile -Append -Encoding UTF8 + } + catch { + Write-Host "[Warning] Failed to write to log file: $($_.Exception.Message)" -ForegroundColor Yellow + } + } + + # ---------- Windows Event Log handling with one-time init + optional auto-elevate ---------- + if ($LogToEvent) { + + # Per-run cache for (LogName|Source) init state + if (-not $Global:EventSourceInitState) { + $Global:EventSourceInitState = @{} + } + + $EntryType = switch ($Level) { + "Info" { "Information" } + "Warning" { "Warning" } + "Error" { "Error" } + "Success" { "Information" } # treat success as info in Event Log + default { "Information" } + } + + $sourceKey = "$EventLog|$EventSource" + + if (-not $Global:EventSourceInitState.ContainsKey($sourceKey) -or + -not $Global:EventSourceInitState[$sourceKey]) { + + try { + # Only bother if the source doesn't already exist + if (-not [System.Diagnostics.EventLog]::SourceExists($EventSource)) { + + # Check if current token is admin + $isAdmin = $false + try { + $current = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = New-Object Security.Principal.WindowsPrincipal($current) + $isAdmin = $principal.IsInRole( + [Security.Principal.WindowsBuiltInRole]::Administrator + ) + } + catch { + $isAdmin = $false + } + + if ($isAdmin) { + # Elevated already: create log/source directly + New-EventLog -LogName $EventLog -Source $EventSource -ErrorAction Stop + } + else { + # Not elevated: run a one-off helper as admin to create log/source + $helperScript = @" +if (-not [System.Diagnostics.EventLog]::SourceExists('$EventSource')) { + New-EventLog -LogName '$EventLog' -Source '$EventSource' +} +"@ + + $tempPath = [System.IO.Path]::Combine( + $env:TEMP, + "Init_${EventLog}_$EventSource.ps1".Replace(' ', '_') + ) + + $helperScript | Set-Content -Path $tempPath -Encoding UTF8 + + try { + # This will trigger UAC prompt in interactive sessions + $null = Start-Process -FilePath "powershell.exe" ` + -ArgumentList "-ExecutionPolicy Bypass -File `"$tempPath`"" ` + -Verb RunAs -Wait -PassThru + } + catch { + Write-Host "[Warning] Auto-elevation to create Event Log '$EventLog' / source '$EventSource' failed: $($_.Exception.Message)" -ForegroundColor Yellow + } + finally { + Remove-Item -Path $tempPath -ErrorAction SilentlyContinue + } + } + } + + # Re-check after creation attempt + if ([System.Diagnostics.EventLog]::SourceExists($EventSource)) { + $Global:EventSourceInitState[$sourceKey] = $true + } + else { + $Global:EventSourceInitState[$sourceKey] = $false + Write-Host "[Warning] Event source '$EventSource' does not exist and could not be created. Skipping Event Log write." -ForegroundColor Yellow + } + } + catch { + Write-Host "[Warning] Failed to initialize Event Log '$EventLog' / source '$EventSource': $($_.Exception.Message)" -ForegroundColor Yellow + $Global:EventSourceInitState[$sourceKey] = $false + } + } + + # Only write if initialization succeeded + if ($Global:EventSourceInitState[$sourceKey]) { + try { + $EventMessage = "TaskCategory: $TaskCategory | Message: $Message" + Write-EventLog -LogName $EventLog -Source $EventSource -EntryType $EntryType -EventId $EventID -Message $EventMessage + } + catch { + Write-Host "[Warning] Failed to write to Event Log: $($_.Exception.Message)" -ForegroundColor Yellow + } + } + } + # ------------------------------------------------------------------------------------------ + + if ($PassThru) { + return $logEntry + } + } + + # ───────────────────────────────────────────────────────────────────────── + # WRITE-LOG HYBRID + # Uses module Write-Log if present; otherwise falls back to Write-LogHelper. + # Defaults aligned with toolkit: + # EventSource = "SVSMSP_Module" + # EventLog = "SVSMSP Events" + # ───────────────────────────────────────────────────────────────────────── + function Write-LogHybrid { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Message, + + [ValidateSet("Info", "Warning", "Error", "Success", "General")] + [string]$Level = "Info", + + [string]$TaskCategory = "GeneralTask", + + [switch]$LogToEvent, + + [string]$EventSource = "SVSMSP_Module", + + [string]$EventLog = "SVSMSP Events", + + [int]$CustomEventID, + + [string]$LogFile, + + [switch]$PassThru, + + [ValidateSet("Black","DarkGray","Gray","White","Red","Green","Blue","Yellow","Magenta","Cyan")] + [string]$ForegroundColorOverride + ) + + $formatted = "[$Level] [$TaskCategory] $Message" + + # Build the common parameter set for forwarding into Write-Log / Write-LogHelper + $invokeParams = @{ + Message = $Message + Level = $Level + TaskCategory = $TaskCategory + LogToEvent = $LogToEvent + EventSource = $EventSource + EventLog = $EventLog + } + + if ($PSBoundParameters.ContainsKey('CustomEventID')) { + $invokeParams.CustomEventID = $CustomEventID + } + if ($PSBoundParameters.ContainsKey('LogFile')) { + $invokeParams.LogFile = $LogFile + } + if ($PassThru) { + $invokeParams.PassThru = $true + } + + if ($PSBoundParameters.ContainsKey('ForegroundColorOverride')) { + # 1) print to console with the override color + Write-Host $formatted -ForegroundColor $ForegroundColorOverride + + # 2) then forward the call (sans the override) to Write-Log or Write-LogHelper + if (Get-Command Write-Log -ErrorAction SilentlyContinue) { + Write-Log @invokeParams + } + else { + Write-LogHelper @invokeParams + } + } + else { + # No override: let Write-Log / Write-LogHelper handle everything (including console color) + if (Get-Command Write-Log -ErrorAction SilentlyContinue) { + Write-Log @invokeParams + } + else { + Write-LogHelper @invokeParams + } + } + } + + #endregion Write-Log + # This function is used as a fallback if the SVSMSP module is not installed # Should change this "[string]$EventLog = "Application", => [string]$EventLog = "SVS Scripting", "