diff --git a/test/Samy.Logging.ps1 b/test/Samy.Logging.ps1 new file mode 100644 index 0000000..45ba6d6 --- /dev/null +++ b/test/Samy.Logging.ps1 @@ -0,0 +1,235 @@ +# Samy.Logging.ps1 +# Logging helpers and Write-LogHybrid fallback + +function Write-LogHelper { + <# + .SYNOPSIS + Simple logging utility with console, file, and Windows Event Log support. + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$Message, + + [ValidateSet("Info", "Warning", "Error", "Success", "General")] + [string]$Level = "Info", + + [string]$TaskCategory = "GeneralTask", + + [switch]$LogToEvent = $false, + + [string]$EventSource = "SAMY", + + [string]$EventLog = "SVSMSP Events", + + [int]$CustomEventID, + + [string]$LogFile, + + [switch]$PassThru + ) + + $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 + + 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) + + 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 + } + } + + if ($LogToEvent) { + + if (-not $Global:EventSourceInitState) { + $Global:EventSourceInitState = @{} + } + + $EntryType = switch ($Level) { + "Info" { "Information" } + "Warning" { "Warning" } + "Error" { "Error" } + "Success" { "Information" } + default { "Information" } + } + + $sourceKey = "$EventLog|$EventSource" + + if (-not $Global:EventSourceInitState.ContainsKey($sourceKey) -or + -not $Global:EventSourceInitState[$sourceKey]) { + + try { + if (-not [System.Diagnostics.EventLog]::SourceExists($EventSource)) { + + $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) { + New-EventLog -LogName $EventLog -Source $EventSource -ErrorAction Stop + } + else { + $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 { + $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 + } + } + } + + 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 + } + } + + 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 + } +} + +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" + + $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')) { + Write-Host $formatted -ForegroundColor $ForegroundColorOverride + + if (Get-Command Write-Log -ErrorAction SilentlyContinue) { + Write-Log @invokeParams + } + else { + Write-LogHelper @invokeParams + } + } + else { + if (Get-Command Write-Log -ErrorAction SilentlyContinue) { + Write-Log @invokeParams + } + else { + Write-LogHelper @invokeParams + } + } +}