Private/SharedFunctions.ps1
# Private functions shared by Configuration and Logging scripts. . $PSScriptRoot\ModuleState.ps1 . $PSScriptRoot\Constants.ps1 <# .SYNOPSIS Ensures at most one of the switch values passed as an argument is set. .DESCRIPTION Ensures at most one of the switch values passed as an argument is set. .NOTES This function is NOT intended to be exported from this module. #> function Private_ValidateSwitchParameterGroup ( [parameter(Mandatory=$True)] [ValidateNotNull()] [switch[]]$SwitchList, [parameter(Mandatory=$True)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [string]$ErrorMessage ) { # Can't use "if ($SwitchList.Count -gt 1)..." because it will always be true, even if no # switches are set when calling the parent function. If one of the switch parameters is not # set it will still be passed to this function but with value $False. # Could use ".Where{$_}" but ".Where{$_ -eq $True}" is easier to understand. if ($SwitchList.Where{$_ -eq $True}.Count -gt 1) { throw [ArgumentException] $ErrorMessage } } <# .SYNOPSIS Function called by ValidateScript to check if the specified host color name is valid when passed as a parameter. .DESCRIPTION If the specified color name is valid this function returns $True. If the specified color name is not valid the function throws an exception rather than returning $False. .NOTES Allows multiple parameters to be validated in a single place, so the validation code does not have to be repeated for each parameter. Throwing an exception when the color name is invalid allows us to specify a custom error message. If the function simply returned $False PowerShell would generate a standard error message that does not indicate why the validation failed. #> function Private_ValidateHostColor ( [Parameter(Mandatory=$True)] [string]$ColorToTest ) { $validColors = @('Black', 'DarkBlue', 'DarkGreen', 'DarkCyan', 'DarkRed', 'DarkMagenta', 'DarkYellow', 'Gray', 'DarkGray', 'Blue', 'Green', 'Cyan', 'Red', 'Magenta', 'Yellow', 'White') if ($validColors -notcontains $ColorToTest) { throw [ArgumentException] "INVALID TEXT COLOR ERROR: '$ColorToTest' is not a valid text color for the PowerShell host." } return $True } <# .SYNOPSIS Function called by ValidateScript to check if the specified log level is valid when passed as a parameter. .DESCRIPTION If the specified log level is valid this function returns $True. If the specified log level is not valid the function throws an exception rather than returning $False. .NOTES Allows multiple parameters to be validated in a single place, so the validation code does not have to be repeated for each parameter. Throwing an exception when the log level is invalid allows us to specify a custom error message. If the function simply returned $False PowerShell would generate a standard error message that does not indicate why the validation failed. #> function Private_ValidateLogLevel ( [Parameter(Mandatory=$True)] [string]$LevelToTest, [parameter(Mandatory=$False)] [switch]$ExcludeOffLevel ) { $validLevels = @('OFF', 'ERROR', 'WARNING', 'INFORMATION', 'DEBUG', 'VERBOSE') if ($ExcludeOffLevel.IsPresent) { $validLevels[0] = $Null } if ($validLevels -notcontains $LevelToTest) { throw [ArgumentException] "INVALID LOG LEVEL ERROR: '$LevelToTest' is not a valid log level." } return $True } <# .SYNOPSIS Gets a Timestamp format string from a specified message format string. .DESCRIPTION Parses a message format string to find a {Timestamp} field placeholder. If the Timestamp plceholder is found and it contains a datetime format string the datetime format string will be returned. If there is no Timestamp plceholder, or if the Timestamp placeholder has no datetime format string, then $Null will be returned. .NOTES This function is NOT intended to be exported from this module. #> function Private_GetTimestampFormat ([string]$MessageFormat) { # The regex can handle zero or more white spaces (spaces or tabs) between the curly braces # and the placeholder name. eg "{ Timestamp}", '{ Timestamp }". It can also handle # zero or more white spaces before or after the colon that separates the placeholder name # from the datetime format string. Note that (?: ... ) is a non-capturing group so $Matches # should contain at most two groups: # $Matches[0] : The overall match. Always present if the {Timestamp} placeholder is # present; # $Matches[1] : The datetime format string. Only present if the {Timestamp} # placeholder has a datetime format string. # Note the first colon in the regex pattern is part of the non-capturing group specifier. # The second colon in the regex pattern represents the separator between the placeholder name # and the datetime format string, eg {Timestamp:d} $regexPattern = "{\s*Timestamp\s*(?::\s*(.+?)\s*)?\s*}" # -imatch is a case insensitive regex match. # No need to compile the regex as it won't be used often. $isMatch = $MessageFormat -imatch $regexPattern if ($isMatch -and $Matches.Count -ge 2) { return $Matches[1].Trim() } return $Null } <# .SYNOPSIS Gets message format info from a message format string. .DESCRIPTION Parses a message format string to determine which fields will appear in the log message and what their format strings are, if applicable. The results are returned in a hash table. .OUTPUTS A hash table with the following keys: RawFormat: The format string passed into this function; WorkingFormat: A modified format string with the field placeholders replaced with variable names. The variable names that may be embedded in the WorkingFormat string are: $Message : Replaces field placeholder {Message}; $Timestamp : Unlike other fields, Timestamp must include a datetime format string. If no datetime format string is included in the Timestamp placeholder it will default to 'yyyy-MM-dd hh:mm:ss.fff'. The Timestamp placeholder will be replaced with "$($Timestamp.ToString('<datetime format string>'))". Examples: 1) Field placeholder '{Timestamp:d}' will be replaced by "$($Timestamp.ToString('d'))"; 2) Field placeholder '{Timestamp}' will use the default datetime format string so will be replaced by "$($Timestamp.ToString('yyyy-MM-dd hh:mm:ss.fff'))"; $CallerName : Replaces field placeholder {CallerName}; $CallerLineNumber : Replaces field placeholder {CallerLineNumber}; $MessageLevel : Replaces field placeholder {MessageLevel}; $Category : Replaces field placeholder {Category}; FieldsPresent: An array of strings representing the names of fields that will appear in the log message. Field names that may appear in the array are: "Message" : Included if the RawFormat string contains field placeholder {Message}; "Timestamp" : Included if the RawFormat string contains field placeholder {Timestamp}; "CallerName" : Included if the RawFormat string contains field placeholder {CallerName}; "CallerLineNumber" : Included if the RawFormat string contains field placeholder {CallerLineNumber}; "MessageLevel" : Included if the RawFormat string contains field placeholder {MessageLevel}; "Category" : Included if the RawFormat string contains field placeholder {Category}. .NOTES This function is NOT intended to be exported from this module. #> function Private_GetMessageFormatInfo([string]$MessageFormat) { $messageFormatInfo = @{ RawFormat = $MessageFormat WorkingFormat = "" FieldsPresent = @() } $workingFormat = $MessageFormat # -ireplace is a case insensitive find and replace. # The regex can handle zero or more white spaces (spaces or tabs) between the curly braces # and the placeholder name. eg "{ Messages}", '{ Messages }". # No need to compile the regex as it won't be used often. $modifiedText = $workingFormat -ireplace '{\s*Message\s*}', '${Message}' if ($modifiedText -ne $workingFormat) { $messageFormatInfo.FieldsPresent += "Message" $workingFormat = $modifiedText } $timestampFormat = Private_GetTimestampFormat $workingFormat if (-not $timestampFormat) { $timestampFormat = $script:_defaultTimestampFormat } # Escape the first two "$" because we want to retain them in the replacement text. Do # not escape the "$" in "$timestampFormat" because we want to expand that variable. $replacementText = "`$(`$Timestamp.ToString('$timestampFormat'))" $modifiedText = $workingFormat -ireplace '{\s*Timestamp\s*(?::\s*.+?\s*)?\s*}', $replacementText if ($modifiedText -ne $workingFormat) { $messageFormatInfo.FieldsPresent += "Timestamp" $workingFormat = $modifiedText } $modifiedText = $workingFormat -ireplace '{\s*CallerName\s*}', '${CallerName}' if ($modifiedText -ne $workingFormat) { $messageFormatInfo.FieldsPresent += "CallerName" $workingFormat = $modifiedText } $modifiedText = $workingFormat -ireplace '{\s*MessageLevel\s*}', '${MessageLevel}' if ($modifiedText -ne $workingFormat) { $messageFormatInfo.FieldsPresent += "MessageLevel" $workingFormat = $modifiedText } $modifiedText = $workingFormat -ireplace '{\s*Category\s*}', '${Category}' if ($modifiedText -ne $workingFormat) { $messageFormatInfo.FieldsPresent += "Category" $workingFormat = $modifiedText } $modifiedText = $workingFormat -ireplace '{\s*CallerLineNumber\s*}', '${CallerLineNumber}' if ($modifiedText -ne $workingFormat) { $messageFormatInfo.FieldsPresent += "CallerLineNumber" $workingFormat = $modifiedText } $messageFormatInfo.WorkingFormat = $workingFormat return $messageFormatInfo } |