Add src/logging.fallback.ps1
This commit is contained in:
334
src/logging.fallback.ps1
Normal file
334
src/logging.fallback.ps1
Normal file
@@ -0,0 +1,334 @@
|
||||
#region Globals
|
||||
|
||||
if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) {
|
||||
$Global:LogCache = [System.Collections.ArrayList]::new()
|
||||
}
|
||||
|
||||
if (-not $Global:EventSinkCache -or -not ($Global:EventSinkCache -is [hashtable])) {
|
||||
$Global:EventSinkCache = @{}
|
||||
}
|
||||
|
||||
#endregion Globals
|
||||
|
||||
#region Helpers: Formatting + File
|
||||
|
||||
function Get-LogColor {
|
||||
param(
|
||||
[ValidateSet("Info", "Warning", "Error", "Success", "General")]
|
||||
[string]$Level
|
||||
)
|
||||
switch ($Level) {
|
||||
"Info" { "Cyan" }
|
||||
"Warning" { "Yellow" }
|
||||
"Error" { "Red" }
|
||||
"Success" { "Green" }
|
||||
default { "White" }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-EventIdForLevel {
|
||||
param(
|
||||
[ValidateSet("Info", "Warning", "Error", "Success", "General")]
|
||||
[string]$Level,
|
||||
[int]$CustomEventID
|
||||
)
|
||||
if ($CustomEventID) { return $CustomEventID }
|
||||
switch ($Level) {
|
||||
"Info" { 1000 }
|
||||
"Warning" { 2000 }
|
||||
"Error" { 3000 }
|
||||
"Success" { 4000 }
|
||||
default { 1000 }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-EventEntryTypeForLevel {
|
||||
param(
|
||||
[ValidateSet("Info", "Warning", "Error", "Success", "General")]
|
||||
[string]$Level
|
||||
)
|
||||
switch ($Level) {
|
||||
"Info" { "Information" }
|
||||
"Warning" { "Warning" }
|
||||
"Error" { "Error" }
|
||||
"Success" { "Information" }
|
||||
default { "Information" }
|
||||
}
|
||||
}
|
||||
|
||||
function Append-Utf8NoBomLine {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$Path,
|
||||
[Parameter(Mandatory)] [string]$Line
|
||||
)
|
||||
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
||||
[System.IO.File]::AppendAllText($Path, $Line + [Environment]::NewLine, $utf8NoBom)
|
||||
}
|
||||
|
||||
#endregion Helpers: Formatting + File
|
||||
|
||||
#region Helpers: Event Log Binding
|
||||
|
||||
function Test-IsAdmin {
|
||||
try {
|
||||
$current = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = [Security.Principal.WindowsPrincipal]::new($current)
|
||||
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Initialize-EventLogBinding {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$DesiredLog,
|
||||
[Parameter(Mandatory)] [string]$DesiredSource,
|
||||
[ValidateSet('Repair', 'Unique', 'Follow')]
|
||||
[string]$ConflictPolicy = 'Repair'
|
||||
)
|
||||
|
||||
$isAdmin = Test-IsAdmin
|
||||
|
||||
$effectiveLog = $DesiredLog
|
||||
$effectiveSource = $DesiredSource
|
||||
|
||||
function Ensure-LogAndSource {
|
||||
param([string]$LogName, [string]$SourceName)
|
||||
|
||||
if (-not $isAdmin) { return $false }
|
||||
|
||||
if (-not [System.Diagnostics.EventLog]::SourceExists($SourceName)) {
|
||||
New-EventLog -LogName $LogName -Source $SourceName -ErrorAction Stop
|
||||
}
|
||||
elseif ([System.Diagnostics.EventLog]::LogNameFromSourceName($SourceName, '.') -ne $LogName) {
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
if ([System.Diagnostics.EventLog]::SourceExists($DesiredSource)) {
|
||||
$boundLog = [System.Diagnostics.EventLog]::LogNameFromSourceName($DesiredSource, '.')
|
||||
|
||||
if ($boundLog -ne $DesiredLog) {
|
||||
switch ($ConflictPolicy) {
|
||||
'Follow' {
|
||||
$effectiveLog = $boundLog
|
||||
$effectiveSource = $DesiredSource
|
||||
}
|
||||
|
||||
'Unique' {
|
||||
$candidate = "$DesiredSource.SAMY"
|
||||
$i = 0
|
||||
while ([System.Diagnostics.EventLog]::SourceExists($candidate)) {
|
||||
$i++
|
||||
$candidate = "$DesiredSource.SAMY$i"
|
||||
}
|
||||
|
||||
$effectiveLog = $DesiredLog
|
||||
$effectiveSource = $candidate
|
||||
|
||||
$ok = Ensure-LogAndSource -LogName $effectiveLog -SourceName $effectiveSource
|
||||
if (-not $ok) { throw "Unable to create unique Event Log source '$effectiveSource' under '$effectiveLog'." }
|
||||
}
|
||||
|
||||
'Repair' {
|
||||
if (-not $isAdmin) {
|
||||
throw "Event source '$DesiredSource' is bound to '$boundLog' but repair requires elevation."
|
||||
}
|
||||
|
||||
if (Get-Command Remove-EventLog -ErrorAction SilentlyContinue) {
|
||||
Remove-EventLog -Source $DesiredSource -ErrorAction Stop
|
||||
}
|
||||
else {
|
||||
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\$boundLog\$DesiredSource"
|
||||
if (Test-Path $regPath) {
|
||||
Remove-Item -Path $regPath -Recurse -Force -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
|
||||
$effectiveLog = $DesiredLog
|
||||
$effectiveSource = $DesiredSource
|
||||
|
||||
Ensure-LogAndSource -LogName $effectiveLog -SourceName $effectiveSource | Out-Null
|
||||
|
||||
$verify = [System.Diagnostics.EventLog]::LogNameFromSourceName($effectiveSource, '.')
|
||||
if ($verify -ne $effectiveLog) {
|
||||
throw "Repair failed: '$effectiveSource' is still bound to '$verify' (wanted '$effectiveLog')."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Bound correctly. Nothing else required.
|
||||
$effectiveLog = $DesiredLog
|
||||
$effectiveSource = $DesiredSource
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($isAdmin) {
|
||||
$effectiveLog = $DesiredLog
|
||||
$effectiveSource = $DesiredSource
|
||||
Ensure-LogAndSource -LogName $effectiveLog -SourceName $effectiveSource | Out-Null
|
||||
}
|
||||
else {
|
||||
$effectiveLog = 'Application'
|
||||
$effectiveSource = 'Windows PowerShell'
|
||||
}
|
||||
}
|
||||
|
||||
[pscustomobject]@{ LogName = $effectiveLog; Source = $effectiveSource; IsAdmin = $isAdmin }
|
||||
}
|
||||
|
||||
#endregion Helpers: Event Log Binding
|
||||
|
||||
#region Public: Write-LogHelper
|
||||
|
||||
function global:Write-LogHelper {
|
||||
[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",
|
||||
|
||||
[ValidateSet('Repair', 'Unique', 'Follow')]
|
||||
[string]$EventLogConflictPolicy = 'Repair',
|
||||
|
||||
[int]$CustomEventID,
|
||||
|
||||
[string]$LogFile,
|
||||
|
||||
[switch]$PassThru
|
||||
)
|
||||
|
||||
$EventID = Get-EventIdForLevel -Level $Level -CustomEventID $CustomEventID
|
||||
$Color = Get-LogColor -Level $Level
|
||||
$EntryType = Get-EventEntryTypeForLevel -Level $Level
|
||||
|
||||
$FormattedMessage = "[$Level] [$TaskCategory] $Message (Event ID: $EventID)"
|
||||
Write-Host $FormattedMessage -ForegroundColor $Color
|
||||
|
||||
$logEntry = [PSCustomObject]@{
|
||||
Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
|
||||
Level = $Level
|
||||
Message = $FormattedMessage
|
||||
}
|
||||
[void]$Global:LogCache.Add($logEntry)
|
||||
|
||||
if ($LogFile) {
|
||||
try {
|
||||
Append-Utf8NoBomLine -Path $LogFile -Line "$($logEntry.Timestamp) $FormattedMessage"
|
||||
}
|
||||
catch {
|
||||
Write-Host "[Warning] Failed to write to log file: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
if ($LogToEvent) {
|
||||
$desiredKey = "$EventLog|$EventSource|$EventLogConflictPolicy"
|
||||
|
||||
if (-not $Global:EventSinkCache.ContainsKey($desiredKey)) {
|
||||
try {
|
||||
$ev = Initialize-EventLogBinding -DesiredLog $EventLog -DesiredSource $EventSource -ConflictPolicy $EventLogConflictPolicy
|
||||
|
||||
$Global:EventSinkCache[$desiredKey] = [pscustomobject]@{
|
||||
Ready = $true
|
||||
LogName = $ev.LogName
|
||||
Source = $ev.Source
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$Global:EventSinkCache[$desiredKey] = [pscustomobject]@{
|
||||
Ready = $false
|
||||
LogName = $EventLog
|
||||
Source = $EventSource
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
|
||||
Write-Host "[Warning] Failed to initialize Event Log '$EventLog' / source '$EventSource': $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
$sink = $Global:EventSinkCache[$desiredKey]
|
||||
|
||||
if ($sink.Ready) {
|
||||
try {
|
||||
$EventMessage = "TaskCategory: $TaskCategory | Message: $Message"
|
||||
Write-EventLog -LogName $sink.LogName -Source $sink.Source -EntryType $EntryType -EventId $EventID -Message $EventMessage
|
||||
}
|
||||
catch {
|
||||
Write-Host "[Warning] Failed to write to Event Log '$($sink.LogName)' / source '$($sink.Source)': $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "[Warning] Event Log not initialized for '$EventLog' / '$EventSource'. Skipping Event Log write." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
if ($PassThru) { return $logEntry }
|
||||
}
|
||||
|
||||
#endregion Public: Write-LogHelper
|
||||
|
||||
#region Public: Write-LogHybrid
|
||||
|
||||
function global: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",
|
||||
[ValidateSet('Repair', 'Unique', 'Follow')]
|
||||
[string]$EventLogConflictPolicy = 'Repair',
|
||||
[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
|
||||
EventLogConflictPolicy = $EventLogConflictPolicy
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Public: Write-LogHybrid
|
||||
Reference in New Issue
Block a user