ScriptLog.psm1
#Region './Enum/ScriptLogMessageSeverity.ps1' 0 # Define log message severity types enum ScriptLogMessageSeverity { Information Verbose Warning Error } #EndRegion './Enum/ScriptLogMessageSeverity.ps1' 9 #Region './Enum/ScriptLogType.ps1' 0 # Define log types enum ScriptLogType { CMTrace Memory } #EndRegion './Enum/ScriptLogType.ps1' 7 #Region './Classes/01-LogMessage.ps1' 0 # Declare class for individual log messages class LogMessage { [datetime]$DateTime [ScriptLogMessageSeverity]$Severity [string]$Source [string]$Context [int]$ProcessId [string]$Message LogMessage([datetime]$DateTime, [ScriptLogMessageSeverity]$Severity, [string]$Source, [string]$Context, [int]$ProcessId, [string]$Message) { $this.DateTime = $DateTime $this.Severity = $Severity $this.Source = $Source $this.Context = $Context $this.ProcessId = $ProcessId $this.Message = $Message } } #EndRegion './Classes/01-LogMessage.ps1' 19 #Region './Classes/02-ScriptLog.ps1' 0 # Declare class for a log object class ScriptLog { [String] $FilePath [ScriptLogType] $LogType [String] $Source = $null [ScriptLogMessageSeverity[]] $MessagesOnConsole [DateTime] $StartTimeStamp [System.Collections.Generic.List[LogMessage]] $Messages hidden [String] $TimeZoneOffset ScriptLog([String] $Path, [String] $BaseName, [Boolean] $AppendDateTime, [ScriptLogType] $LogType, [ScriptLogMessageSeverity[]] $MessagesOnConsole) { $this.LogType = $LogType $this.MessagesOnConsole = $MessagesOnConsole $this.StartTimeStamp = Get-Date $this.Messages = [System.Collections.Generic.List[LogMessage]]::new() $Offset = [timezone]::CurrentTimeZone.GetUtcOffset([datetime]::Now).TotalMinutes if ($Offset -ge 0) { $this.TimeZoneOffset = "+$Offset" } else { $this.TimeZoneOffset = [string]"$Offset" } if ($LogType -eq 'Memory') { $this.FilePath = $null } else { $ConstructedPath = $Path + '\' + $BaseName if ($AppendDateTime) { $ConstructedPath += '-' + (Get-Date -Format 'yyyyMMddHHmmss') } switch ($LogType) { CMTrace { $ConstructedPath += '.log' } } $this.FilePath = $ConstructedPath } } } #EndRegion './Classes/02-ScriptLog.ps1' 40 #Region './Private/00-Initialization.ps1' 0 $PSDefaultParameterValues.Clear() Set-StrictMode -Version 3 # Prepare value defining the default ScriptLog to log messages to $DefaultScriptLog = $null # Prepare collection to hold ScriptLog objects [System.Collections.Generic.List[ScriptLog]]$ScriptLogs = @() #EndRegion './Private/00-Initialization.ps1' 10 #Region './Public/Get-ScriptLog.ps1' 0 function Get-ScriptLog { <# .SYNOPSIS Returns active ScriptLogs. .DESCRIPTION Returns a list of all active ScriptLogs. .EXAMPLE Get-ScriptLog Returns a list of all active ScriptLogs .EXAMPLE Get-ScriptLog -Default Returns the default ScriptLog .NOTES Author: kovergard #> [CmdletBinding()] [OutputType([ScriptLog[]])] Param ( # If specified, return only the default ScriptLog [Parameter()] [switch] $Default ) process { if ($Default) { if (-not $DefaultScriptLog) { Write-Warning 'No ScriptLogs exists, cannot return default ScriptLog' break } return $DefaultScriptLog } return $ScriptLogs } } #EndRegion './Public/Get-ScriptLog.ps1' 42 #Region './Public/New-ScriptLog.ps1' 0 function New-ScriptLog { <# .SYNOPSIS Returns a new ScriptLog object .DESCRIPTION Creates a new ScriptLog object with the settings provided and returns it through the pipeline so it can be used for logging during script execution using the Out-ScriptLog cmdlet. .EXAMPLE New-ScriptLog Create a new ScriptLog object with default settings. File will be created in the temp folder, with the name ScriptLog.log and will be written in the CMTrace format. .EXAMPLE $MemoryLog = New-ScriptLog -LogType Memory -MessagesOnConsole @("Error","Verbose") Create an in-memory SriptLog instance to allow for collection of log messages during runtime. Only errors and verbose messages will be written to the console (Warnings will not, they will only be written to the in-memory log) .EXAMPLE $CriticalFileLog = New-ScriptLog -Path "C:\Logs" -BaseName "CriticalErrors" -AppendDateTime; $VerboseLog = New-ScriptLog -Path "C:\Logs" -BaseName "Verbose" -MessagesOnConsole "Verbose" Create two separate ScriptLog objects to log messages in different formats to two different files. .NOTES Author: kovergard #> [CmdletBinding()] [OutputType([ScriptLog])] Param ( # Directory in which to create the logfile [Parameter()] [string] $Path = $env:TEMP, # Name of the log file without extension [Parameter()] [string] $BaseName = 'ScriptLog', # Indicates if a datetime should be suffixed on the log base name. [Parameter()] [bool] $AppendDateTime = $false, # Type of log [Parameter()] [ScriptLogType] $LogType = 'CMTrace', # Determines which messages (if any) should be written to the console. [Parameter()] [ScriptLogMessageSeverity[]] $MessagesOnConsole = @('Error', 'Warning') ) process { $NewScriptLog = [ScriptLog]::New($Path, $BaseName, $AppendDateTime, $LogType, $MessagesOnConsole) $Script:ScriptLogs.Add($NewScriptLog) if (-not $DefaultScriptLog) { Set-Variable -Name DefaultScriptLog -Value $NewScriptLog -Scope Script -Force } Write-Output $NewScriptLog } } #EndRegion './Public/New-ScriptLog.ps1' 65 #Region './Public/Out-ScriptLog.ps1' 0 function Out-ScriptLog { <# .SYNOPSIS Adds log messages to a ScriptLog. .DESCRIPTION Adds one or more log messages to a ScriptLog object. If multiple messages are sent via the pipleline, each message will get its own message entry in the log. A single messages can have multiple lines, these will be writting to the log file with line changes. If a message is longer than 7500 characters, it will be broken into multiple messages as longer messages will break the CMTrace format. .EXAMPLE Out-ScriptLog -Message "Starting script execution" Write a log message to the information channel in the default ScriptLog instance. .EXAMPLE Out-ScriptLog -Log $VerboseLog -Message "Starting script execution" -Severity Verbose Write a log message to the verbose channel in the ScriptLog $VerboseLog .EXAMPLE $Dir = Get-ChildItem -Path c:\temp; Out-ScriptLog -Message $Dir -Log $Log Write an object with multiple lines in it to the log file. This will be writtin as a single log message, since the message is not passed through the pipeline. .EXAMPLE "One","Two","Three" | Out-ScriptLog -Severity Warning Send multiple messages to the log using the pipeline. Each message will get its own log message. .NOTES Author: kovergard #> [CmdletBinding()] Param ( # One or more messages to add to the log [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)] $Message, # The ScriptLog object to add the messages to. If no ScriptLog is supplied, logging is done to the default ScriptLog. [Parameter()] [ScriptLog] $Log, # The severity of the messages [Parameter()] [ScriptLogMessageSeverity] $Severity = 'Information' ) process { # Fail if no ScriptLogs exists if ($ScriptLogs.Count -eq 0) { throw 'Use New-ScriptLog to create a ScriptLog before using Out-ScriptLog' } # If no ScriptLog is specified, point to default ScriptLog. if (-not $PSBoundParameters.ContainsKey('Log')) { $Log = $DefaultScriptLog } # Convert message to string if necessary if ($Message.GetType() -ne 'System.String') { $Message = ($Message | Out-String).TrimEnd("`r`n") } # Determine log time and source of message $LogTime = Get-Date if ($Log.Source) { $Source = $Log.Source if ($MyInvocation.ScriptLineNumber) { $Source += ":$($MyInvocation.ScriptLineNumber)" } } else { Try { If ($MyInvocation.ScriptName) { [string]$Source = "$(Split-Path -Path $MyInvocation.ScriptName -Leaf -ErrorAction 'Stop'):$($MyInvocation.ScriptLineNumber)" } Else { $Source = 'interactive' } } Catch { $Source = 'unknown' } } # Get context and PID of message $Context = [Security.Principal.WindowsIdentity]::GetCurrent().Name $ProcessId = $global:PID # Add message to in-memory log. $Log.Messages.Add([LogMessage]::New($LogTime, $Severity, $Source, $Context, $ProcessId, $Message)) # If message should be written to a file, convert to proper format and write. switch ($Log.LogType) { CMTrace { if ($Message.Length -gt 7500) { $CMMessage = $Message.Substring(0, 7500) } else { $CMMessage = $Message } $CmLogLine = '<![LOG[{0}]LOG]!><time="{1}" date="{2}" component="{3}" context="{4}" type="{5}" thread="{6}" file="{7}">' $CmMessageType = Switch ($Severity) { Error { 3 } Warning { 2 } Default { 1 } } $CmTime = ($LogTime | Get-Date -Format 'HH\:mm\:ss.fff').ToString() + $Log.TimeZoneOffset $CmDate = ($LogTime | Get-Date -Format 'MM-dd-yyyy') $CmFile = 'ScriptLog' $CmLogLineFormat = $CMMessage, $CmTime, $CmDate, $Source, $Context, $CmMessageType, $ProcessId, $CmFile $LogLine = $CmLogLine -f $CmLogLineFormat $LogLine | Out-File -FilePath $Log.FilePath -Append -Encoding utf8 -NoClobber } } # Write output to console, if applicable if ($Severity -in $Log.MessagesOnConsole) { Switch ($Severity) { Information { Write-Information -MessageData $Message -InformationAction Continue } Verbose { $VerbosePreference = 'Continue'; Write-Verbose -Message $Message } Warning { Write-Warning -Message $Message } Error { Write-Error -Message $Message } } } } } #EndRegion './Public/Out-ScriptLog.ps1' 144 |