Public/Log/Write-Log.ps1

Function Write-Log {
    <#
    .SYNOPSIS
        Writes a nicely formatted Message to stdout/file/event log.

    .DESCRIPTION
        It uses optional $Global:LogConfiguration object which describes logging configuration (see LogConfiguration.ps1).

    .INPUTS
        A string message to log.

    .EXAMPLE
        Write-Log -Info "Generating file test.txt."
        [I] 2017-05-19 11:14:28 [hostName/userName]: (scriptName/commandName/1) Generating file test.txt.

    .EXAMPLE
        $Global.LogConfiguration.LogLevel = [LogLevel]::WARN
        Write-Log -Info "Generating file test.txt."
        <nothing will be logged>
        Write-Log -Error "Failed to generate file test.txt."
        [E] 2017-05-19 11:14:28 [hostName/userName]: (scriptName/commandName/1) Failed to generate file test.txt.

    .EXAMPLE
        $Global.LogConfiguration.LogFile = 'log.txt'
        Write-Log -Info "Generating file test.txt."

        Logs message to stdout and log.txt file.


    .EXAMPLE
        $Global.LogConfiguration.LogEventLogSource = 'log.txt'
        $Global.LogConfiguration.LogEventLogThreshold = 'log.txt'
        Write-Log -Info "Generating file test.txt."
    #>

    
    [CmdletBinding()]
    [OutputType([void])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", '')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", '')]
    param(       
        # If specified, an error will be logged.
        [Parameter(Mandatory=$false)]
        [switch] 
        $Error = $false,
        
        # If specified, a warning will be logged.
        [Parameter(Mandatory=$false)]
        [switch] 
        $Warn = $false,
        
        # If specified, an info message will be logged (default).
        [Parameter(Mandatory=$false)]
        [switch] 
        $Info = $false,
        
        # If specified, a debug message will be logged.
        [Parameter(Mandatory=$false)]
        [switch] 
        $_debug = $false,
        
        # If set, the message at console will be emphasized using colors.
        [Parameter(Mandatory=$false)]
        [switch] 
        $Emphasize = $false,
        
        # If specified, header information will not be logged (e.g. '[ERROR]: (function_name)').
        [Parameter(Mandatory=$false)]
        [switch] 
        $NoHeader = $false,

        # Message to output.
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [string[]]
        $Message,

        # Additional indent.
        [Parameter(Mandatory=$false)]
        [int]
        $Indent = 0,

        # If enabled, all log output will be available as return value (will use Write-Output instead of Write-Host).
        [Parameter(Mandatory=$false)]
        [switch] 
        $PassThru = $false
    )

    Begin { 
        if ($Error) {
            $severity = 3;
            $severityChar = 'E'
        } 
        elseif ($warn) {
            $severity = 2;
            $severityChar = 'W'
        } 
        elseif ($_debug) {
            $severity = 0;
            $severityChar = 'D'
        } 
        else {
            $severity = 1;
            $severityChar = 'I'
        }

        if ($LogConfiguration) {
            switch ($LogConfiguration.LogLevel.ToUpper()[0]) {
                'E' { $configSeverity = 3 }
                'W' { $configSeverity = 2 }
                'D' { $configSeverity = 0 }
                default { $configSeverity = 1 }
            }
            if ($severity -lt $configSeverity) {
                return
            }
        }

        $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        $callerInfo = (Get-PSCallStack)[1]
        $callerCommandName = $callerInfo.InvocationInfo.MyCommand.Name
        if ($callerInfo.ScriptName) {
            $callerScriptName = Split-Path -Leaf $callerInfo.ScriptName
        } 
        else {
            $callerScriptName = 'no script';
        }
        $callerLineNumber = $callerInfo.ScriptLineNumber
        $callerInfoString = "$callerScriptName/$callerCommandName/$callerLineNumber"
        
        if ($NoHeader) {
            $outputHeader = ""
        } 
        else {
            $currentHostname = [system.environment]::MachineName
            $currentUsername = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
            if (Get-Variable -Name PSSenderInfo -ErrorAction SilentlyContinue) {
                $remotingFlag = '[R] '
            } 
            else {
                $remotingFlag = ''
            }
            $outputHeader = "[$severityChar] $timestamp ${remotingFlag}[$currentHostname/${currentUsername}]: ($callerInfoString) "
        }
        $header = " " * $Indent + $outputHeader
    }
    Process { 
        try { 
            Write-LogToStdOut -Header $Header -Message $Message -Severity $Severity -Emphasize:$Emphasize
            Write-LogToFile -Header $Header -Message $Message -Severity $Severity
            Write-LogToEventLog -Header $Header -Message $Message -Severity $Severity
            Write-LogToPSOutput -Header $Header -Message $Message -Severity $Severity -PassThru:$PassThru
        } catch {
            $exception = $_.Exception
            $msg = "Writing to log failed - script will terminate.`r`n"
            $currentUsername = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
            if ($LogConfiguration -and $LogConfiguration.LogFile) {
                $msg += "`r`nPlease ensure that current user ('{0}') has access to file '{1}' or change the path to log file in `$LogConfiguration.LogFile." -f $currentUsername, $LogConfiguration.LogFile
            }
            if ($LogConfiguration -and $LogConfiguration.LogEventLogSource) {
                $msg += "`r`nPlease ensure that current user ('{0}') is able to create Event Log sources or create the source manually." -f $currentUsername
            }
        
            $msg += "`n" + ($_ | Format-List -Force | Out-String) + ($exception | Format-List -Force | Out-String)
            Write-Host $msg
            [void](New-Item -Path "error.txt" -ItemType file -Value $msg -Force)
            throw "Logging failure"
        }
    }

    End {
    }
}