Configuration.ps1
# Functions for reading and setting the Pslogg logger configuration. . $PSScriptRoot\Private\SharedFunctions.ps1 <# .SYNOPSIS Gets a copy of the log configuration settings. .DESCRIPTION Gets a copy of the log configuration settings, not a reference to the live configuration. .OUTPUTS A hashtable with the following keys: LogLevel: The session logging level. It determines whether a message will be logged or not. Possible values, in order from highest to lowest, are: VERBOSE DEBUG INFORMATION WARNING ERROR OFF Only messages with a MessageLevel the same as or lower than the 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; LogFile: Properties of the log file that messages can optionally be written to. The hashtable has the following keys: WriteFromScript: If $True enables logging to a file from a script or module. The default value is $True; WriteFromHost: If $True enables logging to a file from the PowerShell host. The default value is $False; Name: The name of the log file. If LogFile.Name is $Null, empty or blank then messages will not be written to a log file. If LogFile.Name is specified without a path, or with a relative path, it will be relative to the directory of the calling script, not the Pslogg module. The LogFile.Name is the raw file name before the path is resolved, and before any date is appended. The default value for Log.FileName is "Results.log"; IncludeDateInFileName: If $True then the log file name will have a date, of the form '_yyyyMMdd' appended to the end of the file name. For example, 'Results_20171129.log'. The default value is $True; Overwrite: If $True any existing log file with the same name as LogFile.Name, including the date if LogFile.IncludeDateInFileName is set, will be overwritten. The log file will only be overwritten by the first message logged in a given session. Subsequent messages written in the same session will be appended to the end of the log file. If $False new log messages will be appended to the end of the existing log file. If no file exists with the same name as LogFile.Name it will be created, regardless of the value of LogFile.Overwrite. The default value is $True; FullPathReadOnly: The fully resolved path to the current log file. This will include the date if LogFile.IncludeDateInFileName is set. It will also be the full absolute path to the log file, rather than a relative path. Any date included in the file name may not necessarily be today's date; the file name returned is simply the name of the file Pslogg is currently configured to write to, whatever that name may be. WriteToHost: If $True then log messages will be written to the PowerShell host. If $False then log messages will be written to the appropriate PowerShell stream. For example, Error messages will be written to the error stream, Warning messages will be written to the warning stream, etc. The default value is $True; MessageFormat: A string that sets the format of log messages. Text enclosed in curly braces, {...}, represents the name of a field which will be included in the logged message. 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 message exactly as specified. Leading spaces in the MessageFormat string will be retained when the message is written to the logs to allow log messages to be indented. Trailing spaces in the MessageFormat string will not be included in the logged messages. 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, following the field name and separated from it by a colon, ":". 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 sensitive 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 default datetime format string is "yyyy-MM-dd hh:mm:ss.fff". {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 Category of the message. It will always be displayed in upper case. {MessageLevel} : The Log 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. The default MessageFormat is: '{Timestamp:yyyy-MM-dd HH:mm:ss.fff} | {CallerName} | {Category} | {MessageLevel} | {Message}'; HostTextColor: A hashtable that specifies the different text colors that will be used for different log levels, for log messages written to the PowerShell console. HostTextColor only applies if WriteToHost is $True. The hashtable has the following keys: Error: The text color for messages of log level Error. The default value is Red; Warning: The text color for messages of log level Warning. The default value is Yellow; Information: The text color for messages of log level Information. The default value is Cyan; Debug: The text color for messages of log level Debug. The default value is White; Verbose: The text color for messages of log level Verbose. The default value is White. Possible values for text colors are: 'Black', 'DarkBlue', 'DarkGreen', 'DarkCyan', 'DarkRed', 'DarkMagenta', 'DarkYellow', 'Gray', 'DarkGray', 'Blue', 'Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'White'; CategoryInfo: A hashtable that defines Categories and their properties. The keys of the CategoryInfo hashtable are the category names and the values are nested hashtables that set the properties of each category. Two properties are supported: IsDefault: Indicates the category that will be used as the default, if no -Category is specified in Write-LogMesssage; Color: The text color for messages of the specified category, if they are written to the PowerShell console. .NOTES Get-LogConfiguration returns a copy of the Pslogg configuration, NOT a reference to the live configuration. This means any changes to the hashtable retrieved by Get-LogConfiguration will NOT be reflected in Pslogg's configuration. The configuration can only be updated via Set-LogConfiguration. This ensures the Pslogg internal state is updated correctly. Although changes to the hashtable retrieved by Get-LogConfiguration will not be reflected in the Pslogg configuration, the updated hashtable can be written back into the Pslogg configuration via Set-LogConfiguration -LogConfiguration. .EXAMPLE Get the text color for messages with category Success: PS C:\Users\Me> $config = Get-LogConfiguration PS C:\Users\Me> $config.CategoryInfo.Success.Color Green .EXAMPLE Get the text colors for all message levels: PS C:\Users\Me> $config = Get-LogConfiguration PS C:\Users\Me> $config.HostTextColor Name Value ---- ----- Debug White Error Red Warning Yellow Verbose White Information Cyan .EXAMPLE Get the text color for messages of level ERROR: PS C:\Users\Me> $config = Get-LogConfiguration PS C:\Users\Me> $config.HostTextColor.Error Red .EXAMPLE Get the name of the file that messages will be logged to: PS C:\Users\Me> $config = Get-LogConfiguration PS C:\Users\Me> $config.LogFile.Name Results.log The name returned is the "raw" file name. It will not include a date, if Pslogg is configured to include dates in log file names. .EXAMPLE Get the full path of the file that messages will be logged to: PS C:\Users\Me> $config = Get-LogConfiguration PS C:\Users\Me> $config.LogFile.FullPathReadOnly C:\Users\Me\Documents\PowerShell\MyTest\Results_20201027.log In contrast to $config.LogFile.Name, $config.LogFile.FullPathReadOnly is the absolute path to the log file. It will include the date, if Pslogg is configured to include dates in log file names. .EXAMPLE Get the format of log messages: PS C:\Users\Me> $config = Get-LogConfiguration PS C:\Users\Me> $config.MessageFormat {Timestamp:yyyy-MM-dd HH:mm:ss.fff} | {CallerName} | {Category} | {MessageLevel} | {Message} .EXAMPLE Use Get-LogConfiguration and Set-LogConfiguration to update Pslogg's configuration: $config = Get-LogConfiguration $config.LogLevel = 'ERROR' $config.LogFile.Name = 'Error.log' $config.CategoryInfo['FileCopy'] = @{Color = 'DarkYellow'} Set-LogConfiguration -LogConfiguration $config .LINK Write-LogMessage .LINK Set-LogConfiguration .LINK Reset-LogConfiguration #> function Get-LogConfiguration() { if ($script:_logConfiguration -eq $Null -or $script:_logConfiguration.Keys.Count -eq 0) { $script:_logConfiguration = Private_DeepCopyHashTable $script:_defaultLogConfiguration } return Private_DeepCopyHashTable $script:_logConfiguration } <# .SYNOPSIS Sets one or more of the log configuration settings. .DESCRIPTION Sets one or more of the log configuration settings. Set-LogConfiguration can set the configuration in one of two ways: 1) By using different parameters to update individual configuration settings; 2) By editing the configuration hashtable, then passing the edited hashtable into parameter -LogConfiguration. .PARAMETER LogConfiguration A hashtable representing all configuration settings. For the hashtable format see the help topic for Get-LogConfiguration. .PARAMETER LogLevel A string that specifies the logging level for the remainder of the current PowerShell session. It determines whether a message will be logged or not. Possible values, in order from highest to lowest, are: VERBOSE DEBUG INFORMATION WARNING ERROR OFF Only messages with a Message Level the same as or lower than the 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. .PARAMETER EnableFileLoggingFromScript A switch parameter that, if set, enables logging to a file from a script or module. -EnableFileLoggingFromScript and -DisableFileLoggingFromScript cannot both be set at the same time. .PARAMETER DisableFileLoggingFromScript A switch parameter that, if set, disables logging to a file from a script or module. -EnableFileLoggingFromScript and -DisableFileLoggingFromScript cannot both be set at the same time. .PARAMETER EnableFileLoggingFromHost A switch parameter that, if set, enables logging to a file from the PowerShell host. -EnableFileLoggingFromHost and -DisableFileLoggingFromHost cannot both be set at the same time. .PARAMETER DisableFileLoggingFromHost A switch parameter that, if set, disables logging to a file from the PowerShell host. -EnableFileLoggingFromHost and -DisableFileLoggingFromHost cannot both be set at the same time. .PARAMETER LogFileName The path to the log file. If LogFile.Name is $Null, empty or blank then messages will not be written to a log file. They will still be written to either the PowerShell host or PowerShell streams, depending on the value of configuration setting WriteToHost. If LogFile.Name is specified without a path, or with a relative path, it will be relative to the directory of the calling script, not the Pslogg module. The default value for LogFile.Name is 'Results.log'. .PARAMETER IncludeDateInFileName A switch parameter that, if set, will include a date in the log file name. The date will take the form '_yyyyMMdd' appended to the end of the file name. For example, 'Results_20171129.log'. IncludeDateInFileName and ExcludeDateFromFileName cannot both be set at the same time. .PARAMETER ExcludeDateFromFileName A switch parameter that is the opposite of IncludeDateInFileName. If set it will exclude the date from the log file name. For example, 'Results.log'. IncludeDateInFileName and ExcludeDateFromFileName cannot both be set at the same time. .PARAMETER OverwriteLogFile A switch parameter that, if set, will overwrite any existing log file with the same name as LogFile.Name, including the date if LogFile.IncludeDateInFileName is set. The log file will only be overwritten by the first message logged in a given session. Subsequent messages written in the same session will be appended to the end of the log file. OverwriteLogFile and AppendToLogFile cannot both be set at the same time. .PARAMETER AppendToLogFile A switch parameter that is the opposite of OverwriteLogFile. If set new log messages will be appended to the end of an existing log file, if it has the same name as LogFile.Name, including a date if LogFile.IncludeDateInFileName is set. OverwriteLogFile and AppendToLogFile cannot both be set at the same time. .PARAMETER WriteToHost A switch parameter that, if set, will direct all output to the host, as opposed to one of the PowerShell streams such as Error or Warning. If the LogFileName parameter is set the output will also be written to a log file. WriteToHost and WriteToStreams cannot both be set at the same time. .PARAMETER WriteToStreams A switch parameter that complements WriteToHost. If set all output will be directed to PowerShell streams, such as Error or Warning, rather than the host. If the LogFile.Name parameter is set the output will also be written to a log file. WriteToHost and WriteToStreams cannot both be set at the same time. .PARAMETER MessageFormat A string that sets the format of log messages. Text enclosed in curly braces, {...}, represents the name of a field which will be included in the logged message. 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 message exactly as specified. Leading spaces in the MessageFormat string will be retained when the message is written to the logs to allow log messages to be indented. Trailing spaces in the MessageFormat string will not be included in the logged messages. 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, following the field name and separated from it by a colon, ":". 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 sensitive 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 default datetime format string is "yyyy-MM-dd HH:mm:ss.fff". {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 Category of the message. It will always be displayed in upper case. {MessageLevel} : The Log 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 CategoryInfoItem Sets one or more items in the CategoryInfo hashtable. CategoryInfoItem can take two different arguments: 1) A hashtable, of the form: @{ <category_name_1> = @{ <property1>=<value1>; <property2>=<value2>; ...n } <category_name_2> = @{ <property1>=<value1>; <property2>=<value2>; ...n } ... } If a category name already exists in the CategoryInfo hashtable its properties will be updated. If the category name does not exist it will be created. Currently two properties are supported for each CategoryInfoItem: IsDefault: Indicates the category that will be used as the default, if no -Category is specified in Write-LogMesssage; Color: The text color for messages of the specified category, when they are written to the host. 2) A two-element array, of the form: <category_name>, @{ <property1>=<value1>; <property2>=<value2>; ...n } This sets a single category. If the category name already exists its properties will be updated. If the category name does not already exist it will be created. Only one category can have the IsDefault property set. If one of the supplied categories has IsDefault set then the IsDefault property will be removed from all existing categories. If multiple supplied categories have IsDefault set then only the last one processed will end up with IsDefault set. The last category processed will depend on the sort order of the CategoryInfo hashtable Keys collection. .PARAMETER CategoryInfoKeyToRemove Removes one or more items from the CategoryInfo hashtable. CategoryInfoKeyToRemove can take two different arguments: 1) An array of category names (CategoryInfo hashtable keys); 2) A single category name. .PARAMETER HostTextColorConfiguration A hashtable specifying the different text colors that will be used for different log levels, for log messages written to the host. The hashtable must have the following keys: Verbose: The text color for messages of log level Verbose. The default value is 'White'; Debug: The text color for messages of log level Debug. The default value is 'White'; Information: The text color for messages of log level Information. The default value is 'White'; Warning: The text color for messages of log level Warning. The default value is 'Yellow'`; Error: The text color for messages of log level Error. The default value is 'Red'. Possible values for text colors are: 'Black', 'DarkBlue', 'DarkGreen', 'DarkCyan', 'DarkRed', 'DarkMagenta', 'DarkYellow', 'Gray', 'DarkGray', 'Blue', 'Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'White'. These colors are only used if WriteToHost is set. If WriteToStreams is set these colors are ignored. .PARAMETER VerboseTextColor The text color for messages written to the host at message level Verbose. This is only used if WriteToHost is set. If WriteToStreams is set this color is ignored. Acceptable values are: 'Black', 'DarkBlue', 'DarkGreen', 'DarkCyan', 'DarkRed', 'DarkMagenta', 'DarkYellow', 'Gray', 'DarkGray', 'Blue', 'Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'White'. .PARAMETER DebugTextColor The text color for messages written to the host at message level Debug. This is only used if WriteToHost is set. If WriteToStreams is set this color is ignored. Acceptable values are as per VerboseTextColor. .PARAMETER InformationTextColor The text color for messages written to the host at message level Information. This is only used if WriteToHost is set. If WriteToStreams is set this color is ignored. Acceptable values are as per VerboseTextColor. .PARAMETER WarningTextColor The text color for messages written to the host at message level Warning. This is only used if WriteToHost is set. If WriteToStreams is set this color is ignored. Acceptable values are as per VerboseTextColor. .PARAMETER ErrorTextColor The text color for messages written to the host at message level Error. This is only used if WriteToHost is set. If WriteToStreams is set this color is ignored. Acceptable values are as per VerboseTextColor. .EXAMPLE Use parameter -LogConfiguration to update the entire configuration at once: $hostTextColor = @{ Error = 'DarkRed' Warning = 'DarkYellow' Information = 'DarkCyan' Debug = 'Gray' Verbose = 'White' } $logConfiguration = @{ LogLevel = 'DEBUG' MessageFormat = '{CallerName} | {Category} | {Message}' WriteToHost = $True HostTextColor = $hostTextColor LogFile = @{ WriteFromScript = $False WriteFromHost = $True Name = 'Debug.log' IncludeDateInFileName = $False Overwrite = $False } CategoryInfo = @{} } Set-LogConfiguration -LogConfiguration $logConfiguration .EXAMPLE Use Get-LogConfiguration and Set-LogConfiguration to update the configuration: $config = Get-LogConfiguration $config.LogLevel = 'ERROR' $config.LogFile.Name = 'Error.log' $config.CategoryInfo['FileCopy'] = @{Color = 'DarkYellow'} Set-LogConfiguration -LogConfiguration $config .EXAMPLE Set the details of the log file using individual parameters: Set-LogConfiguration -LogFileName 'Debug.log' -ExcludeDateFromFileName ` -AppendToLogFile -EnableFileLoggingFromHost .EXAMPLE Set the LogLevel and MessageFormat using individual parameters: Set-LogConfiguration -LogLevel Warning ` -MessageFormat '{Timestamp:yyyy-MM-dd HH:mm:ss},{Category},{Message}' .EXAMPLE Add a single CategoryInfo item using the tuple (two-element array) syntax: Set-LogConfiguration -CategoryInfoItem FileCopy, @{ Color = 'Blue' } .EXAMPLE Change the default Category using the tuple (two-element array) syntax: Set-LogConfiguration -CategoryInfoItem FileCopy, @{ IsDefault = $True } .EXAMPLE Add multiple CategoryInfo items using the hashtable syntax: Set-LogConfiguration -CategoryInfoItem @{ FileCopy = @{ Color = 'Blue' } FileAdd = @{ Color = 'Yellow' } } If the configuration CategoryInfo hashtable already includes keys 'FileCopy' and 'FileAdd' the colors of those CategoryInfo items will be updated. If the keys do not already exist they will be created. .EXAMPLE Remove a single CategoryInfo item: Set-LogConfiguration -CategoryInfoKeyToRemove PartialFailure The CategoryInfo item with key 'PartialFailure' will be removed, if it exists. No error is thrown if the key does not exist. .EXAMPLE Remove multiple CategoryInfo items: Set-LogConfiguration -CategoryInfoKeyToRemove Progress,PartialFailure The CategoryInfo items with keys 'Progress' and 'PartialFailure' will be removed, if they exist. .EXAMPLE Set the text colors used by the host to display error and warning messages: Set-LogConfiguration -ErrorTextColor DarkRed -WarningTextColor DarkYellow .EXAMPLE Set all text colors simultaneously: $hostColors = @{ Error = 'DarkRed' Warning = 'DarkYellow' Information = 'DarkCyan' Debug = 'Cyan' Verbose = 'Gray' } Set-LogConfiguration -HostTextColorConfiguration $hostColors .LINK Write-LogMessage .LINK Get-LogConfiguration .LINK Reset-LogConfiguration #> function Set-LogConfiguration { # CmdletBinding attribute must be on first non-comment line of the function # and requires that the parameters be defined via the Param keyword rather # than in parentheses outside the function body. [CmdletBinding(DefaultParameterSetName="IndividualSettings_IndividualColors")] Param ( [parameter(Mandatory=$True, ParameterSetName="AllSettings")] [ValidateNotNull()] [Hashtable]$LogConfiguration, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [ValidateScript({ Private_ValidateLogLevel -LevelToTest $_ })] [string]$LogLevel, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [switch]$EnableFileLoggingFromScript, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [switch]$DisableFileLoggingFromScript, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [switch]$EnableFileLoggingFromHost, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [switch]$DisableFileLoggingFromHost, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [string]$LogFileName, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [switch]$IncludeDateInFileName, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [switch]$ExcludeDateFromFileName, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [switch]$OverwriteLogFile, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [switch]$AppendToLogFile, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [switch]$WriteToHost, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [switch]$WriteToStreams, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [string]$MessageFormat, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] $CategoryInfoItem, [parameter(ParameterSetName="IndividualSettings_AllColors")] [parameter(ParameterSetName="IndividualSettings_IndividualColors")] $CategoryInfoKeyToRemove, [parameter(ParameterSetName="IndividualSettings_AllColors")] [ValidateNotNull()] [Hashtable]$HostTextColorConfiguration, [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [ValidateScript({Private_ValidateHostColor $_})] [string]$VerboseTextColor, [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [ValidateScript({Private_ValidateHostColor $_})] [string]$DebugTextColor, [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [ValidateScript({Private_ValidateHostColor $_})] [string]$InformationTextColor, [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [ValidateScript({Private_ValidateHostColor $_})] [string]$WarningTextColor, [parameter(ParameterSetName="IndividualSettings_IndividualColors")] [ValidateScript({Private_ValidateHostColor $_})] [string]$ErrorTextColor ) # Will be $Null if LogFile.FullPathReadOnly does not exist. $oldLogFilePath = $script:_logConfiguration.LogFile.FullPathReadOnly if ($LogConfiguration -ne $Null) { $script:_logConfiguration = Private_DeepCopyHashTable $LogConfiguration Private_SetMessageFormat $LogConfiguration.MessageFormat Private_SetLogFilePath -OldLogFilePath $oldLogFilePath return } # Ensure that mutually exclusive pairs of switch parameters are not both set: Private_ValidateSwitchParameterGroup -SwitchList $EnableFileLoggingFromScript,$DisableFileLoggingFromScript ` -ErrorMessage "Only one FileWriteFromScript switch parameter may be set when calling the function. FileWriteFromScript switch parameters: -EnableFileLoggingFromScript, -DisableFileLoggingFromScript" Private_ValidateSwitchParameterGroup -SwitchList $EnableFileLoggingFromHost,$DisableFileLoggingFromHost ` -ErrorMessage "Only one FileWriteFromHost switch parameter may be set when calling the function. FileWriteFromHost switch parameters: -EnableFileLoggingFromHost, -DisableFileLoggingFromHost" Private_ValidateSwitchParameterGroup -SwitchList $IncludeDateInFileName,$ExcludeDateFromFileName ` -ErrorMessage "Only one FileName switch parameter may be set when calling the function. FileName switch parameters: -IncludeDateInFileName, -ExcludeDateFromFileName" Private_ValidateSwitchParameterGroup -SwitchList $OverwriteLogFile,$AppendToLogFile ` -ErrorMessage "Only one LogFileWriteBehavior switch parameter may be set when calling the function. LogFileWriteBehavior switch parameters: -OverwriteLogFile, -AppendToLogFile" Private_ValidateSwitchParameterGroup -SwitchList $WriteToHost,$WriteToStreams ` -ErrorMessage "Only one Destination switch parameter may be set when calling the function. Destination switch parameters: -WriteToHost, -WriteToStreams" if (![string]::IsNullOrWhiteSpace($LogLevel)) { $script:_logConfiguration.LogLevel = $LogLevel } if ($EnableFileLoggingFromScript.IsPresent) { $script:_logConfiguration.LogFile.WriteFromScript = $True } if ($DisableFileLoggingFromScript.IsPresent) { $script:_logConfiguration.LogFile.WriteFromScript = $False } if ($EnableFileLoggingFromHost.IsPresent) { $script:_logConfiguration.LogFile.WriteFromHost = $True } if ($DisableFileLoggingFromHost.IsPresent) { $script:_logConfiguration.LogFile.WriteFromHost = $False } if (![string]::IsNullOrWhiteSpace($LogFileName)) { $script:_logConfiguration.LogFile.Name = $LogFileName Private_SetLogFilePath -OldLogFilePath $oldLogFilePath } if ($ExcludeDateFromFileName.IsPresent) { $script:_logConfiguration.LogFile.IncludeDateInFileName = $False Private_SetLogFilePath -OldLogFilePath $oldLogFilePath } if ($IncludeDateInFileName.IsPresent) { $script:_logConfiguration.LogFile.IncludeDateInFileName = $True Private_SetLogFilePath -OldLogFilePath $oldLogFilePath } if ($AppendToLogFile.IsPresent) { $script:_logConfiguration.LogFile.Overwrite = $False } if ($OverwriteLogFile.IsPresent) { $script:_logConfiguration.LogFile.Overwrite = $True } if ($WriteToStreams.IsPresent) { $script:_logConfiguration.WriteToHost = $False } if ($WriteToHost.IsPresent) { $script:_logConfiguration.WriteToHost = $True } if (![string]::IsNullOrWhiteSpace($MessageFormat)) { Private_SetMessageFormat $MessageFormat } if ($CategoryInfoItem) { if (-not $script:_logConfiguration.ContainsKey('CategoryInfo') ` -or (-not $script:_logConfiguration.CategoryInfo)) { $script:_logConfiguration.CategoryInfo = @{} } if ($CategoryInfoItem -is [array]) { $key = $CategoryInfoItem[0] $value = $CategoryInfoItem[1] Private_SetCategoryInfoItem ` -CategoryInfoHashtable $script:_logConfiguration.CategoryInfo ` -Key $key -Value $value } elseif ($CategoryInfoItem -is [hashtable]) { foreach($key in $CategoryInfoItem.Keys) { Private_SetCategoryInfoItem ` -CategoryInfoHashtable $script:_logConfiguration.CategoryInfo ` -Key $key -Value $CategoryInfoItem[$key] } } } if ($CategoryInfoKeyToRemove -and $script:_logConfiguration.CategoryInfo) { foreach($key in $CategoryInfoKeyToRemove) { $script:_logConfiguration.CategoryInfo.Remove($key) } } if ($HostTextColorConfiguration -ne $Null) { $script:_logConfiguration.HostTextColor = $HostTextColorConfiguration } if (![string]::IsNullOrWhiteSpace($ErrorTextColor)) { Private_SetConfigTextColor -ConfigurationKey "Error" -ColorName $ErrorTextColor } if (![string]::IsNullOrWhiteSpace($WarningTextColor)) { Private_SetConfigTextColor -ConfigurationKey "Warning" -ColorName $WarningTextColor } if (![string]::IsNullOrWhiteSpace($InformationTextColor)) { Private_SetConfigTextColor -ConfigurationKey "Information" -ColorName $InformationTextColor } if (![string]::IsNullOrWhiteSpace($DebugTextColor)) { Private_SetConfigTextColor -ConfigurationKey "Debug" -ColorName $DebugTextColor } if (![string]::IsNullOrWhiteSpace($VerboseTextColor)) { Private_SetConfigTextColor -ConfigurationKey "Verbose" -ColorName $VerboseTextColor } } <# .SYNOPSIS Resets the log configuration to the default settings. .DESCRIPTION Resets the log configuration to the default settings. .LINK Write-LogMessage .LINK Get-LogConfiguration .LINK Set-LogConfiguration #> function Reset-LogConfiguration() { Set-LogConfiguration -LogConfiguration $script:_defaultLogConfiguration } <# .SYNOPSIS Gets the directory name of the top-most script or module calling into this module. .DESCRIPTION This function attempts to get the directory name of the top-most script or module calling into this module. It ignores any stack frames where the directory is the same as this module: Those are assumed to represent functions in this module. It will continue walking the call stack until it reaches the end, or it reaches a stack frame without a script name. A stack frame without a script name represents the PowerShell console session, presumably the session of the user invoking the script that is calling into the Pslogg module. If a stack frame without a script name is reached the directory from the previous stack frame, which does have a script name, is returned. If the only directory found is the same as the directory of this Pslogg module it is assumed the user is invoking a Pslogg module function directly from the PowerShell console, rather than from a script or module. In that case the function will return the current working directory of the PowerShell console. If the call stack cannot be read the function returns $Null. .NOTES This function is NOT intended to be exported from this module. This is an expensive function. However, it will only be called while setting the logging configuration which shouldn't happen often. WARNING: This function may return unexpected results if a user is calling a script indirectly. For example, if a user is running a Pester test on a module that uses the Pslogg module, this function will return the directory of the Pester module, not the directory of the test script it is running, or the directory of the module under test. That is because the user is invoking Pester, not running the test script or the module under test directly. This function will see the Pester module as the top-most script or module being executed. #> function Private_GetCallerDirectory() { $callStack = Get-PSCallStack if ($null -eq $callStack -or $callStack.Count -eq 0) { return $Null } # Stack frame 0 is this function. Increasing the index takes us further up the call stack, # further away from this function. $thisFunctionStackFrame = $callStack[0] $thisModuleFileName = $thisFunctionStackFrame.ScriptName $thisModuleDirectory = $null if (-not [string]::IsNullOrWhiteSpace($thisModuleFileName)) { $thisModuleDirectory = Split-Path -Path $thisModuleFileName -Parent } $frameCount = $callStack.Count # Start from the 2nd stack frame as we've already read the details of the 1st stack frame. $i = 1 $stackFrameFileName = $Null $stackFrameDirectory = $Null # We want to walk up the call stack out of the module directory and stop at the top-most # script, the script that presumably the user is calling. # We don't want to stop at the first stack frame outside the module directory because that may # be a low-level script or function the user knows nothing about. # We don't want to automatically keep going to the top-most frame in the call stack because, # if the script is being called manually, the top-most frame will represent the PowerShell # console the user is running the script from. We want to save any log file along side the # script being run, if we can. # We can tell stack frames representing scripts from those representing the PowerShell console # via the ScriptName. A stack frame representing a call from a script file (whether from the # root of the file or from a function) will have a non-null ScriptName. A stack frame # representing a call from the console will have ScriptName equal to $Null. while (($i -eq 1 -or $stackFrameFileName) -and $i -lt $frameCount) { $stackFrame = $callStack[$i] $stackFrameFileName = $stackFrame.ScriptName if ($stackFrameFileName) { $stackFrameDirectory = Split-Path -Path $stackFrameFileName -Parent } $i++ # ASSUMPTION: All frames will have a ScriptName until we get to the PowerShell console, at # the top of the call stack. } $callerDirectory = $stackFrameDirectory if (-not $callerDirectory -or $callerDirectory -eq $thisModuleDirectory) { # No calling script outside of this module directory. So assume this module is getting # called directly from the PowerShell console, not from a script. In that case set the # caller directory to the current working directory. $callerDirectory = (Get-Location).Path } return $callerDirectory } <# .SYNOPSIS Gets the absolute path of the specified path. .DESCRIPTION Determines whether the path supplied is an absolute or a relative path. If it is absolute it is returned unaltered. If it is relative then the path to the directory the calling script is running in will be prepended to the specified path. .NOTES This function is NOT intended to be exported from this module. #> function Private_GetAbsolutePath ( [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()] [string]$Path ) { if (-not (Test-Path $Path -IsValid)) { throw [ArgumentException] "Invalid file path: '$Path'" } if ([System.IO.Path]::IsPathRooted($Path)) { return $Path } $callingDirectoryPath = Private_GetCallerDirectory $Path = Join-Path $callingDirectoryPath $Path return $Path } <# .SYNOPSIS Sets the full path to the log file. .DESCRIPTION Sets configuration setting LogFile.FullPathReadOnly. If LogFile.FullPathReadOnly is changed from the previous value then $_logFileOverwritten will be cleared. The function checks whether the LogFile.Name specified in the configuration settings is an absolute or a relative path. If it is relative then the path to the directory the calling script is running in will be prepended to the specified LogFile.Name when setting LogFile.FullPathReadOnly. If configuration setting LogFile.IncludeDateInFileName is $True then the date will be included in the LogFile.FullPathReadOnly file name, in the form: "<log file name>_yyyyMMdd.<file extension>". For example, "Results_20171129.log". .NOTES This function is NOT intended to be exported from this module. #> function Private_SetLogFilePath ([string]$OldLogFilePath) { if ([string]::IsNullOrWhiteSpace($script:_logConfiguration.LogFile.Name)) { $script:_logConfiguration.LogFile.FullPathReadOnly = '' return } $logFilePath = Private_GetAbsolutePath $script:_logConfiguration.LogFile.Name if ($script:_logConfiguration.LogFile.IncludeDateInFileName) { $directory = [System.IO.Path]::GetDirectoryName($logFilePath) $fileName = [System.IO.Path]::GetFileNameWithoutExtension($logFilePath) $fileName += (Get-Date -Format "_yyyyMMdd") # Will include the leading ".": $fileExtension = [System.IO.Path]::GetExtension($logFilePath) $logFilePath = [System.IO.Path]::Combine($directory, $fileName + $fileExtension) } $script:_logConfiguration.LogFile.FullPathReadOnly = $logFilePath if ($script:_logConfiguration.LogFile.FullPathReadOnly -ne $OldLogFilePath) { $script:_logFileOverwritten = $False } } <# .SYNOPSIS Returns a deep copy of a hashtable. .DESCRIPTION Returns a deep copy of a hashtable. .NOTES Assumes the hashtable values are either value types or nested hashtables. This function will not deal properly with values that are reference types; it will make shallow copies of them. This function is required because the Clone method will only perform a shallow copy of a hashtable. This would not be a problem if all values of the hashtable were value types but that is not the case: HostTextColor is a nested hashtable. This function is NOT intended to be exported from this module. #> function Private_DeepCopyHashTable([Collections.Hashtable]$HashTable) { if ($HashTable -eq $Null) { return $Null } if ($HashTable.Keys.Count -eq 0) { return @{} } $copy = @{} foreach($key in $HashTable.Keys) { if ($HashTable[$key] -is [Collections.Hashtable]) { $copy[$key] = (Private_DeepCopyHashTable $HashTable[$key]) } else { # Assumes the value of the hashtable element is a value type, not a reference type. # Works also if the value is an array of values types (ie does a deep copy of the # array). $copy[$key] = $HashTable[$key] } } return $copy } <# .SYNOPSIS Sets the message format in the log configuration settings. .DESCRIPTION Sets the message format in the log configuration settings. .PARAMETER MessageFormat: A string that sets the format of log messages. 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. {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. {Category} : The Message Category. If no Message Category is explicitly specified when calling Write-LogMessage the default Message Category from the logger configuration will be used. .NOTES This function is NOT intended to be exported from this module. #> function Private_SetMessageFormat([string]$MessageFormat) { $script:_logConfiguration["MessageFormat"] = $MessageFormat $script:_messageFormatInfo = Private_GetMessageFormatInfo $MessageFormat } <# .SYNOPSIS Sets one of the host text color values in the log configuration settings. .DESCRIPTIONs Sets one of the host text color values in the log configuration settings. .NOTES This function is NOT intended to be exported from this module. #> function Private_SetConfigTextColor([string]$ConfigurationKey, [string]$ColorName) { if (-not $script:_logConfiguration.ContainsKey("HostTextColor")) { $script:_logConfiguration.HostTextColor = $script:_defaultHostTextColor } $script:_logConfiguration.HostTextColor[$ConfigurationKey] = $ColorName } <# .SYNOPSIS Function called by ValidateScript to check if the value passed to parameter -CategoryInfoItem is valid. .DESCRIPTION Checks whether the parameter is either a hashtable or an array of two elements, the second of which is a hashtable. If the parameter meets these criteria this function returns $True. If the parameter doesn't meet the criteria the function throws an exception rather than returning $False. .NOTES Throwing an exception allows us to specify a custom error message. If the function simply returned $False PowerShell would generate a standard error message that would not indicate why the validation failed. #> function Private_ValidateCategoryInfoItem ( [Parameter(Mandatory=$True)] $CategoryInfoItem ) { if ($CategoryInfoItem -is [array]) { if ($CategoryInfoItem.Count -ne 2) { throw [ArgumentException]::new( ` "Expected an array of 2 elements but $($CategoryInfoItem.Count) supplied.", 'CategoryInfoItem') } $key = $CategoryInfoItem[0] $value = $CategoryInfoItem[1] if (-not ($key -is [string])) { throw [ArgumentException]::new( ` "Expected first element to be a string but it is $($key.GetType().FullName).", 'CategoryInfoItem') } if (-not ($value -is [hashtable])) { throw [ArgumentException]::new( ` "Expected second element to be a hashtable but it is $($value.GetType().FullName).", 'CategoryInfoItem') } return $True } if ($CategoryInfoItem -is [hashtable]) { foreach($key in $CategoryInfoItem.Keys) { if (-not ($key -is [string])) { throw [ArgumentException]::new( ` "Expected key to be a string but it is $($key.GetType().FullName).", 'CategoryInfoItem') } $value = $CategoryInfoItem[$key] if (-not ($value -is [hashtable])) { throw [ArgumentException]::new( ` "Expected value to be a hashtable but it is $($value.GetType().FullName).", 'CategoryInfoItem') } } return $True } throw [ArgumentException]::new( ` "Expected argument to be either a hashtable or an array but it is $($CategoryInfoItem.GetType().FullName).", 'CategoryInfoItem') } <# .SYNOPSIS Sets a configuration CategoryInfo item. .DESCRIPTION The item to set is specified via the -Key and -Value parameters. If the value hashtable contains an IsDefault key then any existing value hashtable with an IsDefault key will have the key removed. #> function Private_SetCategoryInfoItem ( [hashtable]$CategoryInfoHashtable, [string]$Key, [hashtable]$Value ) { if (-not $CategoryInfoHashtable) { $CategoryInfoHashtable = @{} } if ($Value.ContainsKey('IsDefault') -and $Value.IsDefault -eq $True) { foreach ($existingKey in $CategoryInfoHashtable.Keys) { $existingValue = $CategoryInfoHashtable[$existingKey] $existingValue.Remove('IsDefault') } } $CategoryInfoHashtable[$Key] = $Value } |