PSWriteLog.psm1

<#
.DESCRIPTION
    Write a Log Header similar to the one created by `Start-Transcript -IncludeInvocationHeader`:
 
        **********************
        Windows PowerShell transcript start
        Start time: 20230110191101
        Username: UTSARR\SYSTEM
        RunAs User: UTSARR\SYSTEM
        Configuration Name:
        Machine: RR711111IP01 (Microsoft Windows NT 10.0.19044.0)
        Host Application: C:\WINDOWS\system32\WindowsPowerShell\v1.0\PowerShell.exe -NoLogo -Noninteractive -NoProfile -ExecutionPolicy Bypass & 'C:\WINDOWS\CCM\SystemTemp\861996c9-1d3e-4522-9837-ff30577a8184.ps1' True
        Process ID: 40784
        PSVersion: 5.1.19041.1682
        PSEdition: Desktop
        PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.19041.1682
        BuildVersion: 10.0.19041.1682
        CLRVersion: 4.0.30319.42000
        WSManStackVersion: 3.0
        PSRemotingProtocolVersion: 2.3
        SerializationVersion: 1.1.0.1
        **********************
#>

function Get-InvocationHeader {
    [CmdletBinding()]
    param()

    $tmp = New-TemporaryFile
    Start-Transcript -LiteralPath $tmp.FullName -IncludeInvocationHeader -Force | Out-Null
    Stop-Transcript | Out-Null

    $inHeader = $false
    $header = Get-Content $tmp.FullName | ForEach-Object {
        if ($_.StartsWith('*') -and $inHeader) {
            # Reached end of header
            break
        } elseif ($_.StartsWith('*')) {
            # Reached start of header
            $inHeader = $true
        } else {
            # In header
            switch -regex ($_.Trim()) {
                '^Windows PowerShell transcript start' {
                    Write-Output ('PSWriteLog v{0} Invocation Header' -f (Get-Module 'PSWriteLog' | Select-Object -First 1).Version)
                    break
                }
                '^Start time\:\s+' {
                    Write-Output ('Start time: {0}' -f (Get-Date -Format 'O'))
                    break
                }
                default {
                    Write-Output $_
                }
            }
        }
    }

    $tmp.FullName | Remove-Item -ErrorAction 'SilentlyContinue' -Force
    $env:PSWriteLogIncludeInvocationHeader = $null

    return $header
}
<#
.SYNOPSIS
Write messages to a log file in CMTrace.exe compatible format or Legacy text file format.
.DESCRIPTION
Write messages to a log file in CMTrace.exe compatible format or Legacy text file format and optionally display in the console.
.PARAMETER Message
The message to write to the log file or output to the console.
.PARAMETER Severity
Defines message type. When writing to console or CMTrace.exe log format, it allows highlighting of message type.
Options: 1 = Information (default), 2 = Warning (highlighted in yellow), 3 = Error (highlighted in red)
.PARAMETER Source
The source of the message being logged.
.PARAMETER Component
The heading for the portion of the script that is being executed. Default is: $script:installPhase.
.PARAMETER LogType
Choose whether to write a CMTrace.exe compatible log file or a Legacy text log file.
.PARAMETER LogFileDirectory
Set the directory where the log file will be saved.
.PARAMETER LogFileName
Set the name of the log file.
.PARAMETER MaxLogFileSizeMB
Maximum file size limit for log file in megabytes (MB). Default is 10 MB.
Setting to 0 will disable log rotations.
.PARAMETER ContinueOnError
Suppress writing log message to console on failure to write message to log file.
.EXAMPLE
Write-Log -Message "Installing patch MS15-031" -Source 'Add-Patch' -LogType 'CMTrace'
.EXAMPLE
Write-Log -Message "Script is running on Windows 8" -Source 'Test-ValidOS' -LogType 'Legacy'
.NOTES
Taken from PSAppDeployToolkit:
https://github.com/PSAppDeployToolkit/PSAppDeployToolkit/blob/3.6.4/Toolkit/AppDeployToolkit/AppDeployToolkitMain.ps1#L480-L741
.LINK
https://github.com/VertigoRay/PSWriteLog
#>

function global:Write-Log {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true)]
        [AllowEmptyCollection()]
        [string[]]
        $Message,

        [Parameter(Mandatory = $false, Position = 1)]
        [string]
        $Severity = 'Info',

        [Parameter(Mandatory = $false, Position = 2)]
        [ValidateNotNull()]
        [string]
        $Source = (& { if ($script:MyInvocation.Value.ScriptName) {
            Split-Path -Path $script:MyInvocation.Value.ScriptName -Leaf
        } else {
            Split-Path -Path $script:MyInvocation.MyCommand.Definition -Leaf
        } }) + ':' + $MyInvocation.ScriptLineNumber,

        [Parameter(Mandatory = $false, Position = 3)]
        [alias('ScriptSection')]
        [ValidateNotNull()]
        [string]
        $Component = (& { $PSCallStack = (Get-PSCallStack)[1]; "$($PSCallStack.Command) $($PSCallStack.Arguments)" }),

        [Parameter(Mandatory = $false, Position = 4)]
        [ValidateSet('CMTrace', 'Legacy')]
        [string]
        $LogType = $(if ($env:PSWriteLogType) { $env:PSWriteLogType } else { 'CMTrace' }),

        [Parameter(Mandatory = $false, Position = 5)]
        [ValidateNotNullorEmpty()]
        [IO.FileInfo]
        $FilePath = $(if ($env:PSWriteLogFilePath) { $env:PSWriteLogFilePath } else { [IO.Path]::Combine($env:Temp, ('PowerShell {0} {1} {2}.log' -f $PSVersionTable.PSEdition, $PSVersionTable.PSVersion, $MyInvocation.CommandOrigin)) }),

        [Parameter(Mandatory = $false, Position = 6)]
        [ValidateNotNullorEmpty()]
        [decimal]
        $MaxLogFileSizeMB = $(if ($env:PSWriteLogMaxLogFileSizeMB) { $env:PSWriteLogMaxLogFileSizeMB -as [decimal] } else { 10 }),

        [Parameter(Mandatory = $false)]
        [bool]
        $ContinueOnError = $(if ($env:PSWriteLogContinueOnError) { $env:PSWriteLogContinueOnError -as [bool] } else { $false }),

        [Parameter(Mandatory = $false)]
        [bool]
        $DisableLogging = $(if ($env:PSWriteLogDisableLogging) { $env:PSWriteLogDisableLogging -as [bool] } else { $false }),

        [Parameter(Mandatory = $false)]
        [bool]
        $IncludeInvocationHeader = $(if ($env:PSWriteLogIncludeInvocationHeader) { $env:PSWriteLogIncludeInvocationHeader -as [bool] } else { $false })
    )

    begin {
        Microsoft.PowerShell.Utility\Write-Debug ('[Write-Log] BoundParameters: {0}' -f $($MyInvocation.BoundParameters | Out-String))

        if ($env:PSWriteLogDisableLogging) {
            # If logging is not currently disabled, get out now!
            Microsoft.PowerShell.Utility\Write-Debug ('[Write-Log] env:PSWriteLogDisableLogging: {0}' -f $env:PSWriteLogDisableLogging)
            return $null
        }

        # Get the name of this function
        [string] $CmdletName = $PSCmdlet.MyInvocation.MyCommand.Name

        [scriptblock] $logDate = {
            return (Get-Date -Format MM-dd-yyyy).ToString()
        }

        [scriptblock] $logTime = {
            [string] $script:logTimeZoneBias = [System.TimeZone]::CurrentTimeZone.GetUtcOffset([datetime]::Now).TotalMinutes
            return "$((Get-Date -Format HH:mm:ss.fff).ToString())${script:logTimeZoneBias}"
        }

        # Create script block for generating a Legacy log entry
        [scriptblock] $legacyLogString = {
            Param(
                [string]
                $lMessage
            )

            [System.Collections.ArrayList] $legacyMessage = @()

            $legacyMessage.Add(('[{0}]' -f (Get-Date -Format 'O'))) | Out-Null
            if ($Source) {
                $legacyMessage.Add("[${Source}]") | Out-Null
            }
            # $legacyMessage.Add("[${Component}]") | Out-Null
            $legacyMessage.Add("[${Severity}]") | Out-Null
            $legacyMessage.Add(($lMessage.Trim() | Out-String)) | Out-Null

            return ($legacyMessage -join ' ').Trim()
        }

        # Create script block for generating CMTrace.exe compatible log entry
        [scriptblock] $cmTraceLogString = {
            param(
                [string]
                $lMessage
            )
            Microsoft.PowerShell.Utility\Write-Debug "[Write-Log] Source (sb): ${Source}"
            $severityMap = @{ # Vaguely based on POSH stream numbers
                Debug       = 5
                Error       = 3
                Host        = 1
                Info        = 6
                Information = 6
                Output      = 4
                Progress    = 1
                Verbose     = 4
                Warning     = 2
            }

            return ('<![LOG[{0}: {1}]LOG]!><time="{2}" date="{3}" component="{4}" context="{5}" type="{6}" thread="{7}" file="{8}">' -f @(
                $Severity
                $lMessage.Trim()
                & $logTime
                & $logDate
                $Component
                [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
                $severityMap.$Severity
                [Threading.Thread]::CurrentThread.ManagedThreadId
                $Source
            ))
        }

        [scriptblock] $logLine = {
            param(
                [string]
                $sMsg
            )
            ## Choose which log type to write to file
            $line = if ($LogType -ieq 'CMTrace') {
                & $cmTraceLogString -lMessage ($sMsg | Out-String).Trim() -lSource $Source
            } else {
                & $legacyLogString -lMessage ($sMsg | Out-String).Trim() -lSource $Source
            }

            $line | Out-File -FilePath $FilePath.FullName -Append -NoClobber -Force -Encoding 'UTF8' -ErrorAction 'Stop'
        }

        # Create the directory where the log file will be saved
        if (-not $FilePath.Directory.Exists) {
            New-Item -Path $FilePath.DirectoryName -Type 'Directory' -Force -ErrorAction 'Stop' | Out-Null
        }
    }

    process {
        if ($IncludeInvocationHeader) {
            & $logLine -sMsg ("{1}`n{0}`n{1}" -f (Get-InvocationHeader),('#' * 40))
        }

        foreach ($msg in $Message) {
            Microsoft.PowerShell.Utility\Write-Debug ('[Write-Log] Source: {0}' -f $Source)
            try {
                & $logLine -sMsg $msg
            } catch {
                if (-not $ContinueOnError) {
                    throw ('[{0} {1}] [{2}] [{3}] :: Failed to write message [{4}] to the log file [{5}].{6}{7}' -f @(
                        & $logDate
                        & $logTime
                        $CmdletName
                        $Component
                        $Msg
                        $FilePath.FullName
                        "`n"
                        Resolve-Error | Out-String
                    ))
                }
            }
        }
    }

    end {
        # Archive log file if size is greater than $MaxLogFileSizeMB and $MaxLogFileSizeMB > 0
        if ($MaxLogFileSizeMB) {
            try {
                [decimal] $LogFileSizeMB = $FilePath.Length/1MB
                Microsoft.PowerShell.Utility\Write-Debug "[Write-Log] LogFileSizeMB: $LogFileSizeMB / $MaxLogFileSizeMB"
                if ($LogFileSizeMB -gt $MaxLogFileSizeMB) {
                    Microsoft.PowerShell.Utility\Write-Debug "[Write-Log] Log File Needs to be archived ..."
                    # Change the file extension to "lo_"
                    [string] $archivedOutLogFile = [IO.Path]::ChangeExtension($FilePath.FullName, 'lo_')

                    # Log message about archiving the log file
                    if ((Get-PSCallStack)[1].Command -ne 'Write-Log') {
                        # Prevent Write-Log from looping more than once.
                        & $logLine -sMsg "Maximum log file size [${MaxLogFileSizeMB} MB] reached. Rename log file to: ${archivedOutLogFile}"
                    }

                    # Archive existing log file from <filename>.log to <filename>.lo_. Overwrites any existing <filename>.lo_ file. This is the same method SCCM uses for log files.
                    Move-Item -Path $FilePath.FullName -Destination $archivedOutLogFile -Force -ErrorAction 'Stop'

                    # Start new log file and Log message about archiving the old log file
                    & $logLine -sMsg "Maximum log file size [${MaxLogFileSizeMB} MB] reached. Previous log file was renamed to: ${archivedOutLogFile}"
                } else {
                    Microsoft.PowerShell.Utility\Write-Debug "[Write-Log] Log File does not need to be archived."
                }
            } catch {
                # If renaming of file fails, script will continue writing to log file even if size goes over the max file size
                Microsoft.PowerShell.Utility\Write-Debug "[Write-Log] Archive Error: ${_}"
            }
        }
    }
}
<#
.SYNOPSIS
    Enumerate error record details.
.DESCRIPTION
    Enumerate an error record, or a collection of error record, properties. By default, the details for the last error will be enumerated.
.PARAMETER ErrorRecord
    The error record to resolve. The default error record is the latest one: $global:Error[0]. This parameter will also accept an array of error records.
.PARAMETER Property
    The list of properties to display from the error record. Use "*" to display all properties.
    Default list of error properties is: Message, FullyQualifiedErrorId, ScriptStackTrace, PositionMessage, InnerException
.PARAMETER GetErrorRecord
    Get error record details as represented by $_.
.PARAMETER GetErrorInvocation
    Get error record invocation information as represented by $_.InvocationInfo.
.PARAMETER GetErrorException
    Get error record exception details as represented by $_.Exception.
.PARAMETER GetErrorInnerException
    Get error record inner exception details as represented by $_.Exception.InnerException. Will retrieve all inner exceptions if there is more than one.
.EXAMPLE
    Resolve-Error
.EXAMPLE
    Resolve-Error -Property *
.EXAMPLE
    Resolve-Error -Property InnerException
.EXAMPLE
    Resolve-Error -GetErrorInvocation:$false
.NOTES
.LINK
    http://psappdeploytoolkit.com
#>

function Resolve-Error {
    [CmdletBinding()]
    param(
        [Parameter(
            Mandatory = $false,
            Position=0,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true
            )]
        [AllowEmptyCollection()]
        [array]
        $ErrorRecord,

        [Parameter(Mandatory = $false, Position = 1)]
        [ValidateNotNullorEmpty()]
        [string[]]
        $Property = ('Message', 'InnerException', 'FullyQualifiedErrorId', 'ScriptStackTrace', 'PositionMessage'),

        [Parameter(Mandatory = $false, Position = 2)]
        [switch]
        $SkipGetErrorRecord,

        [Parameter(Mandatory = $false, Position = 3)]
        [switch]
        $SkipGetErrorInvocation,

        [Parameter(Mandatory = $false, Position = 4)]
        [switch]
        $SkipGetErrorException,

        [Parameter(Mandatory = $false, Position = 5)]
        [switch]
        $SkipGetErrorInnerException
    )

    begin {
        ## If function was called without specifying an error record, then choose the latest error that occurred
        if (-not $ErrorRecord) {
            if ($global:Error.Count -eq 0) {
                # Microsoft.PowerShell.Utility\Write-Information -Message 'The $Error collection is empty' -Tags 'VertigoRay\PSWriteLog','Resolve-Error'
                return
            }
            Else {
                [array] $ErrorRecord = $global:Error[0]
            }
        }

        ## Allows selecting and filtering the properties on the error object if they exist
        [scriptblock] $selectProperty = {
            param(
                [Parameter(Mandatory = $true)]
                [ValidateNotNullorEmpty()]
                $InputObject,

                [Parameter(Mandatory = $true)]
                [ValidateNotNullorEmpty()]
                [string[]]
                $Property
            )

            [string[]] $objectProperty = $InputObject | Get-Member -MemberType '*Property' | Select-Object -ExpandProperty 'Name'
            foreach ($prop in $Property) {
                if ($prop -eq '*') {
                    [string[]] $propertySelection = $objectProperty
                    Break
                }
                Elseif ($objectProperty -contains $prop) {
                    [string[]] $propertySelection += $prop
                }
            }
            return $propertySelection
        }

        # Initialize variables to avoid error if 'Set-StrictMode' is set
        $logErrorRecordMsg = $null
        $logErrorInvocationMsg = $null
        $logErrorExceptionMsg = $null
        $logErrorMessageTmp = $null
        $logInnerMessage = $null
    }
    process {
        if (-not $ErrorRecord) { return }
        foreach ($errRecord in $ErrorRecord) {
            ## Capture Error Record
            if (-not $SkipErrorRecord) {
                [string[]] $selectedProperties = & $selectProperty -InputObject $errRecord -Property $Property
                $logErrorRecordMsg = $errRecord | Select-Object -Property $selectedProperties
            }

            ## Error Invocation Information
            if (-not $SkipGetErrorInvocation) {
                if ($errRecord.InvocationInfo) {
                    [string[]] $selectedProperties = & $selectProperty -InputObject $errRecord.InvocationInfo -Property $Property
                    $logErrorInvocationMsg = $errRecord.InvocationInfo | Select-Object -Property $selectedProperties
                }
            }

            ## Capture Error Exception
            if (-not $SkipGetErrorException) {
                if ($errRecord.Exception) {
                    [string[]] $selectedProperties = & $selectProperty -InputObject $errRecord.Exception -Property $Property
                    $logErrorExceptionMsg = $errRecord.Exception | Select-Object -Property $selectedProperties
                }
            }

            ## Display properties in the correct order
            if ($Property -eq '*') {
                # If all properties were chosen for display, then arrange them in the order the error object displays them by default.
                if ($logErrorRecordMsg) { [array] $logErrorMessageTmp += $logErrorRecordMsg }
                if ($logErrorInvocationMsg) { [array] $logErrorMessageTmp += $logErrorInvocationMsg }
                if ($logErrorExceptionMsg) { [array] $logErrorMessageTmp += $logErrorExceptionMsg }
            }
            Else {
                # Display selected properties in our custom order
                if ($logErrorExceptionMsg) { [array] $logErrorMessageTmp += $logErrorExceptionMsg }
                if ($logErrorRecordMsg) { [array] $logErrorMessageTmp += $logErrorRecordMsg }
                if ($logErrorInvocationMsg) { [array] $logErrorMessageTmp += $logErrorInvocationMsg }
            }

            if ($logErrorMessageTmp) {
                $logErrorMessage = 'Error Record:'
                $logErrorMessage += "`n-------------"
                $logErrorMsg = $logErrorMessageTmp | Format-List | Out-String
                $logErrorMessage += $logErrorMsg
            }

            ## Capture Error Inner Exception(s)
            if (-not $SkipGetErrorInnerException) {
                if ($errRecord.Exception -and $errRecord.Exception.InnerException) {
                    $logInnerMessage = 'Error Inner Exception(s):'
                    $logInnerMessage += "`n-------------------------"

                    $errorInnerException = $errRecord.Exception.InnerException
                    $count = 0

                    while ($errorInnerException) {
                        [string] $InnerExceptionSeperator = '~' * 40

                        [string[]] $selectedProperties = & $selectProperty -InputObject $errorInnerException -Property $Property
                        $logerrorInnerExceptionMsg = $errorInnerException | Select-Object -Property $selectedProperties | Format-List | Out-String

                        if ($count -gt 0) { $logInnerMessage += $InnerExceptionSeperator }
                        $logInnerMessage += $logerrorInnerExceptionMsg

                        $count++
                        $errorInnerException = $errorInnerException.InnerException
                    }
                }
            }

            if ($logErrorMessage) { $output = $logErrorMessage }
            if ($logInnerMessage) { $output += $logInnerMessage }

            Write-Output -InputObject $output

            if (Test-Path -LiteralPath 'variable:Output') { Clear-Variable -Name 'Output' }
            if (Test-Path -LiteralPath 'variable:LogErrorMessage') { Clear-Variable -Name 'LogErrorMessage' }
            if (Test-Path -LiteralPath 'variable:LogInnerMessage') { Clear-Variable -Name 'LogInnerMessage' }
            if (Test-Path -LiteralPath 'variable:LogErrorMessageTmp') { Clear-Variable -Name 'LogErrorMessageTmp' }
        }
    }
    end {
    }
}
function global:Write-Debug {
    [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkID=113424', RemotingCapability='None')]
    param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [Alias('Msg')]
        [AllowEmptyString()]
        [string]
        ${Message}
    )

    begin
    {
        if ($MyInvocation.PSCommandPath) {
            $invoFile = Split-Path $MyInvocation.PSCommandPath -Leaf
        } else {
            $invoFile = Split-Path $MyInvocation.InvocationName -Leaf
        }

        $writeLog = @{
            'Severity' = 'Debug';
            'Component' = (& { $PSCallStack = (Get-PSCallStack)[2]; "$($PSCallStack.Command) $($PSCallStack.Arguments)" });
            'Source' = "${invoFile}:$($MyInvocation.ScriptLineNumber)";
        }

        if (-not ($env:PSWriteLogDebugSilent -as [bool])) {
            try {
                $outBuffer = $null
                if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
                {
                    $PSBoundParameters['OutBuffer'] = 1
                }
                $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Write-Debug', [System.Management.Automation.CommandTypes]::Cmdlet)
                $scriptCmd = { & $wrappedCmd @PSBoundParameters }
                $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
                $steppablePipeline.Begin($PSCmdlet)
            } catch {
                throw
            }
        }
    }

    process
    {
        if (-not ($env:PSWriteLogDebugNoLog -as [bool])) {
            if ((Get-Command 'Write-Log' -ErrorAction 'Ignore') -and ($DebugPreference -ine 'SilentlyContinue')) {
                Write-Log @writeLog -Message $Message
            }
        }

        if (-not ($env:PSWriteLogDebugSilent -as [bool])) {
            try {
                $steppablePipeline.Process($_)
            } catch {
                throw
            }
        }
    }

    end
    {
        if (-not ($env:PSWriteLogDebugSilent -as [bool])) {
            try {
                $steppablePipeline.End()
            } catch {
                throw
            }
        }
    }
    <#
 
    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Write-Debug
    .ForwardHelpCategory Cmdlet
 
    #>

}
function global:Write-Error {
    [CmdletBinding(DefaultParameterSetName='NoException', HelpUri='http://go.microsoft.com/fwlink/?LinkID=113425', RemotingCapability='None')]
    param(
        [Parameter(ParameterSetName='WithException', Mandatory=$true)]
        [System.Exception]
        ${Exception},

        [Parameter(ParameterSetName='NoException', Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [Parameter(ParameterSetName='WithException')]
        [Alias('Msg')]
        [AllowEmptyString()]
        [AllowNull()]
        [string]
        ${Message},

        [Parameter(ParameterSetName='ErrorRecord', Mandatory=$true)]
        [System.Management.Automation.ErrorRecord]
        ${ErrorRecord},

        [Parameter(ParameterSetName='NoException')]
        [Parameter(ParameterSetName='WithException')]
        [System.Management.Automation.ErrorCategory]
        ${Category},

        [Parameter(ParameterSetName='NoException')]
        [Parameter(ParameterSetName='WithException')]
        [string]
        ${ErrorId},

        [Parameter(ParameterSetName='WithException')]
        [Parameter(ParameterSetName='NoException')]
        [System.Object]
        ${TargetObject},

        [string]
        ${RecommendedAction},

        [Alias('Activity')]
        [string]
        ${CategoryActivity},

        [Alias('Reason')]
        [string]
        ${CategoryReason},

        [Alias('TargetName')]
        [string]
        ${CategoryTargetName},

        [Alias('TargetType')]
        [string]
        ${CategoryTargetType}
    )

    begin
    {
        if ($MyInvocation.PSCommandPath) {
            $invoFile = Split-Path $MyInvocation.PSCommandPath -Leaf
        } else {
            $invoFile = Split-Path $MyInvocation.InvocationName -Leaf
        }

        $writeLog = @{
            'Severity' = 'Error';
            'Component' = (& { $PSCallStack = (Get-PSCallStack)[2]; "$($PSCallStack.Command) $($PSCallStack.Arguments)" });
            'Source' = "${invoFile}:$($MyInvocation.ScriptLineNumber)";
        }

        if (-not ($env:PSWriteLogErrorSilent -as [bool])) {
            try {
                $outBuffer = $null
                if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
                {
                    $PSBoundParameters['OutBuffer'] = 1
                }
                $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Write-Error', [System.Management.Automation.CommandTypes]::Cmdlet)
                $scriptCmd = {& $wrappedCmd @PSBoundParameters }
                $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
                $steppablePipeline.Begin($PSCmdlet)
            } catch {
                throw
            }
        }
    }

    process
    {
        if (Get-Command 'Write-Log' -ErrorAction 'Ignore') {
            [System.Collections.ArrayList] $msg = @()

            if ($PSBoundParameters.ContainsKey('ErrorRecord')) {
                $msg.Add("[$($ErrorRecord.Exception.GetType().FullName)]") | Out-Null
                $msg.Add($ErrorRecord.Exception.Message) | Out-Null
            } elseif ($PSBoundParameters.ContainsKey('Exception')) {
                $msg.Add("[${Exception}]") | Out-Null
                $msg.Add($Message) | Out-Null
            } else {
                $msg.Add($Message) | Out-Null
            }

            if (
                $Category.isPresent -or
                $ErrorId.isPresent -or
                $TargetObject.isPresent -or
                $RecommendedAction.isPresent -or
                $CategoryActivity.isPresent -or
                $CategoryReason.isPresent -or
                $CategoryTargetName.isPresent -or
                $CategoryTargetType.isPresent
            ) {
                $msg.Add("`nError Details As Json: $($PSBoundParameters | ConvertTo-Json)") | Out-Null
            }

            Write-Log @writeLog -Message ($msg -join ' ') -ErrorAction 'Stop'
        }

        if (-not ($env:PSWriteLogErrorSilent -as [bool])) {
            try {
                $steppablePipeline.Process($_)
            } catch {
                throw
            }
        }
    }

    end
    {
        if (-not ($env:PSWriteLogErrorSilent -as [bool])) {
            try {
                $steppablePipeline.End()
            } catch {
                throw
            }
        }
    }
    <#
 
    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Write-Error
    .ForwardHelpCategory Cmdlet
 
    #>

}
function global:Write-Host {
    [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkID=113426', RemotingCapability='None')]
    param(
        [Parameter(Position=0, ValueFromPipeline=$true, ValueFromRemainingArguments=$true)]
        [System.Object]
        ${Object},

        [switch]
        ${NoNewline},

        [System.Object]
        ${Separator},

        [System.ConsoleColor]
        ${ForegroundColor},

        [System.ConsoleColor]
        ${BackgroundColor}
    )

    begin
    {
        if ($MyInvocation.PSCommandPath) {
            $invoFile = Split-Path $MyInvocation.PSCommandPath -Leaf
        } else {
            $invoFile = Split-Path $MyInvocation.InvocationName -Leaf
        }

        $writeLog = @{
            'Component' = (& { $PSCallStack = (Get-PSCallStack)[2]; "$($PSCallStack.Command) $($PSCallStack.Arguments)" });
            'Source' = "${invoFile}:$($MyInvocation.ScriptLineNumber)";
        }

        if (-not ($env:PSWriteLogHostSilent -as [bool])) {
            try {
                $outBuffer = $null
                if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
                {
                    $PSBoundParameters['OutBuffer'] = 1
                }
                $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Write-Host', [System.Management.Automation.CommandTypes]::Cmdlet)
                $scriptCmd = {& $wrappedCmd @PSBoundParameters }
                $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
                $steppablePipeline.Begin($PSCmdlet)
            } catch {
                throw
            }
        }
    }

    process
    {
        if (Get-Command 'Write-Log' -ErrorAction 'Ignore') {
            Write-Log @writeLog -Message $Object
        }

        if (-not ($env:PSWriteLogHostSilent -as [bool])) {
            try {
                $steppablePipeline.Process($_)
            } catch {
                throw
            }
        }
    }

    end
    {
        if (-not ($env:PSWriteLogHostSilent -as [bool])) {
            try {
                $steppablePipeline.End()
            } catch {
                throw
            }
        }
    }
    <#
 
    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Write-Host
    .ForwardHelpCategory Cmdlet
 
    #>

}
function global:Write-Information {
    [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkId=525909', RemotingCapability='None')]
    param(
        [Parameter(Mandatory=$true, Position=0)]
        [Alias('Msg')]
        [System.Object]
        ${MessageData},

        [Parameter(Position=1)]
        [string[]]
        ${Tags}
    )

    begin
    {
        if ($MyInvocation.PSCommandPath) {
            $invoFile = Split-Path $MyInvocation.PSCommandPath -Leaf
        } else {
            $invoFile = Split-Path $MyInvocation.InvocationName -Leaf
        }

        $writeLog = @{
            'Component' = (& { $PSCallStack = (Get-PSCallStack)[2]; "$($PSCallStack.Command) $($PSCallStack.Arguments)" });
            'Source' = "${invoFile}:$($MyInvocation.ScriptLineNumber)";
        }

        if (-not ($env:PSWriteLogInformationSilent -as [bool])) {
            try {
                $outBuffer = $null
                if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
                {
                    $PSBoundParameters['OutBuffer'] = 1
                }
                $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Write-Information', [System.Management.Automation.CommandTypes]::Cmdlet)
                $scriptCmd = {& $wrappedCmd @PSBoundParameters }
                $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
                $steppablePipeline.Begin($PSCmdlet)
            } catch {
                throw
            }
        }
    }

    process
    {
        if ((Get-Command 'Write-Log' -ErrorAction 'Ignore') -and ($InformationPreference -ine 'SilentlyContinue')) {
            if ($Tags.isPresent) {
                Write-Log @writeLog -Message "{$($Tags -join ',')} $MessageData"
            } else {
                Write-Log @writeLog -Message "$MessageData"
            }
        }

        if (-not ($env:PSWriteLogInformationSilent -as [bool])) {
            try {
                $steppablePipeline.Process($_)
            } catch {
                throw
            }
        }
    }

    end
    {
        if (-not ($env:PSWriteLogInformationSilent -as [bool])) {
            try {
                $steppablePipeline.End()
            } catch {
                throw
            }
        }
    }
    <#
 
    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Write-Information
    .ForwardHelpCategory Cmdlet
 
    #>

}
function global:Write-Output {
    [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkID=113427', RemotingCapability='None')]
    param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromRemainingArguments=$true)]
        [AllowNull()]
        [AllowEmptyCollection()]
        [psobject[]]
        ${InputObject},

        [switch]
        ${NoEnumerate}
    )

    begin
    {
        if ($MyInvocation.PSCommandPath) {
            $invo_file = Split-Path $MyInvocation.PSCommandPath -Leaf
        } else {
            $invo_file = Split-Path $MyInvocation.InvocationName -Leaf
        }

        $writeLog = @{
            'Severity' = 'Output'
            'Component' = (& { $PSCallStack = (Get-PSCallStack)[2]; "$($PSCallStack.Command) $($PSCallStack.Arguments)" });
            'Source' = "${invo_file}:$($MyInvocation.ScriptLineNumber)";
        }

        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
            {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Write-Output', [System.Management.Automation.CommandTypes]::Cmdlet)
            $scriptCmd = {& $wrappedCmd @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
        } catch {
            throw
        }
    }

    process
    {
        if ((Get-Command 'Write-Log' -ErrorAction 'Ignore') -and ($env:PSWriteLogOutputLog -as [bool])) {
            Write-Log @writeLog -Message ($InputObject | Out-String)
        }

        try {
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }

    end
    {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
    <#
 
    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Write-Output
    .ForwardHelpCategory Cmdlet
 
    #>

}
function global:Write-Progress {
    [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkID=113428', RemotingCapability='None')]
    param(
        [Parameter(Mandatory=$true, Position=0)]
        [string]
        ${Activity},

        [Parameter(Position=1)]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Status},

        [Parameter(Position=2)]
        [ValidateRange(0, 2147483647)]
        [int]
        ${Id},

        [ValidateRange(-1, 100)]
        [int]
        ${PercentComplete},

        [int]
        ${SecondsRemaining},

        [string]
        ${CurrentOperation},

        [ValidateRange(-1, 2147483647)]
        [int]
        ${ParentId},

        [switch]
        ${Completed},

        [int]
        ${SourceId}
    )

    begin
    {
        if ($MyInvocation.PSCommandPath) {
            $invoFile = Split-Path $MyInvocation.PSCommandPath -Leaf
        } else {
            $invoFile = Split-Path $MyInvocation.InvocationName -Leaf
        }

        $writeLog = @{
            'Severity' = 'Progress'
            'Component' = (& { $PSCallStack = (Get-PSCallStack)[2]; "$($PSCallStack.Command) $($PSCallStack.Arguments)" });
            'Source' = "${invoFile}:$($MyInvocation.ScriptLineNumber)";
        }

        if (-not ($env:PSWriteLogProgessSilent -as [bool])) {
            try {
                $outBuffer = $null
                if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
                {
                    $PSBoundParameters['OutBuffer'] = 1
                }
                $PSBoundParameters.Remove('WriteHostColor')
                $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Write-Progress', [System.Management.Automation.CommandTypes]::Cmdlet)
                $scriptCmd = {& $wrappedCmd @PSBoundParameters }
                $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
                $steppablePipeline.Begin($PSCmdlet)
            } catch {
                throw
            }
        }
    }

    process
    {
        if (Get-Command 'Write-Log' -ErrorAction 'Ignore' -and ($ProgressPreference -ine 'SilentlyContinue')) {
            [System.Collections.ArrayList] $Message = @()
            if ($PSBoundParameters.ContainsKey('ParentId')) { $Message.Add("[${ParentId}]") | Out-Null }
            if ($PSBoundParameters.ContainsKey('Id')) { $Message.Add("[${Id}]") | Out-Null }
            $Message.Add($Activity) | Out-Null
            if ($PSBoundParameters.ContainsKey('PercentComplete')) { $Message.Add("${PercentComplete}%") | Out-Null }
            if ($PSBoundParameters.ContainsKey('SecondsRemaining')) { $Message.Add("(${SecondsRemaining} Seconds Remaining)") | Out-Null }
            $Message.Add(':') | Out-Null
            $Message.Add($Status) | Out-Null
            $Message.Add(':') | Out-Null
            $Message.Add($CurrentOperation) | Out-Null

            Write-Log @writeLog -Message ($Message -join ' ')
        }

        if (-not ($env:PSWriteLogProgessSilent -as [bool])) {
            try {
                $steppablePipeline.Process($_)
            } catch {
                throw
            }
        }
    }

    end
    {
        if (-not ($env:PSWriteLogProgessSilent -as [bool])) {
            try {
                $steppablePipeline.End()
            } catch {
                throw
            }
        }
    }
    <#
 
    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Write-Progress
    .ForwardHelpCategory Cmdlet
 
    #>

}
function global:Write-Verbose {
    [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkID=113429', RemotingCapability='None')]
    param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [Alias('Msg')]
        [AllowEmptyString()]
        [string]
        ${Message}
    )

    begin
    {
        if ($MyInvocation.PSCommandPath) {
            $invoFile = Split-Path $MyInvocation.PSCommandPath -Leaf
        } else {
            $invoFile = Split-Path $MyInvocation.InvocationName -Leaf
        }

        $writeLog = @{
            'Severity' = 'Verbose';
            'Component' = (& { $PSCallStack = (Get-PSCallStack)[2]; "$($PSCallStack.Command) $($PSCallStack.Arguments)" });
            'Source' = "${invoFile}:$($MyInvocation.ScriptLineNumber)";
        }

        if (-not ($env:PSWriteLogVerboseSilent -as [bool])) {
            try {
                $outBuffer = $null
                if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
                {
                    $PSBoundParameters['OutBuffer'] = 1
                }
                $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Write-Verbose', [System.Management.Automation.CommandTypes]::Cmdlet)
                $scriptCmd = {& $wrappedCmd @PSBoundParameters }
                $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
                $steppablePipeline.Begin($PSCmdlet)
            } catch {
                throw
            }
        }
    }

    process
    {
        if ((Get-Command 'Write-Log' -ErrorAction 'Ignore') -and ($VerbosePreference -ine 'SilentlyContinue')) {
            Write-Log @writeLog -Message $Message
        }

        if (-not ($env:PSWriteLogVerboseSilent -as [bool])) {
            try {
                $steppablePipeline.Process($_)
            } catch {
                throw
            }
        }
    }

    end
    {
        if (-not ($env:PSWriteLogVerboseSilent -as [bool])) {
            try {
                $steppablePipeline.End()
            } catch {
                throw
            }
        }
    }
    <#
 
    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Write-Verbose
    .ForwardHelpCategory Cmdlet
 
    #>

}
function global:Write-Warning {
    [CmdletBinding(HelpUri='http://go.microsoft.com/fwlink/?LinkID=113430', RemotingCapability='None')]
    param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [Alias('Msg')]
        [AllowEmptyString()]
        [string]
        ${Message}
    )

    begin
    {
        if ($MyInvocation.PSCommandPath) {
            $invoFile = Split-Path $MyInvocation.PSCommandPath -Leaf
        } else {
            $invoFile = Split-Path $MyInvocation.InvocationName -Leaf
        }

        $writeLog = @{
            'Severity' = 'Warning';
            'Component' = (& { $PSCallStack = (Get-PSCallStack)[2]; "$($PSCallStack.Command) $($PSCallStack.Arguments)" });
            'Source' = "${invoFile}:$($MyInvocation.ScriptLineNumber)";
        }

        if (-not ($env:PSWriteLogWarningSilent -as [bool])) {
            try {
                $outBuffer = $null
                if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
                {
                    $PSBoundParameters['OutBuffer'] = 1
                }
                $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Write-Warning', [System.Management.Automation.CommandTypes]::Cmdlet)
                $scriptCmd = {& $wrappedCmd @PSBoundParameters }
                $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
                $steppablePipeline.Begin($PSCmdlet)
            } catch {
                throw
            }
        }
    }

    process
    {
        if ((Get-Command 'Write-Log' -ErrorAction 'Ignore') -and ($WarningPreference -ine 'SilentlyContinue')) {
            Write-Log @writeLog -Message $Message
        }

        if (-not ($env:PSWriteLogWarningSilent -as [bool])) {
            try {
                $steppablePipeline.Process($_)
            } catch {
                throw
            }
        }
    }

    end
    {
        if (-not ($env:PSWriteLogWarningSilent -as [bool])) {
            try {
                $steppablePipeline.End()
            } catch {
                throw
            }
        }
    }
    <#
 
    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Write-Warning
    .ForwardHelpCategory Cmdlet
 
    #>

}
$psd1 = Import-PowerShellDataFile ([IO.Path]::Combine($PSScriptRoot, 'PSWriteLog.psd1'))

# Check if the current context is elevated (Are we running as an administrator?)
if ((New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
    # Anytime this Module is used, the version and timestamp will be stored in the registry.
    # This will allow more intelligent purging of unused versions.
    $versionUsed = @{
        LiteralPath = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\VertigoRay\PSWriteLog\VersionsUsed'
        Name = $psd1.ModuleVersion
        Value = (Get-Date -Format 'O')
        Force = $true
    }
    Write-Debug ('Version Used: {0}' -f ($versionUsed | ConvertTo-Json))
    if (-not (Test-Path $versionUsed.LiteralPath)) {
        New-Item -Path $versionUsed.LiteralPath -Force
    }
    Set-ItemProperty @versionUsed
}

$moduleMember = @{
    Cmdlet = $psd1.CmdletsToExport
    Function = $psd1.FunctionsToExport
    Alias = $psd1.AliasesToExport
}
if ($psd1.VariablesToExport) {
    $moduleMember.Set_Item('Variable', $psd1.VariablesToExport)
}
Export-ModuleMember @moduleMember