BAMCIS.Logging.psm1
#region Logging Function Write-Log { <# .SYNOPSIS Writes to a log file and echoes the message to the console. .DESCRIPTION The cmdlet writes text or a PowerShell ErrorRecord to a log file and displays the log message to the console at the specified logging level. .PARAMETER Message The message to write to the log file. .PARAMETER ErrorRecord Optionally specify a PowerShell ErrorRecord object to include with the message. .PARAMETER Level The level of the log message, this is either INFO, WARNING, ERROR, DEBUG, or VERBOSE. This defaults to INFO. .PARAMETER Path The path to the log file. If this is not specified, the message is only echoed out. .PARAMETER NoInfo Specify to not add the timestamp and log level to the message being written. .INPUTS System.String The log message can be piped to Write-Log .OUTPUTS None .EXAMPLE try { $Err = 10 / 0 } catch [Exception] { Write-Log -Message $_.Exception.Message -ErrorRecord $_ -Level ERROR } Writes an ERROR log about dividing by 0 to the default log path. .EXAMPLE Write-Log -Message "The script is starting" Writes an INFO log to the default log path. .NOTES AUTHOR: Michael Haken LAST UPDATE: 8/24/2016 #> [CmdletBinding()] [OutputType()] Param( [Parameter()] [ValidateSet("INFO", "WARNING", "ERROR", "DEBUG", "VERBOSE", "FATAL", "VERBOSEERROR")] [System.String]$Level = "INFO", [Parameter(Position = 0, ValueFromPipeline = $true, ParameterSetName = "Message", Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String]$Message, [Parameter(Position = 0, ValueFromPipeline = $true, ParameterSetName = "Error", Mandatory = $true)] [Parameter(Position = 1, ParameterSetName = "Message")] [ValidateNotNull()] [System.Management.Automation.ErrorRecord]$ErrorRecord, [Parameter()] [System.String]$Path, [Parameter()] [Switch]$NoInfo ) Begin { Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState } Process { if ($ErrorRecord -ne $null) { if (-not [System.String]::IsNullOrEmpty($Message)) { $Message += "`r`n" } $Message += ("Exception: `n" + ($ErrorRecord.Exception | Select-Object -Property * | Format-List | Out-String) + "`n") $Message += ("Category: " + ($ErrorRecord.CategoryInfo.Category.ToString()) + "`n") $Message += ("Stack Trace: `n" + ($ErrorRecord.ScriptStackTrace | Format-List | Out-String) + "`n") $Message += ("Invocation Info: `n" + ($ErrorRecord.InvocationInfo | Format-List | Out-String)) } if ($NoInfo) { $Content = $Message } else { $Lvl = $Level if ($Level -eq "VERBOSEERROR") { $Lvl = "ERROR" } $Content = "$(Get-Date) : [$Lvl] $Message" } if ([System.String]::IsNullOrEmpty($Path)) { $Path = [System.Environment]::GetEnvironmentVariable("LogPath", [System.EnvironmentVariableTarget]::Machine) } if (-not [System.String]::IsNullOrEmpty($Path)) { try { Add-Content -Path $Path -Value $Content -ErrorAction Stop } catch [System.UnauthorizedAccessException] { Write-Verbose -Message "Could not write to log file, you probably need to run the cmdlet with administrative privileges." } catch [Exception] { Write-Verbose -Message "Could not write to log file : $($_.Exception.Message)`n$Content" } } switch ($Level) { "INFO" { Write-Host $Content break } "WARNING" { Write-Warning -Message $Content break } "ERROR" { Write-Error -Message $Content break } "DEBUG" { Write-Debug -Message $Content break } "VERBOSE" { Write-Verbose -Message $Content break } "VERBOSEERROR" { Write-Verbose -Message $Content break } "FATAL" { throw (New-Object -TypeName System.Exception($Content)) } default { Write-Warning -Message "Could not determine log level to write." Write-Host $Content break } } } End { } } Function Write-CMTraceLog { <# .SYNOPSIS Writes a log file formatted to be read by the CMTrace tool. .DESCRIPTION The cmdlet takes a message and writes it to a file in the format that can be read by CMTrace. .PARAMETER Message The message to be written to the file. .PARAMETER FilePath The path of the file to write the log information. .PARAMETER LogLevel The log level of the message. 1 is Informational, 2 is Warning, and 3 is Error. This defaults to Informational. .PARAMETER Component The component generating the log file. .PARAMETER Thread The thread ID of the process running the task. This defaults to the current managed thread ID. .PARAMETER ErrorRecord Specify a PowerShell ErrorRecord object to include with the message. The resulting message content will be in JSON format. .EXAMPLE Write-CMTraceLog -Message "Test Warning Message" -FilePath "c:\logpath.log" -LogLevel 2 -Component "PowerShell" This command writes "Test Warning Message" to c:\logpath.log and sets it as a Warning message in the CMTrace log viewer tool. .INPUTS System.String, System.Management.Automation.ErrorRecord .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 10/25/2017 .FUNCTIONALITY The intended use of this cmdlet is to write CMTrace formatted log files to be used with the viewer tool. #> [CmdletBinding()] [OutputType()] Param( [Parameter(Position = 0, ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = "Message")] [ValidateNotNullOrEmpty()] [System.String]$Message = [System.String]::Empty, [Parameter(Position = 1, Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String]$FilePath, [Parameter(Position = 2)] [ValidateSet(1,2,3)] [System.Int32]$LogLevel = 1, [Parameter(Position = 3)] [ValidateNotNullOrEmpty()] [System.String]$Component = [System.String]::Empty, [Parameter(Position = 4)] [System.Int32]$Thread = 0, [Parameter(ParameterSetName = "Message")] [Parameter(Position = 0, Mandatory = $true, ParameterSetName = "Error")] [ValidateNotNull()] [System.Management.Automation.ErrorRecord]$ErrorRecord = $null ) Begin { Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState } Process { if ($Thread -eq 0) { $Thread = [System.Threading.Thread]::CurrentThread.ManagedThreadId } $Date = Get-Date $Time = ($Date.ToString("HH:mm:ss.fff") + "+" + ([System.TimeZone]::CurrentTimeZone.GetUtcOffset((Get-Date)).TotalMinutes * -1)) $Day = $Date.ToString("MM-dd-yyyy") if ($ErrorRecord -ne $null) { [System.Collections.Hashtable]$Data = @{Exception = $ErrorRecord.Exception; Category = $ErrorRecord.CategoryInfo.Category.ToString(); StackTrace = $ErrorRecord.ScriptStackTrace; InvocationInfo = $ErrorRecord.InvocationInfo} if (-not [System.String]::IsNullOrEmpty($Message)) { $Data.Add("Message", $Message) } $Message = ConvertTo-Json -InputObject $Data -Compress } $File = $FilePath.Substring($FilePath.LastIndexOf("\") + 1) [System.String]$Log = @" <![LOG[$Message]LOG]!><time="$Time" date="$Day" component="$Component" context="" type="$LogLevel" thread="$Thread" file="$File"> "@ Add-Content -Path $FilePath -Value $Log -Force } End { } } Function Get-CallerPreference { <# .SYNOPSIS Fetches "Preference" variable values from the caller's scope. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, but they can be obtained through the $PSCmdlet variable in Advanced Functions. This function is a helper function for any script module Advanced Function; by passing in the values of $ExecutionContext.SessionState and $PSCmdlet, Get-CallerPreference will set the caller's preference variables locally. .PARAMETER Cmdlet The $PSCmdlet object from a script module Advanced Function. .PARAMETER SessionState The $ExecutionContext.SessionState object from a script module Advanced Function. This is how the Get-CallerPreference function sets variables in its callers' scope, even if that caller is in a different script module. .PARAMETER Name Optional array of parameter names to retrieve from the caller's scope. Default is to retrieve all Preference variables as defined in the about_Preference_Variables help file (as of PowerShell 4.0) This parameter may also specify names of variables that are not in the about_Preference_Variables help file, and the function will retrieve and set those as well. .EXAMPLE Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Imports the default PowerShell preference variables from the caller into the local scope. .EXAMPLE Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState -Name 'ErrorActionPreference','SomeOtherVariable' Imports only the ErrorActionPreference and SomeOtherVariable variables into the local scope. .EXAMPLE 'ErrorActionPreference','SomeOtherVariable' | Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Same as Example 2, but sends variable names to the Name parameter via pipeline input. .INPUTS System.String .OUTPUTS None .NOTES AUTHOR: Dave Wyatt (Original Content) Michael Haken (Updates) LAST MODIFIED: 10/25/2017 #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [ValidateScript({ $_.GetType().FullName -eq "System.Management.Automation.PSScriptCmdlet" })] [ValidateNotNull()] $Cmdlet, [Parameter(Mandatory = $true)] [ValidateNotNull()] [System.Management.Automation.SessionState]$SessionState, [Parameter(ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [System.String[]]$Name = @() ) Begin { [System.Collections.Hashtable]$FilterHash = @{} } Process { # Add any filter values supplied via the pipeline foreach ($Item in $Name) { $FilterHash.Add($Item, $true) } } End { # List of preference variables taken from the about_Preference_Variables help file in PowerShell version 4.0 [System.Collections.Hashtable]$Preferences = @{ 'ErrorView' = $null 'FormatEnumerationLimit' = $null 'LogCommandHealthEvent' = $null 'LogCommandLifecycleEvent' = $null 'LogEngineHealthEvent' = $null 'LogEngineLifecycleEvent' = $null 'LogProviderHealthEvent' = $null 'LogProviderLifecycleEvent' = $null 'MaximumAliasCount' = $null 'MaximumDriveCount' = $null 'MaximumErrorCount' = $null 'MaximumFunctionCount' = $null 'MaximumHistoryCount' = $null 'MaximumVariableCount' = $null 'OFS' = $null 'OutputEncoding' = $null 'ProgressPreference' = $null 'PSDefaultParameterValues' = $null 'PSEmailServer' = $null 'PSModuleAutoLoadingPreference' = $null 'PSSessionApplicationName' = $null 'PSSessionConfigurationName' = $null 'PSSessionOption' = $null 'ErrorActionPreference' = 'ErrorAction' 'DebugPreference' = 'Debug' 'ConfirmPreference' = 'Confirm' 'WhatIfPreference' = 'WhatIf' 'VerbosePreference' = 'Verbose' 'WarningPreference' = 'WarningAction' } foreach ($Item in $Preferences.GetEnumerator()) { # If the preference doesn't have a value or the caller command wasn't supplied that parameter explicitly AND # we're not filtering or if we are, that the filter contains the key, # go ahead and get the preference value from the session state of the calling module if ([System.String]::IsNullOrEmpty($Item.Value) -or -not $Cmdlet.MyInvocation.BoundParameters.ContainsKey($Item.Value) -and ($FilterHash.Count -eq 0 -or $FilterHash.ContainsKey($Item.Name))) { $Variable = $Cmdlet.SessionState.PSVariable.Get($Item.Key) if ($Variable -ne $null) { # If the provided session state is the same as the current session state, set the variable locally since they are in the # same module if ($SessionState -eq $ExecutionContext.SessionState) { Set-Variable -Scope 1 -Name $Variable.Name -Value $Variable.Value -Force -Confirm:$false -WhatIf:$false } # Otherwise set them in the provided session state else { $SessionState.PSVariable.Set($Variable.Name, $Variable.Value) } } } } # If we are filtering, and the filter was not in the standard set of preferences (like a custom preference), # add it here if ($FilterHash.Count -gt 0) { foreach ($Key in $FilterHash.Keys) { if (-not $Preferences.ContainsKey($Key)) { $Variable = $Cmdlet.SessionState.PSVariable.Get($Key) if ($Variable -ne $null) { if ($SessionState -eq $ExecutionContext.SessionState) { Set-Variable -Scope 1 -Name $Variable.Name -Value $Variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($Variable.Name, $Variable.Value) } } } } } } } #endregion |