PwSh.Fw.Log.psm1

<#
    .NOTES
        Author: Charles-Antoine Degennes <cadegenn@gmail.com>
        Version: 0.0.1
        Changelog:
            2018.09.24, DCA - inital version
        Manifest created with command line:
        New-ModuleManifest PwSh.Log.psd1 -RootModule PwSh.Log.psm1 -ModuleVersion "0.0.1" -Author "Charles-Antoine Degennes <cadegenn@gmail.com>"
#>


$Script:HOSTNAME = hostname

<#
    .SYNOPSIS
    Print a message into a log file
 
    .DESCRIPTION
    Print a message into a log file
 
    .PARAMETER Append
    Append message to the log file. Do not overwrite it.
    Append = $true is the default. If you want to overwrite or initiate the file, call
    Write-ToLogFile -message "Logfile initialized" -Append:$false
 
    .PARAMETER NoNewline
    Do not append a new line at the end of file.
 
    .PARAMETER NoHeader
    Do not print header informations : "date hostname scriptname". Usefull to append text to an existing line.
 
    .EXAMPLE
    Write-ToLogFile -message "a log entry" -append
 
#>

function Write-ToLogFile() {
    [CmdletBinding()]param(
        [switch]$Append,
        [switch]$NoNewLine,
        [switch]$NoHeader,
        [string]$message,
        [string]$logFile = $Global:LOG
    )

    $date = Get-Date -UFormat %c
    if ($NoHeader) {
        $msg = $message
    } else {
        $msg = "`n$date $script:hostname $Global:BASENAME $message"
    }

    if ($logFile) {
        $msg | Out-File $logFile -Append:$Append -NoNewLine:$NoNewLine -Encoding utf8
    }
}


<#
    .SYNOPSIS
    Send a message to a syslog server.
 
    .DESCRIPTION
    Send a message to a syslog server.
 
 
    .EXAMPLE
    $message = "This is a test"
    SendTo-SysLog aa.bb.cc.dd "local7" "notice" $message $script:hostname "myapp"
 
#>

function Send-ToSysLog () {
    [CmdletBinding()]Param (
        [string]$Server = "127.0.0.1",
        [string]$Port = 514,
        [ValidateSet('TCP', 'UDP', IgnoreCase = $true)]
        [string]$Protocol = "UDP",
        [switch]$TCP,
        [switch]$UDP,
        [ValidateSet('kern', 'user', 'mail', 'daemon', 'auth', 'syslog', 'lpr', 'news', 'uucp', 'cron', 'authpriv', 'ftp', 'ntp', 'logaudit', 'logalert', 'clock', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', IgnoreCase = $true)]
        [string]$Facility = "local7",
        [ValidateSet('Emergency', 'Alert', 'Critical', 'Error', 'Warning', 'Notice', 'Info', 'Debug', IgnoreCase = $true)]
        [string]$Severity = "Notice",
        [string]$Message = "Your payload...",
        [string]$FromHost = $script:hostname,
        [string]$Tag = "PowerShell"
    )
    Begin {
        # eenter($MyInvocation.MyCommand)
        if ($UDP) {
            $Protocol = "UDP"
        }
        if ($TCP) {
            $Protocol = "TCP"
        }
    }

    Process {
        switch ($Facility.ToLower()) {
            'kern' {$iFacility = 0 * 8 ; break }
            'user' {$iFacility = 1 * 8 ; break }
            'mail' {$iFacility = 2 * 8 ; break }
            'system' {$iFacility = 3 * 8 ; break }
            'auth' {$iFacility = 4 * 8 ; break }
            'syslog' {$iFacility = 5 * 8 ; break }
            'lpr' {$iFacility = 6 * 8 ; break }
            'news' {$iFacility = 7 * 8 ; break }
            'uucp' {$iFacility = 8 * 8 ; break }
            'cron' {$iFacility = 9 * 8 ; break }
            'authpriv' {$iFacility = 10 * 8 ; break }
            'ftp' {$iFacility = 11 * 8 ; break }
            'ntp' {$iFacility = 12 * 8 ; break }
            'logaudit' {$iFacility = 13 * 8 ; break }
            'logalert' {$iFacility = 14 * 8 ; break }
            'clock' {$iFacility = 15 * 8 ; break }
            'local0' {$iFacility = 16 * 8 ; break }
            'local1' {$iFacility = 17 * 8 ; break }
            'local2' {$iFacility = 18 * 8 ; break }
            'local3' {$iFacility = 19 * 8 ; break }
            'local4' {$iFacility = 20 * 8 ; break }
            'local5' {$iFacility = 21 * 8 ; break }
            'local6' {$iFacility = 22 * 8 ; break }
            'local7' {$iFacility = 23 * 8 ; break }
            default {$iFacility = 23 * 8 } #Default is local7
        }

        switch ($Severity.ToLower()) {
            'emergency' {$iSeverity = 0 ; break } #Emergency
            'alert' {$iSeverity = 1 ; break } #Alert
            'critical' {$iSeverity = 2 ; break } #Critical
            'error' {$iSeverity = 3 ; break } #Error
            'warning' {$iSeverity = 4 ; break } #Warning
            'notice' {$iSeverity = 5 ; break } #Notice
            'info' {$iSeverity = 6 ; break } #Informational
            'debug' {$iSeverity = 7 ; break } #Debug
            default {$iSeverity = 5 } #Default is Notice
        }

        $pri = "<" + ($iFacility + $iSeverity) + ">"

        # Note that the timestamp is local time on the originating computer, not UTC.
        # if ($(get-date).day -lt 10) { $timestamp = $(get-date).tostring("MMM d HH:mm:ss") } else { $timestamp = $(get-date).tostring("MMM dd HH:mm:ss") }
        # comply with RFC3164 §4.1.2 @url https://tools.ietf.org/html/rfc3164#page-10
        if ((Get-Culture).Name -eq "en-US") {
            $timestamp = (get-date).tostring("MMM dd HH:mm:ss")
        } else {
            # check if Use-Culture exist (bundled with PowerShellCookBook module)
            if (Get-Command Use-Culture -ErrorAction SilentlyContinue) {
                $timestamp = Use-Culture en-US { (get-date).tostring("MMM dd HH:mm:ss") }
            } else {
                eerror "Use-Culture cmdlet not found on this system. Please install PowerShellCookBook module."
                return $false
            }
        }

        # Hostname does not have to be in lowercase, and it shouldn't have spaces anyway, but lowercase is more traditional.
        # The name should be the simple hostname, not a fully-qualified domain name, but the script doesn't enforce this.
        $header = $timestamp + " " + $FromHost.tolower().replace(" ","").trim() + " "

        #Cannot have non-alphanumerics in the TAG field or have it be longer than 32 characters.
        if ($tag -match '[^a-z0-9]') { $tag = $tag -replace '[^a-z0-9]','' } #Simply delete the non-alphanumerics
        if ($tag.length -gt 32) { $tag = $tag.substring(0,31) } #and truncate at 32 characters.

        $msg = $pri + $header + $tag + ": " + $Message

        # Convert message to array of ASCII bytes.
        $bytearray = $([System.Text.Encoding]::ASCII).getbytes($msg)

        # RFC3164 Section 4.1: "The total length of the packet MUST be 1024 bytes or less."
        # "Packet" is not "PRI + HEADER + MSG", and IP header = 20, UDP header = 8, hence:
        if ($bytearray.count -gt 996) { $bytearray = $bytearray[0..995] }

        # Send the message...
        switch ($Protocol) {
            'UDP' {
                $UdpClient = New-Object System.Net.Sockets.UdpClient
                try {
                    # $Server
                    # $Port
                    $UdpClient.Connect($Server,$Port)
                    $UdpClient.Send($ByteArray, $ByteArray.length) | out-null
                } catch {
                    eerror "Failed to send syslog message via UDP. More Info: $_"
                } finally {
                    $UdpClient.Close()
                }
            }
            'TCP' {
                $TcpClient = New-Object System.Net.Sockets.TcpClient
                try {
                    # $Server
                    # $Port
                    $TcpClient.Connect($Server,$Port)
                    $TcpStream = $TcpClient.GetStream()
                    $TcpWriter = New-Object System.IO.StreamWriter($TcpStream)
                    $TcpWriter.AutoFlush = $true
                    $TcpWriter.Write($ByteArray, 0, $ByteArray.length)
                } catch {
                    eerror "Failed to send syslog message via TCP. More Info: $_"
                } finally {
                    $TcpClient.Close()
                }
            }
        }
    }

    End {
        # eleave($MyInvocation.MyCommand)
    }

}

function Write-LogMessage {
    [CmdletBinding()]Param (
        [Parameter(Mandatory = $true,ValueFromPipeLine = $true)][string]$Message,
        [ValidateSet('kern', 'user', 'mail', 'daemon', 'auth', 'syslog', 'lpr', 'news', 'uucp', 'cron', 'authpriv', 'ftp', 'ntp', 'logaudit', 'logalert', 'clock', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', IgnoreCase = $true)]
        [string]$Facility = "local7",
        [ValidateSet('Emergency', 'Alert', 'Critical', 'Error', 'Warning', 'Notice', 'Info', 'Debug', 'Devel', IgnoreCase = $true)]
        [string]$Severity = "Notice"
    )
    Begin {
        # eenter($Script:NS + '\' + $MyInvocation.MyCommand)
    }

    Process {
        return $Message
    }

    End {
        # eleave($Script:NS + '\' + $MyInvocation.MyCommand)
    }
}