powershell-logging.psm1
Enum LogTargetType{ File Console Stream None } Class Severity { $Name $Color Severity($name) { switch ($name) { "DEBUG" { $this.Color = [System.ConsoleColor]::DarkBlue $this.Name = "DEBUG" break } "VERBOSE" { $this.Color = [System.ConsoleColor]::DarkYellow $this.Name = "VERBOSE" break } "INFO" { $this.Color = [System.ConsoleColor]::White $this.Name = "INFO" break } "WARNING" { $this.Color = [System.ConsoleColor]::Yellow $this.Name = "WARNING" break } "SUCCESS" { $this.Color = [System.ConsoleColor]::Green $this.Name = "SUCCESS" break } "ERROR" { $this.Color = [System.ConsoleColor]::Red $this.Name = "ERROR" break } default { break } } } [string] ToString() { return $this.Name } } Class LogLine { [DateTime]$DateTime [string]$User [string]$Domain [Severity]$Severity [String]$Message [bool]$Saved = $false LogLine($severity, $message) { $this.DateTime = Get-Date if (-not[string]::IsNullOrEmpty($env:USERNAME)) { $this.User = $env:USERNAME.ToLower() } elseif (-not[string]::IsNullOrEmpty($env:USER)) { $this.User = $env:USER.ToLower() } if (-not[string]::IsNullOrEmpty($env:USERDNSDOMAIN)) { $this.Domain = $env:USERDNSDOMAIN.ToLower() } elseif (-not[string]::IsNullOrEmpty($env:COMPUTERNAME)) { $this.Domain = $env:COMPUTERNAME.ToLower() } elseif (-not[string]::IsNullOrEmpty($env:NAME)) { $this.Domain = $env:NAME.ToLower() } else { $this.Domain = $(hostname).ToLower() } $this.Severity = [Severity]::new($severity) $this.Message = $message.trim() } LogLine([string]$line) { $lineRegex = [regex]::new("(\d{4}\-\d{2}\-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{4})\s\-\s(.+?)@(.+?)\s\-\s+([A-Z]{1,7})\s\-\s(.+)$") $match = $lineRegex.Match($line) if ($match.Success) { $this.DateTime = Get-Date $match.Groups[1].Value $this.User = $match.Groups[2].Value $this.Domain = $match.Groups[3].Value $this.Severity = [Severity]::new($match.Groups[4].Value) $this.Message = $match.Groups[5].Value.trim() } else { Write-Warning "Skipping Line (Format not recognized)$([Environment]::NewLine)`t`"$line`"" } } [string] ToString() { return "{0} - {1}@{2} - {3} - {4}" -f @( (Get-Date $this.DateTime -Format "yyyy-MM-dd HH:mm:ss.ffff") $this.User $this.Domain $this.Severity.Name.padLeft(7, " ") $this.Message ) } } Class LogTarget { [string]$GUID [LogTargetType]$Type [bool]$Active = $true LogTarget([LogTargetType]$type) { $this.GUID = [GUID]::NewGuid().GUID.ToUpper() $this.Type = $type } Disable(){ $this.Active = $false } Enable(){ $this.Active = $true } hidden checkState(){ if(!$this.Active){ throw "the target `"$($this.GUID)`" is inactive, please enable it to use it again!" } } Set([LogLine[]]$logLines) { throw "not implemented" } [LogLine[]] Get() { throw "not implemented" } Rename() { throw "not implemented" } Move() { throw "not implemented" } Clear() { throw "not implemented" } [String] ToString() { return "LogTarget.$($this.Type)" } } Class LogTargetConsole : LogTarget { [Severity[]]$severitiesToDisplay LogTargetConsole($severitiesToDisplay) : base([LogTargetType]::Console) { $this.severitiesToDisplay = $severitiesToDisplay } Set([LogLine[]]$logLines) { $this.checkState() $logLines | ForEach-Object -Process { $_logLine = $_ if ($this.severitiesToDisplay.Name -contains $_logLine.Severity.Name) { Write-Host -Object $logline.ToString() -ForegroundColor $logline.Severity.Color } } } [LogLine[]] Get() { $this.checkState() return @() } Rename() { throw "cannot rename the console target" } Move() { throw "cannot move the console target" } Clear() { Clear-Host } } Class LogTargetFile : LogTarget { [System.IO.FileSystemInfo]$fileInformation LogTargetFile($filePath) : base([LogTargetType]::File) { try { $this.fileInformation = Get-Item -Path $filePath -ErrorAction Stop } catch { $this.fileInformation = New-Item -Path $filePath -ItemType File -Force } } Set([LogLine[]]$logLines) { $this.checkState() $newContent = $logLines | ForEach-Object -Process { $_.ToString() } $joinedContent = $newContent -join [Environment]::NewLine try { Out-File -InputObject $joinedContent -Append -FilePath $this.fileInformation.FullName -Encoding utf8 } catch { throw "Error Saving File" } } [LogLine[]] Get() { $this.checkState() $availableContent = (Get-Content -Encoding UTF8 -Path $this.fileInformation.FullName) -split [Environment]::NewLine $returnedLines = @() foreach ($line in $availableContent) { $newline = [LogLine]::new($line) $newline.Saved = $true $returnedLines += $newline } return $returnedLines } Rename($newName) { $this.checkState() $dest = Rename-Item -Path $this.fileInformation.FullName -NewName $newName -PassThru $this.fileInformation = $dest } Move($newLocation) { $this.checkState() if (Test-Path -Path $newLocation) { $newLocationItem = Get-Item -Path $newLocation if ($newLocationItem -is [System.IO.DirectoryInfo]) { $dest = Move-Item -Path $this.fileInformation.FullName -Destination $newLocation -PassThru $this.fileInformation = $dest } else { throw "destination needs to be a folder" } } else { throw "folder '$newLocation' not existant" } } Clear() { New-Item -Path $this.fileInformation.FullName -ItemType File -Force } } Class LogTargetStream : LogTarget { [Severity[]]$severitiesToDisplay LogTargetStream($severitiesToDisplay) : base([LogTargetType]::Console) { $this.severitiesToDisplay = $severitiesToDisplay } Set([LogLine[]]$logLines) { $this.checkState() $logLines | ForEach-Object -Process { $_logLine = $_ if ($this.severitiesToDisplay.Name -contains $_logLine.Severity.Name) { switch ($_logLine.Severity.Name) { "DEBUG" { Write-Debug -Message $_logLine.Message break } "VERBOSE" { Write-Verbose -Message $_logLine.Message break } "INFO" { Write-Information -MessageData $_logLine.Message break } "WARNING" { Write-Warning -Message $_logLine.Message break } "SUCCESS" { Write-Host -Object $_logLine.Message break } "ERROR" { Write-Error -Message $_logLine.Message break } } } } } [LogLine[]] Get() { $this.checkState() return @() } Rename() { throw "cannot rename the stream target" } Move() { throw "cannot move the stream target" } Clear() { Clear-Host } } Class LogFile { [string]$Name [array]$LogLines [bool]$Active [LogTarget[]]$Targets LogFile($name) { $this.Name = $name $this.Active = $true } [LogTarget] AddTarget([LogTargetType]$targetType = [LogTargetType]::File, $targetArguments) { $newTaget = New-Object -TypeName "LogTarget$targetType" -ArgumentList @($targetArguments.Values) $this.Targets += $newTaget if ($newTaget | Get-Member -MemberType Method -Name Get) { $this.LogLines += $newTaget.Get() $this.LogLines = $this.LogLines | Sort-Object -Property DateTime -Unique } return $newTaget } RemoveTarget([GUID]$GUID) { $newTargets = $this.Targets | Where-Object -Property GUID -NE $GUID.Guid $this.Targets = $newTargets } AddLine($severity, $message) { if (!$this.Active) { throw "Log '$($this.Name)' is inactive! Open it again to use it." } $logline = [LogLine]::new($severity, $message) $this.LogLines += $logline $this.Targets |Where-Object -Property Active -EQ -Value $true | ForEach-Object -Process { if ($null -eq $_) { continue } $_.Set($logline); } } Close() { if (!$this.active) { throw "Log '$($this.Name)' is inactive! Open it again to use it." } $this.active = $false } } function Add-LogTarget() { [CMDLetBinding(PositionalBinding = $false, DefaultParameterSetName = "file")] param( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "file", ValueFromPipelineByPropertyName = $true)] [string] $FullName, [Parameter(Mandatory = $false)] [ValidateSet("Host", "Stream", "None")] [string] $ConsoleType = "Host", [Parameter(Mandatory = $true, ParameterSetName = "console")] [Switch] $Console, [Parameter(Mandatory = $false, ParameterSetName = "console")] [switch] $ShowDebug, [Parameter(Mandatory = $false, ParameterSetName = "console")] [switch] $ShowVerbose, [Parameter(Mandatory = $false, ParameterSetName = "console")] [switch] $ShowInfo, [Parameter(Mandatory = $false, ParameterSetName = "console")] [switch] $ShowWarning, [Parameter(Mandatory = $false, ParameterSetName = "console")] [switch] $ShowSuccess, [Parameter(Mandatory = $false, ParameterSetName = "console")] [switch] $ShowError, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin { if ([string]::isnullorempty($PSBoundParameters.ShowInfo)) { $ShowInfo = $true } if ([string]::isnullorempty($PSBoundParameters.ShowWarning)) { $ShowWarning = $true } if ([string]::isnullorempty($PSBoundParameters.ShowSuccess)) { $ShowSuccess = $true } if ([string]::isnullorempty($PSBoundParameters.ShowError)) { $ShowError = $true } } process { if ($null -eq $LogConnection) { throw "Use `"Open-Log`" first, to connect to a logfile!" return } switch ($PsCmdlet.ParameterSetName) { "file" { $fileTarget = $LogConnection.AddTarget([LogTargetType]::File, [ordered]@{ filePath = $FullName }) break; } "console" { if ($LogConnection.Targets.Type -Contains "Console") { throw "there can only be one console target!" } $LogLevel = @() if ($ShowDebug) { $LogLevel += "DEBUG" } if ($ShowVerbose) { $LogLevel += "VERBOSE" } if ($ShowInfo) { $LogLevel += "INFO" } if ($ShowWarning) { $LogLevel += "WARNING" } if ($ShowSuccess) { $LogLevel += "SUCCESS" } if ($ShowError) { $LogLevel += "ERROR" } switch ($ConsoleType) { "Host" { $consoleTarget = $LogConnection.AddTarget([LogTargetType]::Console, [ordered]@{ severitiesToDisplay = [Severity[]]$LogLevel }) break } "Stream" { $consoleTarget = $LogConnection.AddTarget([LogTargetType]::Stream, [ordered]@{ severitiesToDisplay = [Severity[]]$LogLevel }) break } default { # add no console target } } break; } } return $LogConnection } end {} } function Clear-LogTarget(){ [CmdletBinding(PositionalBinding=$false, DefaultParameterSetName="GUID")] param( [Parameter(ParameterSetName="GUID",ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [GUID] $GUID, [Parameter(ParameterSetName="pipeline",ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [LogTarget[]] $Targets, [parameter(ValueFromPipeline = $false)] [LogFile] $LogConnection = $Script:LogConnection ) begin{ } process { if($null -eq $LogConnection){ throw "Use `"Open-Log`" first, to connect to a logfile!" return } if($PsCmdlet.ParameterSetName -eq "GUID"){ $Targets = $LogConnection.Targets |Where-Object -Property GUID -EQ $GUID } $Targets |ForEach-Object -Process {$_.Clear()} } end{} } function Close-Log(){ [CmdletBinding()] param( [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin{ } process{ if($null -eq $Script:LogConnection){ throw "Use `"Open-Log`" first, to connect to a logfile!" return } $LogConnection.close() Remove-Variable "LogConnection" -Scope "Script" } end{} } function Disable-LogTarget() { [CmdletBinding(PositionalBinding=$false, DefaultParameterSetName="GUID")] param( [Parameter(ParameterSetName = "GUID", ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [GUID] $GUID, [Parameter(ParameterSetName = "pipeline", ValueFromPipelineByPropertyName = $true)] [LogTarget[]] $Targets, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin { } process { if ($null -eq $LogConnection) { throw "Use `"Open-Log`" first, to connect to a logfile!" return } if($PsCmdlet.ParameterSetName -eq "GUID"){ $Targets = $LogConnection.Targets | Where-Object -Property GUID -EQ $GUID } $Targets |ForEach-Object -Process {$_.Disable()} } end {} } function Enable-LogTarget() { [CmdletBinding(PositionalBinding=$false, DefaultParameterSetName="GUID")] param( [Parameter(ParameterSetName = "GUID", ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [GUID] $GUID, [Parameter(ParameterSetName = "pipeline", ValueFromPipelineByPropertyName = $true)] [LogTarget[]] $Targets, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin { } process { if ($null -eq $LogConnection) { throw "Use `"Open-Log`" first, to connect to a logfile!" return } if($PsCmdlet.ParameterSetName -eq "GUID"){ $Targets = $LogConnection.Targets | Where-Object -Property GUID -EQ $GUID } $Targets |ForEach-Object -Process {$_.Enable()} } end {} } function Get-Log(){ [CmdletBinding()] param( [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin{ } process{ if($null -eq $LogConnection){ throw "Use `"Open-Log`" first, to connect to a logfile!" return } return $LogConnection } end{} } function Get-LogContent(){ [CmdletBinding(DefaultParameterSetName="__default")] param( [Parameter(ValueFromRemainingArguments=$true)] [string] $Filter, [int32] [Parameter(ParameterSetName="First")] $First, [int32] [Parameter(ParameterSetName="Last")] $Last, [switch] $IncludeDebug, [switch] $IncludeVerbose, [switch] $IncludeInfo, [switch] $IncludeWarning, [switch] $IncludeSuccess, [switch] $IncludeError, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin{ } process{ if($null -eq $LogConnection){ throw "Use `"Open-Log`" first, to connect to a logfile!" return } if($LogConnection.isEncrypted){ throw "Use Unprotect-Log first, to edit this logfile!" return } $Lines = $LogConnection.LogLines if(![string]::IsNullOrEmpty($PSBoundParameters.Filter)){ $Lines = $Lines |Where-Object -FilterScript { $_LogLine = $_ $_LogLine.Domain -like $Filter -or $_LogLine.User -like $Filter -or $_LogLine.Message -like $Filter } } if($PSBoundParameters.IncludeDebug -or $PSBoundParameters.IncludeVerbose -or $PSBoundParameters.IncludeInfo -or $PSBoundParameters.IncludeWarning -or $PSBoundParameters.IncludeSuccess -or $PSBoundParameters.IncludeError ){ $selectedSeverityLevels = @() if($PSBoundParameters.IncludeDebug){ $selectedSeverityLevels += "DEBUG" } if($PSBoundParameters.IncludeVerbose){ $selectedSeverityLevels += "VERBOSE" } if($PSBoundParameters.IncludeInfo){ $selectedSeverityLevels += "INFO" } if($PSBoundParameters.IncludeWarning){ $selectedSeverityLevels += "WARNING" } if($PSBoundParameters.IncludeSuccess){ $selectedSeverityLevels += "SUCCESS" } if($PSBoundParameters.IncludeError){ $selectedSeverityLevels += "ERROR" } $Lines = $Lines |Where-Object -FilterScript { $_LogLine = $_ $_LogLine.Severity.Name -in $selectedSeverityLevels } } if(![string]::IsNullOrEmpty($PSBoundParameters.First)){ $Lines = $Lines |Select-Object -First $First } elseif(![string]::IsNullOrEmpty($PSBoundParameters.Last)){ $Lines = $Lines |Select-Object -Last $Last } return $Lines } end{} } function Move-LogTarget() { [CmdletBinding(PositionalBinding=$false, DefaultParameterSetName="GUID")] param( [Parameter(ParameterSetName = "GUID", ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [GUID] $GUID, [Parameter(ParameterSetName = "pipeline", ValueFromPipelineByPropertyName = $true)] [LogTarget] $Targets, [Parameter(Mandatory = $true, Position = 0)] [string] $Path, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin { } process { if ($null -eq $LogConnection) { throw "Use `"Open-Log`" first, to connect to a logfile!" return } if($PsCmdlet.ParameterSetName -eq "GUID"){ $Targets = $LogConnection.Targets | Where-Object -Property GUID -EQ $GUID } $Targets |ForEach-Object -Process {$_.Move($Path)} } end {} } function Open-Log(){ [CmdletBinding()] [Alias("Connect-Log")] param( [parameter(Mandatory=$true,Position=0,ValueFromPipelineByPropertyName=$true)] [string] $Name, [Parameter(ParameterSetName="file",ValueFromPipelineByPropertyName=$true)] [Alias("DirectoryName")] [String] $LogPath = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Desktop), [Parameter(Mandatory=$false)] [ValidateSet("Host", "Stream", "None")] [string] $ConsoleType = "Host", [switch] $ShowDebug, [switch] $ShowVerbose, [switch] $ShowInfo, [switch] $ShowWarning, [switch] $ShowSuccess, [switch] $ShowError ) begin{ if([string]::isnullorempty($PSBoundParameters.ShowInfo)){ $ShowInfo = $true } if([string]::isnullorempty($PSBoundParameters.ShowWarning)){ $ShowWarning = $true } if([string]::isnullorempty($PSBoundParameters.ShowSuccess)){ $ShowSuccess = $true } if([string]::isnullorempty($PSBoundParameters.ShowError)){ $ShowError = $true } } process{ # try{Close-Log}catch{} $LogLevel = @() if($ShowDebug){$LogLevel += "DEBUG"} if($ShowVerbose){$LogLevel += "VERBOSE"} if($ShowInfo){$LogLevel += "INFO"} if($ShowWarning){$LogLevel += "WARNING"} if($ShowSuccess){$LogLevel += "SUCCESS"} if($ShowError){$LogLevel += "ERROR"} $Script:LogConnection = [LogFile]::new($Name) switch($ConsoleType){ "Host" { $consoleTarget = $Script:LogConnection.AddTarget([LogTargetType]::Console, [ordered]@{ severitiesToDisplay = [Severity[]]$LogLevel }) break } "Stream" { $consoleTarget = $Script:LogConnection.AddTarget([LogTargetType]::Stream, [ordered]@{ severitiesToDisplay = [Severity[]]$LogLevel }) break } default { # add no console target } } if($PsCmdlet.ParameterSetName -eq "file"){ if(-not($Name -like "*.log")){$Name += ".log"} $fileTarget = $Script:LogConnection.AddTarget([LogTargetType]::File, [ordered]@{ filePath = (Join-Path -Path $LogPath -ChildPath $Name) }) } return $Script:LogConnection } end{} } function Remove-LogTarget() { [CmdletBinding(PositionalBinding=$false)] param( [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [GUID] $GUID, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin { } process { if ($null -eq $LogConnection) { throw "Use `"Open-Log`" first, to connect to a logfile!" return } $Target = $LogConnection.Targets | Where-Object -Property GUID -EQ $GUID $LogConnection.RemoveTarget($Target.GUID) } end {} } function Rename-LogTarget() { [CmdletBinding(PositionalBinding=$false, DefaultParameterSetName="GUID")] param( [Parameter(ParameterSetName = "GUID", ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [GUID]$GUID, [Parameter(ParameterSetName = "pipeline", ValueFromPipelineByPropertyName = $true)] [LogTarget] $Targets, [Parameter(Mandatory = $true, Position = 1)] [string] $NewName, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin { } process { if ($null -eq $LogConnection) { throw "Use `"Open-Log`" first, to connect to a logfile!" return } if($PsCmdlet.ParameterSetName -eq "GUID"){ $Targets = $LogConnection.Targets | Where-Object -Property GUID -EQ $GUID } $Targets |ForEach-Object -Process {$_.Rename($NewName)} } end {} } function Switch-Log(){ param( [parameter(Mandatory=$true)] [LogFile]$LogConnection ) begin{} process{ $Script:LogConnection = $LogConnection return $Script:LogConnection } end{} } function Write-Log(){ [CMDLetBinding(PositionalBinding=$false)] [Alias("ulog")] param( # severity of logline [Parameter(Mandatory=$true, Position=0)] [ValidateSet("DEBUG", "VERBOSE", "INFO", "WARNING", "SUCCESS", "ERROR")] [string] $Severity, # actual error text [Parameter(Mandatory=$true, Position=1, ValueFromRemainingArguments=$true)] [string] $LogLine, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin{ } process { if($null -eq $LogConnection){ throw "Use `"Open-Log`" first, to connect to a logfile!" return } $LogConnection.AddLine($Severity, $LogLine) } end{} } |