powershell-logging.psm1
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 LogFile { [string]$Name [string]$FullName [string]$Folder [string[]]$LogLevels [array]$LogLines [bool]$WriteThrough = $true [bool]$isEncrypted = $false [bool]$active LogFile($name, $folder, $loglevel) { if (Test-Path -Path $folder) { $this.LogLevels = $loglevel $this.Folder = (Get-Item $folder).FullName $this.Name = $name $this.FullName = Join-Path -Path $this.Folder -ChildPath "$name.log" $this.active = $true if (Test-Path -Path $this.FullName) { $this.Import() } else { New-Item -Path $this.FullName -ItemType File } } else { throw "folder '$folder' not existant" } } LogFile($name, $folder, $loglevel, [SecureString]$password) { if (Test-Path -Path $folder) { $this.LogLevels = $loglevel $this.Folder = (Get-Item $folder).FullName $this.Name = $name $this.FullName = Join-Path -Path $this.Folder -ChildPath "$name.log" $this.active = $true if (Test-Path -Path $this.FullName) { $this.isEncrypted = $true $this.Decrypt($password) $this.Import() } else { New-Item -Path $this.FullName -ItemType File } } else { throw "folder '$folder' not existant" } } LogFile($path, $loglevel) { $this.Folder = Split-Path -Path $path -Parent if (Test-Path -Path $this.Folder) { $this.LogLevels = $loglevel $this.Name = Split-Path -Path $path -Leaf $this.FullName = $path $this.active = $true if (Test-Path -Path $this.FullName -PathType Leaf) { $this.Import() } else { New-Item -Path $this.FullName -ItemType File } } else { throw "folder '$($this.Folder)' not existant" } } LogFile($path, $loglevel, [SecureString]$password) { $this.Folder = Split-Path -Path $path -Parent if (Test-Path -Path $this.Folder) { $this.LogLevels = $loglevel $this.Name = Split-Path -Path $path -Leaf $this.FullName = $path $this.active = $true if (Test-Path -Path $this.FullName -PathType Leaf) { $this.isEncrypted = $true $this.Decrypt($password) $this.Import() } else { New-Item -Path $this.FullName -ItemType File } } else { throw "folder '$($this.Folder)' not existant" } } AddLine($severity, $message) { if(!$this.active){throw "Log '$($this.Name)' is inactive! Open it again to use it."} $logline = [LogLine]::new($severity, $message) if ($this.LogLevels -contains $logline.Severity.Name) { Write-Host -Object $logline.ToString() -ForegroundColor $logline.Severity.Color } $this.LogLines += $logline if ($this.WriteThrough) { $this.SaveFile() } } Clear() { if(!$this.active){throw "Log '$($this.Name)' is inactive! Open it again to use it."} $this.LogLines = @() New-Item -Path $this.FullName -ItemType File -Force } Move($newLocation) { if(!$this.active){throw "Log '$($this.Name)' is inactive! Open it again to use it."} if (Test-Path -Path $newLocation) { $newLocationItem = Get-Item -Path $newLocation if ($newLocationItem -is [System.IO.DirectoryInfo]) { $dest = Move-Item -Path $this.FullName -Destination $newLocation -PassThru $this.Folder = $dest.DirectoryName $this.FullName = $dest.FullName } else { throw "destination needs to be a folder" } } else { throw "folder '$newLocation' not existant" } } Rename($newName) { if(!$this.active){throw "Log '$($this.Name)' is inactive! Open it again to use it."} $newPath = Join-Path -Path $this.Folder -ChildPath $newName if (-not(Test-Path -Path $newPath)) { $dest = Rename-Item -Path $this.FullName -NewName "$newName.log" -PassThru $this.Name = $dest.Name $this.FullName = $dest.FullName } else { throw "file '$newName' allready existant" } } SaveFile() { if(!$this.active){throw "Log '$($this.Name)' is inactive! Open it again to use it."} $unsavedLines = $this.LogLines | Where-Object -Property "Saved" -EQ $false if ($unsavedLines.Count -lt 1) { throw "nothing to save!" } $newContent = $unsavedLines | ForEach-Object -Process { $_.ToString() } $joinedContent = $newContent -join [Environment]::NewLine try { Out-File -InputObject $joinedContent -Append -FilePath $this.FullName -Encoding utf8 $unsavedLines | ForEach-Object -Process { $_.saved = $true } } catch { throw "Error Saving File" } } Import() { if(!$this.active){throw "Log '$($this.Name)' is inactive! Open it again to use it."} $availableContent = (Get-Content $this.FullName) -split [Environment]::NewLine foreach ($line in $availableContent) { $newline = [LogLine]::new($line) $newline.Saved = $true $this.LogLines += $newline } } Encrypt([SecureString]$password) { if(!$this.active){throw "Log '$($this.Name)' is inactive! Open it again to use it."} if (-not$this.isEncrypted) { $hashKey = $this.HashPassword($password) $firstKey = [int32[]]$hashKey.ToCharArray()[0..31] $secondKey = [int32[]]$hashKey.ToCharArray()[32..63] $encryptedLog = Get-Content -Path $this.FullName | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString -Key $firstKey | Out-String | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString -Key $secondKey Set-Content -Path $this.FullName -Force -Value $encryptedLog $this.isEncrypted = $true } else { throw "File allready encrypted" } } Decrypt([SecureString]$password) { if(!$this.active){throw "Log '$($this.Name)' is inactive! Open it again to use it."} if ($this.isEncrypted) { $hashKey = $this.HashPassword($password) $firstKey = [int32[]]$hashKey.ToCharArray()[0..31] $secondKey = [int32[]]$hashKey.ToCharArray()[32..63] try{ $SecureString = Get-Content -Path $this.FullName | ConvertTo-SecureString -Key $secondKey -ErrorAction Stop } catch{ throw [exception]::new("Password is incorrect") } $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString) $splitSecureString = ([string]([Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr))).split("`n") $decryptedSecureString = $splitSecureString[0..($splitSecureString.Length-2)] |ConvertTo-SecureString -Key $firstKey $decryptedLog = $decryptedSecureString |ForEach-Object { $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($_) return [string]([Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)) } Set-Content -Path $this.FullName -Force -Value $decryptedLog [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) $this.isEncrypted = $false } else { throw "File not encrypted" } } Close(){ if(!$this.active){throw "Log '$($this.Name)' is inactive! Open it again to use it."} $this.active = $false } hidden [string] HashPassword([SecureString]$password){ $stringAsStream = [System.IO.MemoryStream]::new() $writer = [System.IO.StreamWriter]::new($stringAsStream) $writer.write(([PSCredential]::new("user", $password)).GetNetworkCredential().Password) $writer.Flush() $stringAsStream.Position = 0 $hashKey = Get-FileHash -InputStream $stringAsStream | Select-Object -ExpandProperty Hash return $hashKey } } function Clear-Log(){ [CmdletBinding(PositionalBinding=$false)] param( [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 } $LogConnection.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 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-Log(){ [CMDLetBinding(PositionalBinding=$false)] param( # new directory [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 } $LogConnection.Move($Path) } end{} } function Open-Log(){ [CmdletBinding(DefaultParameterSetName="__DEFAULT")] [Alias("Connect-Log")] param( [parameter(Mandatory=$true,Position=0,ParameterSetName="__DEFAULT")] [string] $Name, [Parameter(Mandatory=$false,Position=1,ParameterSetName="__DEFAULT")] [String] $LogPath = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Desktop), [parameter(Mandatory=$false,Position=2,ParameterSetName="__DEFAULT")] [SecureString] $Password, [Parameter(Mandatory=$true,Position=0,ParameterSetName="LOGFULLNAME")] [System.IO.FileInfo] $LogFullName, [switch] $ShowDebug, [switch] $ShowVerbose, [switch] $ShowInfo, [switch] $ShowWarning, [switch] $ShowSuccess, [switch] $ShowError, [switch] $WriteThrough ) 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 } if([string]::isnullorempty($PSBoundParameters.WriteThrough)){ $WriteThrough = $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"} if($PsCmdlet.ParameterSetName -eq "LOGFULLNAME"){ $Script:LogConnection = [LogFile]::new($LogFullName, $LogLevel) } else{ $invalidCharIndex = $Name.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) if($invalidCharIndex -gt -1){ try{ if($null -eq $Password){ $Script:LogConnection = [LogFile]::new($Name, $LogLevel) } else{ $Script:LogConnection = [LogFile]::new($Name, $LogLevel, $Password) } } catch{ throw "There is an invalid character `"$($Name[$invalidCharIndex])`" at position $invalidCharIndex of the logname `"$Name`"" } } else{ if($null -eq $Password){ $Script:LogConnection = [LogFile]::new($Name, $LogPath, $LogLevel) } else{ $Script:LogConnection = [LogFile]::new($Name, $LogPath, $LogLevel, $Password) } } } $Script:LogConnection.WriteThrough = $WriteThrough return $Script:LogConnection } end{} } function Protect-Log(){ [CmdletBinding()] param( [parameter(Mandatory=$true)] [SecureString]$Password, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin{ } process{ if($null -eq $LogConnection){ throw "Use `"Open-Log`" first, to connect to a logfile!" return } $LogConnection.encrypt($Password) } end{} } function Rename-Log(){ [CMDLetBinding(PositionalBinding=$false)] param( # new directory [Parameter(Mandatory=$true, Position=0)] [string] $NewName, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin{ } process { if($null -eq $LogConnection){ throw "Use `"Open-Log`" first, to connect to a logfile!" return } $LogConnection.Rename($NewName) } end{} } function Save-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 } if($LogConnection.isEncrypted){ throw "Use Unprotect-Log first, to edit this logfile!" return } $LogConnection.SaveFile() } end{} } function Switch-Log(){ param( [parameter(Mandatory=$true)] [LogFile]$LogConnection ) begin{} process{ $Script:LogConnection = $LogConnection return $Script:LogConnection } end{} } function Unprotect-Log(){ [CmdletBinding()] param( [parameter(Mandatory=$true)] [SecureString]$Password, [parameter()] [LogFile] $LogConnection = $Script:LogConnection ) begin{ } process{ if($null -eq $LogConnection){ throw "Use `"Open-Log`" first, to connect to a logfile!" return } $LogConnection.decrypt($Password) } 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 } if($LogConnection.isEncrypted){ throw "Use Unprotect-Log first, to edit this logfile!" return } $LogConnection.AddLine($Severity, $LogLine) } end{} } |