ShoutOut.psm1
# START: source\.bootstrap.ps1 function _buildBasicFileLogger { param( [string]$FilePath ) return { param($message) if (-not (Test-Path $FilePath -PathType Leaf)) { New-Item -Path $FilePath -ItemType File -Force -ErrorAction Stop | Out-Null } $message | Out-File $FilePath -Encoding utf8 -Append -Force }.GetNewClosure() } $script:_ShoutOutSettings = @{ DefaultMsgType="Info" LogFileRedirection=@{} MsgStyles=@{ Success = @{ ForegroundColor="Green" } Exception = @{ ForegroundColor="Red"; BackgroundColor="Black" } Error = @{ ForegroundColor="Red" } Warning = @{ ForegroundColor="Yellow"; BackgroundColor="Black" } Info = @{ ForegroundColor="Cyan" } Result = @{ ForegroundColor="White" } } LogContext=$true Disabled=$false } $defaultLogFilename = "{0}.{1}.{2:yyyyMMddHHmmss}.log" -f $env:COMPUTERNAME, $pid, [datetime]::Now $defaultLogFile = switch ((whoami).split('\')[0]) { System { "{0}\Logs\shoutOut\{1}" -f $env:windir, $defaultLogFilename } default { "{0}\shoutOut\Logs\{1}" -f $env:APPDATA, $defaultLogFilename } } $script:_ShoutOutSettings.DefaultLog = _buildBasicFileLogger $defaultLogFile # END: source\.bootstrap.ps1 # START: source\_ensureShoutOutLogFile.ps1 function _ensureShoutOutLogFile { param( [string]$logFile, [string]$MsgType = "*" ) if (!(Test-Path $logFile -PathType Leaf)) { try { return new-Item $logFile -ItemType File -Force -ErrorAction Stop } catch { "Unable to create log file '{0}' for '{1}'." -f $logFile, $msgType | shoutOut -MsgType Error "Messages marked with '{0}' will be redirected." -f $msgType | shoutOut -MsgType Error shoutOut $_ Error throw ("Unable to use log file '{0}', the file cannot be created." -f $logFile) } } return gi $logFile } # END: source\_ensureShoutOutLogFile.ps1 # START: source\_ensureShoutOutLogHandler.ps1 function _ensureshoutOutLogHandler { param( [scriptblock]$logHandler, [string]$msgType = "*" ) $params = $logHandler.Ast.ParamBlock.Parameters if ($params.count -eq 0) { "Invalid handler, no parameters found: {0}" -f $logHandler | shoutOut -MsgType Error "Messages marked with '{0}' will not be redirected using this handler." -f $msgType | shoutOut -MsgType Error throw "No parameters declared by the given handler." } $paramName = '$message' $param = $params | ? { $_.Name.Extent.Text -eq $paramName } if (!$param) { "Invalid handler, no '{0}' parameter found" -f $paramName | shoutOut -MsgType Error "Messages marked with '{0}' will not be redirected using this handler." -f $msgType | shoutOut -MsgType Error throw ("No '{0}' parameter declared by the given handler." -f $paramName) } if (($t = $param.StaticType) -and !($t.IsAssignableFrom([String])) ) { "Invalid handler, the '{0}' parameter should accept values of type [String]." -f $paramName | shoutOut -MsgType Error "Messages marked with '{0}' will not be redirected using this handler." -f $msgType | shoutOut -MsgType Error throw ("'{0}' parameter on the given handler is of invalid type (not assignable from [string])." -f $paramName) } return $logHandler } # END: source\_ensureShoutOutLogHandler.ps1 # START: source\Clear-ShoutOutRedirect.ps1 function Clear-ShoutOutRedirect { param( [Parameter(HelpMessage="Mesage Type to remove redirection of.")] [string]$msgType ) $_ShoutOutSettings.LogFileRedirection.Remove($msgType) } # END: source\Clear-ShoutOutRedirect.ps1 # START: source\Get-ShoutOutConfig.ps1 function Get-ShoutOutConfig { return $_ShoutOutSettings } # END: source\Get-ShoutOutConfig.ps1 # START: source\Get-ShoutOutDefaultLog.ps1 function Get-ShoutOutDefaultLog { param() return $script:_ShoutOutSettings.DefaultLog } # END: source\Get-ShoutOutDefaultLog.ps1 # START: source\Get-ShoutOutRedirect.ps1 function Get-ShoutOutRedirect { param( [Parameter(HelpMessage="Message Type to retrieve redirection information for.")] [string]$msgType ) return $script:_ShoutOutSettings.LogFileRedirection[$msgType] } # END: source\Get-ShoutOutRedirect.ps1 # START: source\Set-ShoutOutConfig.ps1 function Set-ShoutOutConfig { param( [Parameter(HelpMessage="The default Message Type that ShoutOut should apply to messages.")] [string]$DefaultMsgType, [Parameter(HelpMessage="Default log handler to use for Messages Types without redirection.")] [Alias("LogFile")] $Log, [Parameter(HelpMessage="Enable/Disable Context logging.")] [Alias("LogContext")] [boolean]$EnableContextLogging, [Parameter(HelpMessage="Disable/Enable ShoutOut.")] [Alias("Disabled")] [boolean]$DisableLogging ) if ($PSBoundParameters.ContainsKey("DefaultMsgType")) { $_shoutOutSettings.DefaultMsgType = $DefaultMsgType } if ($PSBoundParameters.ContainsKey("Log")) { Set-ShoutOutDefaultLog $Log | Out-Null } if ($PSBoundParameters.ContainsKey("LogContext")) { $_shoutOutSettings.LogContext = $LogContext } if ($PSBoundParameters.ContainsKey("Disabled")) { $_shoutOutSettings.Disabled = $Disabled } } # END: source\Set-ShoutOutConfig.ps1 # START: source\Set-ShoutOutDefaultLog.ps1 function Set-ShoutOutDefaultLog { param( [parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1, ParameterSetName="LogFilePath")][String]$LogFilePath, [parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1, ParameterSetName="LogFile")][System.IO.FileInfo]$LogFile, [parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1, ParameterSetName="LogHandler")][scriptblock]$LogHandler ) switch ($PSCmdlet.ParameterSetName) { "LogFilePath" { try { _ensureShoutOutLogFile $LogFilePath -ErrorAction Stop | Out-Null $LogHandler = _buildBasicFileLogger $LogFilePath } catch { return $_ } } "LogFile" { try { _ensureShoutOutLogFile $LogFile.FullName -ErrorAction Stop | Out-Null $LogHandler = _buildBasicFileLogger $LogFile.FullName } catch { return $_ } } } try { $_shoutOutSettings.DefaultLog = _ensureshoutOutLogHandler $LogHandler -ErrorAction Stop } catch { return $_ } } # END: source\Set-ShoutOutDefaultLog.ps1 # START: source\Set-ShoutOutRedirect.ps1 function Set-ShoutOutRedirect { [CmdletBinding()] param( [parameter(Mandatory=$true, Position=1, HelpMessage="Message type to redirect.")][string]$MsgType, [Parameter(ParameterSetName="FilePath", ValueFromPipeline=$true, Mandatory=$true, Position=2, HelpMessage="Path to log file.")][string]$LogFilePath, [Parameter(ParameterSetName="FileInfo", ValueFromPipeline=$true, Mandatory=$true, Position=2, HelpMessage="FileInfo object.")][System.IO.FileInfo]$LogFile, [Parameter(ParameterSetName="Scriptblock", ValueFromPipeline=$true, Mandatory=$true, Position=2, HelpMessage="ScriptBlock to use as log handler.")][scriptblock]$LogHandler ) switch ($PSCmdlet.ParameterSetName) { "FileInfo" { try { _ensureShoutOutLogFile $LogFile.FullName $msgType | Out-Null $LogHandler = _buildBasicFileLogger $LogFile.FullName } catch { return $_ } $log = $LogFile.FullName } "FilePath" { try { _ensureShoutOutLogFile $LogFilePath $msgType | Out-Null $LogHandler = _buildBasicFileLogger $LogFilePath } catch { return $_ } } } try { $_ShoutOutSettings.LogFileRedirection[$msgType] = _ensureshoutOutLogHandler $LogHandler $MsgType } catch { return $_ } } # END: source\Set-ShoutOutRedirect.ps1 # START: source\ShoutOut.ps1 # ShoutOut.ps1 # First-things first: Logging function (is the realest, push the message and let the harddrive feel it.) <# .SYNOPSIS Pushes a message of the given $MsgType to the given $Log file with attached invocation metadata. .DESCRIPTION Logging function, used to push a message to a corresponding log-file. The message is prepended with meta data about the invocation to shoutOut as: <MessageType>|<Computer name>|<PID>|<calling Context>|<Date & time>|$Message The default values for the parameters can be set using the Set-ShoutOutConfig, Set-ShoutOutDefaultLog, and Set-ShotOutRedirect functions. #> function shoutOut { param( [parameter(Mandatory=$false, position=1, ValueFromPipeline=$true)] [Object]$Message, [Alias("ForegroundColor")] [parameter(Mandatory=$false, position=2)][String]$MsgType=$null, [parameter(Mandatory=$false, position=3)]$Log=$null, [parameter(Mandatory=$false, position=4)][Int32]$ContextLevel=1, # The number of levels to proceed up the call # stack when reporting the calling script. [parameter(Mandatory=$false)] [bool] $LogContext = ( !$_ShoutOutSettings.ContainsKey("LogContext") -or ($_ShoutOutSettings.ContainsKey("LogContext") -and $_ShoutOutSettings.LogContext) ), [parameter(Mandatory=$false)] [Switch] $NoNewline, [parameter(Mandatory=$false)] [Switch] $Quiet ) process { $defaultLogHandler = { param($msg) $msg | Out-File $Log -Encoding utf8 -Append } $msgObjectType = if ($null -ne $Message) { $Message.GetType() } else { $null } $msgObjectTypeName = if ($null -ne $msgObjectType) { $msgObjectType.Name } else { "NULL" } if ( (-not $PSBoundParameters.ContainsKey("MsgType")) -or ($null -eq $PSBoundParameters["MsgType"]) ) { switch ($msgObjectTypeName) { "ErrorRecord" { $MsgType = "Error" } default { if ([System.Exception].IsAssignableFrom($msgObjectType)) { $MsgType = "Exception" } else { $MsgType = "Info" } } } } # Apply global settings. if ( ( $settingsV = Get-Variable "_ShoutOutSettings" ) -and ($settingsV.Value -is [hashtable]) ) { $settings = $settingsV.Value if ($settings.ContainsKey("Disabled") -and ($settings.Disabled)) { Write-Debug "Call to Shoutout, but Shoutout is disabled. Turn back on with 'Set-ShoutOutConfig -Disabled `$false'." return } if (!$MsgType -and $settings.containsKey("DefaultMsgType")) { $MsgType = $settings.DefaultMsgType } if (!$Log -and $settings.containsKey("DefaultLog")) { $Log = $settings.DefaultLog } if ($settings.LogFileRedirection.ContainsKey($MsgType)) { $Log = $settings.LogFileRedirection[$MsgType] } if ($settings.containsKey("MsgStyles") -and ($settings.MsgStyles -is [hashtable]) -and $settings.MsgStyles.containsKey($MsgType)) { $msgStyle = $settings.MsgStyles[$MsgType] } } # Hard-coded defaults just in case. if (!$Log) { $Log = ".\setup.log" } if (!$msgStyle) { if ($MsgType -in [enum]::GetNames([System.ConsoleColor])) { $msgStyle = @{ ForegroundColor=$MsgType } } else { $msgStyle = @{ ForegroundColor="White" } } } # Apply formatting to make output more readable. switch ($msgObjectTypeName) { "String" { # No transformation necessary. } "NULL" { # No Transformation necessary. } "ErrorRecord" { if ($null -ne $message.Exception) { shoutOut $message.Exception } if ($null -ne $message.InnerException) { shoutOut $message.InnerException } $m = $message $Message = $m.Exception, $m.CategoryInfo, $m.InvocationInfo, $m.ScriptStackTrace | Out-string | % Split "`n`r" | ? { $_ } $Message = $Message | Out-String | % TrimEnd "`n`r" } default { $message = $Message | Out-String | % TrimEnd "`n`r" } } # Print to console if necessary if ([Environment]::UserInteractive -and !$Quiet) { $p = @{ Object = $Message NoNewline = $NoNewline } if ($msgStyle.ForegroundColor) { $p.ForegroundColor = $msgStyle.ForegroundColor } if ($msgStyle.BAckgroundColor) { $p.BackgroundColor = $msgStyle.BackgroundColor } Write-Host @p } $parentContext = if ($LogContext) { $cs = Get-PSCallStack $csd = @($cs).Length # CallStack Depth, should always be greater than or equal to 2. 1 would indicate that we # are running the directly on the command line, but since we are inside the shoutOut # function there should always be at least one level to the callstack in addition to the # calling context. switch ($csd) { 2 { "[{0}]<commandline>" -f $csd } default { $parentCall = $cs[$ContextLevel] if ($parentCall.ScriptName) { "[{0}]{1}:{2}" -f $csd, $parentCall.ScriptName,$parentCall.ScriptLineNumber } else { for($i = $ContextLevel; $i -lt $cs.Length; $i++) { $level = $cs[$i] if ($level.ScriptName) { break; } } if ($level.ScriptName) { "[{0}]{1}:{2}\<scriptblock>" -f $csd, $level.ScriptName,$level.ScriptLineNumber } else { "[{0}]<commandline>\<scriptblock>" -f $csd } } } } } else { "[context logging disabled]" } $createRecord = { param($m) "{0}|{1}|{2}|{3}|{4}|{5}|{6}" -f $MsgType, $env:COMPUTERNAME, $pid, $parentContext, [datetime]::Now.toString('o'), $msgObjectTypeName, $m } $record = . $createRecord $Message if ($log -is [scriptblock]) { try { . $Log -Message $record } catch { $errorMsgRecord1 = . $createRecord ("An error occurred while trying to log a message to '{0}'" -f ( $Log | Out-String)) $errorMsgRecord2 = . $createRecord "The following is the record that would have been written:" $Log = "{0}\shoutOut.error.{1}.{2}.{3:yyyyMMddHHmmss}.log" -f $env:APPDATA, $env:COMPUTERNAME, $pid, [datetime]::Now $errorRecord = . $createRecord ($_ | Out-String) . $defaultLogHandler $errorMsgRecord1 . $defaultLogHandler $errorRecord . $defaultLogHandler $errorMsgRecord2 . $defaultLogHandler $record } } else { . $defaultLogHandler $record } } } # END: source\ShoutOut.ps1 |