Pslogg.psm1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 |
<#
.SYNOPSIS Functions for logging messages to the host or to PowerShell streams and, optionally, to a log file. .DESCRIPTION A module for logging messages to the host or to PowerShell streams, such as the Error stream or the Information stream. In addition, messages may optionally be logged to a log file. Messages are logged using the Write-LogMessage function. The logger may be configured prior to logging any messages via function Set-LogConfiguration. For example, the logger may be configured to write to the PowerShell host, or to PowerShell streams such as the Error stream or the Verbose stream. Function Get-LogConfiguration will return a copy of the current logger configuration as a hash table. The configuration can be reset back to its default values via function Reset-LogConfiguration. .NOTES #> . $PSScriptRoot\Configuration.ps1 # Function naming conventions: # ---------------------------- # Functions to be exported: Follow the standard PowerShell naming convention of # "<verb>-<singular noun>", eg "Write-LogMessage". # # Private functions that # shouldn't be exported: Have a "Private_" prefix. They must not include a dash, "-". # # These naming conventions simplify the exporting of public functions but not private ones # because we can then simply export all functions containing a dash, "-". #region Write Log Messages ************************************************************************ <# .SYNOPSIS Writes a message to the host or a stream and, optionally, to a log file. .DESCRIPTION Writes a message to either the host or to PowerShell streams such as the Information stream or the Verbose stream, depending on the logging configuration. In addition the message may be written to a log file, once again depending on the logging configuration. .NOTES The Pslogg logger can be configured via function Set-LogConfiguration with settings that persist between messages. For example, it can be configured to write to the PowerShell host, or to PowerShell streams such as the Error stream or the Verbose stream. The most important configuration setting is the LogLevel. This determines which messages will be logged and which will not. Possible LogLevels, in order from lowest to highest, are: OFF ERROR WARNING INFORMATION DEBUG VERBOSE Each message to be logged has a Message Level. This may be set explicitly when calling Write-LogMessage or the default value of INFORMATION may be used. The Message Level is compared to the LogLevel in the logger configuration. Only messages with a Message Level the same as or lower than the configured LogLevel will be logged. For example, if the LogLevel is INFORMATION then only messages with a Message Level of INFORMATION, WARNING or ERROR will be logged. Messages with a Message Level of DEBUG or VERBOSE will not be logged, as those levels are higher than INFORMATION. When calling Write-LogMessage the Message Level can be set in two different ways: 1) Via parameter -MessageLevel: The Message Level is specified as text, for example: Write-LogMessage 'Hello world' -MessageLevel 'VERBOSE' 2) Via Message Level switch parameters: There are switch parameters for each possible Message Level: -IsError, -IsWarning, -IsInformation, -IsDebug and -IsVerbose. For example: Write-LogMessage 'Hello world' -IsVerbose Only one Message Level switch may be set for a given message. Several configuration settings can be overridden for a single log message. The changes apply only to that one message; subsequent messages will return to using the settings in the logger configuration. Settings that can be overridden on a per-message basis are: 1) The message destination: The message can be logged to a different destination from the one specified in the logger configuration by using the switch parameters -WriteToHost or -WriteToStreams; 2) The host text color: If the message is being written to the host, as opposed to PowerShell streams, its text color can be set via parameter -HostTextColor. Any PowerShell console color can be used; 3) The message format: The format of the message can be set via parameter -MessageFormat. .PARAMETER Message The message to be logged. .PARAMETER HostTextColor The ForegroundColor the message will be written in, if the message is written to the host. If the message is written to a PowerShell stream, such as the Information stream, this color is ignored. Acceptable values are: 'Black', 'DarkBlue', 'DarkGreen', 'DarkCyan', 'DarkRed', 'DarkMagenta', 'DarkYellow', 'Gray', 'DarkGray', 'Blue', 'Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'White'. .PARAMETER MessageFormat: A string that sets the format of the text that will be logged. Text enclosed in curly braces, {...}, represents the name of a field which will be included in the logged text. The field names are not case sensitive. Any other text, not enclosed in curly braces, will be treated as a string literal and will appear in the logged text exactly as specified. Leading spaces in the MessageFormat string will be retained when the text is written to the log to allow log messages to be indented. Trailing spaces in the MessageFormat string will be removed, and will not be written to the log. Possible field names are: {Message} : The supplied text message to write to the log; {Timestamp} : The date and time the log message is recorded. The Timestamp field may include an optional datetime format string, inside the curly braces, following the field name and separated from it by a colon, ':'. For example, '{Timestamp:T}'. Any .NET datetime format string is valid. For example, "{Timestamp:d}" will format the timestamp using the short date pattern, which is "MM/dd/yyyy" in the US. While the field names in the MessageFormat string are NOT case sentive the datetime format string IS case sensitive. This is because .NET datetime format strings are case sensitive. For example, "d" is the short date pattern while "D" is the long date pattern. The Timestamp field may be specified without any datetime format string. For example, '{Timestamp}'. In that case the default datetime format string, 'yyyy-MM-dd hh:mm:ss.fff', will be used; {CallerName} : The name of the function or script that is writing to the log. When determining the caller name all functions in this module will be ignored; the caller name will be the external function or script that calls into this module to write to the log. If a function is writing to the log the function name will be displayed. If the log is being written to from a script file, outside any function, the name of the script file will be displayed. If the log is being written to manually from the Powershell console then '[CONSOLE]' will be displayed. {Category} : The Message Category. If no Message Category is explicitly specified when calling Write-LogMessage the default Category from the logger configuration will be used. {MessageLevel} : The Message Level at which the message is being recorded. For example, the message may be an Error message or a Debug message. The MessageLevel will always be displayed in upper case. .PARAMETER MessageLevel A string that specifies the Message Level of the message. Possible values are the LogLevels: ERROR WARNING INFORMATION DEBUG VERBOSE The Message Level is compared to the LogLevel in the logger configuration. If the Message Level is the same as or lower than the LogLevel the message will be logged. If the Message Level is higher than the LogLevel the message will not be logged. For example, if the LogLevel is INFORMATION then only messages with a Message Level of INFORMATION, WARNING or ERROR will be logged. Messages with a Message Level of DEBUG or VERBOSE will not be logged, as those levels are higher than INFORMATION. -MessageLevel cannot be specified at the same time as one of the Message Level switch parameters: -IsError, -IsWarning, -IsInformation, -IsDebug or -IsVerbose. Either -MessageLevel can be specified or one or the Message Level switches can be specified but not both. In addition to determining whether the message will be logged or not, -MessageLevel has the following effects: 1) If the message is set to be written to a PowerShell stream it determines which stream the message will be written to: The Error stream, the Warning stream, the Information stream, the Debug stream or the Verbose stream; 2) If the message is set to be written to the host and the -HostTextColor parameter is not specified -MessageLevel determines the ForegroundColor the message will be written in. The appropriate color is read from the logger configuration HostTextColor hash table. For example, if the -MessageLevel is ERROR the text ForegroundColor will be set to the color specified by logger configuration HostTextColor.Error; 3) The {MessageLevel} placeholder in the MessageFormat string, if present, will be replaced by the -MessageLevel text. For example, if -MessageLevel is ERROR the {MessageLevel} placeholder will be replaced by the text 'ERROR'. .PARAMETER IsError Sets the Message Level to ERROR. -IsError is one of the Message Type switch parameters. Only one Message Type switch may be set at the same time. The Message Type switch parameters are: -IsError, -IsWarning, -IsInformation, -IsDebug, -IsVerbose. .PARAMETER IsWarning Sets the Message Level to WARNING. -IsWarning is one of the Message Type switch parameters. Only one Message Type switch may be set at the same time. The Message Type switch parameters are: -IsError, -IsWarning, -IsInformation, -IsDebug, -IsVerbose. .PARAMETER IsInformation Sets the Message Level to INFORMATION. -IsInformation is one of the Message Type switch parameters. Only one Message Type switch may be set at the same time. The Message Type switch parameters are: -IsError, -IsWarning, -IsInformation, -IsDebug, -IsVerbose. .PARAMETER IsDebug Sets the Message Level to DEBUG. -IsDebug is one of the Message Type switch parameters. Only one Message Type switch may be set at the same time. The Message Type switch parameters are: -IsError, -IsWarning, -IsInformation, -IsDebug, -IsVerbose. .PARAMETER IsVerbose Sets the Message Level to VERBOSE. -IsVerbose is one of the Message Type switch parameters. Only one Message Type switch may be set at the same time. The Message Type switch parameters are: -IsError, -IsWarning, -IsInformation, -IsDebug, -IsVerbose .PARAMETER Category A string that specifies the Message Category of the message. Any string can be specified. The Message Category can have a color specified in the configuration CategoryInfo hash table. If the -HostTextColor parameter is not specified and the message is being written to the host, the ForegroundColor will be set to the CategoryInfo color from the configuration. For example, if the -Category is 'Success' the text ForegroundColor will be set to the logger configuration CategoryInfo.Success.Color. -Category will default to the configuration CategoryInfo name which has IsDefault set. .PARAMETER WriteToHost A switch parameter that, if set, will write the message to the host, as opposed to one of the PowerShell streams such as Error or Warning, overriding the logger configuration setting WriteToHost. -WriteToHost and -WriteToStreams cannot both be set at the same time. .PARAMETER WriteToStreams A switch parameter that complements -WriteToHost. If set the message will be written to a PowerShell stream. This overrides the logger configuration setting WriteToHost. Which PowerShell stream is written to is determined by the Message Level, which may be set via the -MessageLevel parameter or by one of the Message Level switch parameters: -IsError, -IsWarning, -IsInformation, -IsDebug or -IsVerbose. -WriteToHost and -WriteToStreams cannot both be set at the same time. .EXAMPLE Write error message to the log: try { ... } catch [System.IO.FileNotFoundException] { Write-LogMessage -Message "Error while updating file: $_.Exception.Message" -IsError } .EXAMPLE Write debug message to the log: Write-LogMessage "Updating user settings for $userName..." -IsDebug The -Message parameter is optional. .EXAMPLE Write to the log, specifying the MessageLevel rather than using a Message Level switch: Write-LogMessage "Updating user settings for $userName..." -MessageLevel 'DEBUG' .EXAMPLE Write message to the log with a certain category: Write-LogMessage 'File copy completed successfully.' -Category 'Success' -IsInformation .EXAMPLE Write message to the PowerShell host in a specified color: Write-LogMessage 'Updating user settings...' -WriteToHost -HostTextColor Cyan The MessageLevel wasn't specified so it will default to INFORMATION. .EXAMPLE Write message to the Debug PowerShell stream, rather than to the host: Write-LogMessage 'Updating user settings...' -WriteToStreams -IsDebug .EXAMPLE Write message with a custom message format which only applies to this one message: Write-LogMessage "***** Running on server: $serverName *****" -MessageFormat '{message}' The message written to the log will only include the specified message text. It will not include other fields, such as {Timestamp} or {CallerName}. .LINK Get-LogConfiguration .LINK Set-LogConfiguration .LINK Reset-LogConfiguration #> function Write-LogMessage { [CmdletBinding(DefaultParameterSetName='MessageLevelText')] Param ( [Parameter(Mandatory=$False, Position=0)] [string]$Message, [Parameter(Mandatory=$False)] [ValidateScript({Private_ValidateHostColor $_})] [string]$HostTextColor, [Parameter(Mandatory=$False)] [string]$MessageFormat, [Parameter(Mandatory=$False, � ParameterSetName='MessageLevelText')] [ValidateScript({ Private_ValidateLogLevel -LevelToTest $_ -ExcludeOffLevel })] [string]$MessageLevel, [Parameter(Mandatory=$False, � ParameterSetName='MessageLevelSwitches')] [switch]$IsError, [Parameter(Mandatory=$False, � ParameterSetName='MessageLevelSwitches')] [switch]$IsWarning, [Parameter(Mandatory=$False, � ParameterSetName='MessageLevelSwitches')] [switch]$IsInformation, [Parameter(Mandatory=$False, � ParameterSetName='MessageLevelSwitches')] [switch]$IsDebug, [Parameter(Mandatory=$False, � ParameterSetName='MessageLevelSwitches')] [switch]$IsVerbose, [Parameter(Mandatory=$False)] [string]$Category, [Parameter(Mandatory=$False)] [switch]$WriteToHost, [Parameter(Mandatory=$False)] [switch]$WriteToStreams ) Private_ValidateSwitchParameterGroup -SwitchList $IsError,$IsWarning,$IsInformation,$IsDebug,$IsVerbose ` -ErrorMessage 'Only one Message Level switch parameter may be set when calling the function. Message Level switch parameters: -IsError, -IsWarning, -IsInformation, -IsDebug, -IsVerbose' Private_ValidateSwitchParameterGroup -SwitchList $WriteToHost,$WriteToStreams ` -ErrorMessage 'Only one Destination switch parameter may be set when calling the function. Destination switch parameters: -WriteToHost, -WriteToStreams' $Timestamp = Get-Date $CallerName = '' $TextColor = $Null $messageFormatInfo = $script:_messageFormatInfo if ($MessageFormat) { $messageFormatInfo = Private_GetMessageFormatInfo $MessageFormat } # Getting the calling object name is an expensive operation so only perform it if needed. if ($messageFormatInfo.FieldsPresent -contains 'CallerName') { $CallerName = Private_GetCallerName } # Parameter sets mean either $MessageLevel is supplied or a message level switch, such as # -IsError, but not both. Of course, they're all optional so none have to be specified, in # which case we set the default values: if ($IsError.IsPresent) { $MessageLevel = 'ERROR' } elseif ($IsWarning.IsPresent) { $MessageLevel = 'WARNING' } elseif ($IsInformation.IsPresent) { $MessageLevel = 'INFORMATION' } elseif ($IsDebug.IsPresent) { $MessageLevel = 'DEBUG' } elseif ($IsVerbose.IsPresent) { $MessageLevel = 'VERBOSE' } # Default. if (-not $MessageLevel) { $MessageLevel = 'INFORMATION' } $configuredLogLevelValue = $script:_logLevels[$script:_logConfiguration.LogLevel] $messageLogLevelValue = $script:_logLevels[$MessageLevel] if ($messageLogLevelValue -gt $configuredLogLevelValue) { return } # Long-winded logic because we want either of the local parameters to override the # configuration setting: If either of the parameters is set ignore the configuration. $LogTarget = '' if ($WriteToHost.IsPresent) { $LogTarget = 'Host' } elseif ($WriteToStreams.IsPresent) { $LogTarget = 'Streams' } elseif ($script:_logConfiguration.WriteToHost) { $LogTarget = 'Host' } else { $LogTarget = 'Streams' } $configuredCategories = @{} if ($script:_logConfiguration.ContainsKey('CategoryInfo')) { $configuredCategories = $script:_logConfiguration.CategoryInfo } if ([string]::IsNullOrWhiteSpace($Category) -and $configuredCategories) { $Category = $configuredCategories.Keys.Where( { $configuredCategories[$_] -is [hashtable] ` -and $configuredCategories[$_].ContainsKey('IsDefault') ` -and $configuredCategories[$_]['IsDefault'] -eq $True }, 'First', 1) } if ($Category) { $Category = $Category.Trim() } $textToLog = $ExecutionContext.InvokeCommand.ExpandString($messageFormatInfo.WorkingFormat) if ($LogTarget -eq 'Host') { if ($HostTextColor) { $TextColor = $HostTextColor } elseif ($Category -and $configuredCategories ` -and $configuredCategories.ContainsKey($Category) ` -and $configuredCategories[$Category].ContainsKey('Color')) { $TextColor = $configuredCategories[$Category].Color } else { switch ($MessageLevel) { ERROR { $TextColor = $script:_logConfiguration.HostTextColor.Error; break } WARNING { $TextColor = $script:_logConfiguration.HostTextColor.Warning; break } INFORMATION { $TextColor = $script:_logConfiguration.HostTextColor.Information; break } DEBUG { $TextColor = $script:_logConfiguration.HostTextColor.Debug; break } VERBOSE { $TextColor = $script:_logConfiguration.HostTextColor.Verbose; break } } } if ($TextColor) { Write-Host $textToLog -ForegroundColor $TextColor } else { Write-Host $textToLog } } elseif ($LogTarget -eq 'Streams') { switch ($MessageLevel) { ERROR { Write-Error $textToLog; break } WARNING { Write-Warning $textToLog; break } INFORMATION { Write-Information $textToLog; break } DEBUG { Write-Debug $textToLog; break } VERBOSE { Write-Verbose $textToLog; break } } } if (-not $script:_logConfiguration.ContainsKey('LogFile') ` -or -not $script:_logConfiguration.LogFile.ContainsKey('Name') ` -or [string]::IsNullOrWhiteSpace($script:_logConfiguration.LogFile.Name)) { return } if (-not (Test-Path $script:_logFilePath -IsValid)) { # Fail silently so that every message output to the console doesn't include an error # message. return } $overwriteLogFile = $False if ($script:_logConfiguration.LogFile.ContainsKey('Overwrite')) { $overwriteLogFile = $script:_logConfiguration.LogFile.Overwrite } if ($overwriteLogFile -and (-not $script:_logFileOverwritten)) { Set-Content -Path $script:_logFilePath -Value $textToLog $script:_logFileOverwritten = $True } else { Add-Content -Path $script:_logFilePath -Value $textToLog } } <# .SYNOPSIS Gets the name of the function calling into this module. .DESCRIPTION Walks up the call stack until it finds a stack frame where the ScriptName is not the filename of this module. If the call stack cannot be read then the function returns '[UNKNOWN CALLER]'. If no stack frame is found with a different ScriptName then the function returns "----". If the ScriptName of the first stack frame outside of this module is $Null then the module is being called from the PowerShell console. In that case the function returns '[CONSOLE]'. If the ScriptName of the first stack frame outside of this module is NOT $Null then the module is being called from a script file. In that case the function will return the the stack frame FunctionName, unless the FunctionName is "<ScriptBlock>". A FunctionName of "<ScriptBlock>" means the module is being called from the root of a script file, outside of any function. In that case the function returns "Script <script short file name>". The script short file name returned will include the file extension but not any path information. .NOTES This function is NOT intended to be exported from this module. #> function Private_GetCallerName() { $callStack = Get-PSCallStack if ($callStack -eq $null -or $callStack.Count -eq 0) { return '[UNKNOWN CALLER]' } $thisFunctionStackFrame = $callStack[0] $thisModuleFileName = $thisFunctionStackFrame.ScriptName $stackFrameFileName = $thisModuleFileName # Skip this function in the call stack as we've already read it. We also know there must # be at least two stack frames in the call stack as this function will only be called from # another function in this module, so it's safe to skip the first stack frame. $i = 1 $stackFrameFunctionName = '----' while ($stackFrameFileName -eq $thisModuleFileName -and $i -lt $callStack.Count) { $stackFrame = $callStack[$i] $stackFrameFileName = $stackFrame.ScriptName $stackFrameFunctionName = $stackFrame.FunctionName $i++ } if ($stackFrameFileName -eq $null) { return '[CONSOLE]' } if ($stackFrameFunctionName -eq '<ScriptBlock>') { $scriptFileNameWithoutPath = (Split-Path -Path $stackFrameFileName -Leaf) return "Script $scriptFileNameWithoutPath" } return $stackFrameFunctionName } #endregion # Set up initial conditions. Reset-LogConfiguration # Only export public functions. To simplify the exporting of public functions but not private # ones public functions must follow the standard PowerShell naming convention, # "<verb>-<singular noun>", while private functions must not contain a dash, "-". Export-ModuleMember -Function *-* |