Noveris.Logger.psm1

<#
#>


$script:LoggerTypes = @{}
$script:LoggerDefault = $null

$ErrorActionPreference = "Stop"

Class LoggerType
{
    [string]$Name
    [string]$Prefix
    [Nullable[System.ConsoleColor]]$Color
    [string]$LogPath
    [bool]$WriteHost
    [bool]$WriteOutput
    [ScriptBlock]$FormatBlock
    [ScriptBlock]$OutputBlock

    LoggerType([string]$Name)
    {
        $this.Name = $Name
        $this.Prefix = [string]::Empty
        $this.Color = $null
        $this.LogPath = [string]::Empty
        $this.WriteHost = $false
        $this.WriteOutput = $false
        $this.FormatBlock = $null
        $this.OutputBlock = $null
    }

    LoggerType([LoggerType] $loggerType)
    {
        $this.Name = $loggerType.Name
        $this.Prefix = $loggerType.Prefix
        $this.Color = $loggerType.Color
        $this.LogPath = $loggerType.LogPath
        $this.WriteHost = $loggerType.WriteHost
        $this.WriteOutput = $loggerType.WriteOutput
        $this.FormatBlock = $loggerType.FormatBlock
        $this.OutputBlock = $loggerType.OutputBlock
    }
}

<#
    .SYNOPSIS
    Generate a new Logger Type for use by Write-Logger when generating messages. Names are accepted by pipeline or parameter.
 
    .PARAMETER Name
    The name of the Logger Type
 
    .PARAMETER Prefix
    A string value to prefix the message with. This is performed after any FormatBlock script.
 
    .PARAMETER Color
    The Color to use when writing content via Write-Host.
 
    .PARAMETER LogPath
    Path to a file to write finalised message content to. Contents are appended and written in UTF8
    format.
 
    .PARAMETER WriteHost
    Determine whether the message should be written to the console using Write-Host
 
    .PARAMETER WriteOutput
    Determine whether the message should be written as object output from the function
 
    .PARAMETER FormatBlock
    Script Block to be called for reformat the original message e.g. { "test: $_" }
 
    .PARAMETER OutputBlock
    Script Block to be called with the finalised message text.
 
    .PARAMETER Default
    Defines whether this Logger Type should be the default logger type
#>

Function New-LoggerType
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline)]
        [string]$Name,

        [Parameter(Mandatory=$false)]
        [string]$Prefix = [string]::Empty,

        [Parameter(Mandatory=$false)]
        [Nullable[System.ConsoleColor]]$Color = $null,

        [Parameter(Mandatory=$false)]
        [string]$LogPath = [string]::Empty,

        [Parameter(Mandatory=$false)]
        [switch]$WriteHost = $false,

        [Parameter(Mandatory=$false)]
        [switch]$WriteOutput = $false,

        [Parameter(Mandatory=$false)]
        [ScriptBlock]$FormatBlock = $null,

        [Parameter(Mandatory=$false)]
        [ScriptBlock]$OutputBlock = $null,

        [Parameter(Mandatory=$false)]
        [switch]$Default = $false
    )

    process
    {
        if ([string]::IsNullOrEmpty($Name))
        {
            throw New-Object ArgumentException -ArgumentList "Name supplied to New-Logger is null or empty"
        }
    
        if ($script:LoggerTypes.ContainsKey($Name))
        {
            throw New-Object ArgumentException -ArgumentList "Name supplied to New-Logger already exists"
        }
    
        Write-Verbose "Creating new logger type: ${Name}"
        $newType = New-Object LoggerType -ArgumentList $Name
        $newType.WriteHost = $WriteHost
        $newType.WriteOutput = $WriteOutput
    
        foreach ($param in $PSBoundParameters.Keys)
        {
            switch ($param)
            {
                "Prefix" {
                    $newType.Prefix = $Prefix
                    break
                }
                "Color" {
                    $newType.Color = $Color
                    break
                }
                "LogPath" {
                    $newType.LogPath = $LogPath
                    break
                }
                "FormatBlock" {
                    $newType.FormatBlock = $FormatBlock
                    break
                }
                "OutputBlock" {
                    $newType.OutputBlock = $OutputBlock
                    break
                }
            }
        }
    
        $script:LoggerTypes[$Name] = $newType
    
        if ($Default)
        {
            Write-Verbose "Setting default logger type to ${Name}"
            $script:LoggerDefault = $Name
        }
    
        Get-LoggerType $Name
    }
}

<#
    .SYNOPSIS
    Returns all Logger Types or a single Logger Type, if a name is referenced. Names may be passed by pipeline input.
 
    .PARAMETER Name
    The Name of the specific Logger Type to display
#>

Function Get-LoggerType
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$false,Position=0,ValueFromPipeline)]
        [string]$Name = [string]::Empty
    )

    process
    {
        $types = $script:LoggerTypes.Keys
        if ($PSBoundParameters.Keys -contains "Name" -and ![string]::IsNullOrEmpty($Name))
        {
            if (!$script:LoggerTypes.ContainsKey($Name))
            {
                throw New-Object ArgumentException -ArgumentList "Name supplied to Get-LoggerType does not exist"
            }
    
            $types = $($Name)
        }
    
        $types = $types | ForEach-Object { $_ }
        $types | ForEach-Object { New-Object LoggerType -ArgumentList $script:LoggerTypes[$_] }
    }
}

<#
    .SYNOPSIS
    Update-LoggerType updates an existing LoggerType with new options or may be used to remove options. LoggerType objects
    can be supplied by pipeline.
 
    .PARAMETER Name
    The name of the Logger Type to update
 
    .PARAMETER Prefix
    A string value to prefix the message with. This is performed after any FormatBlock script.
 
    .PARAMETER Color
    The Color to use when writing content via Write-Host.
 
    .PARAMETER LogPath
    Path to a file to write finalised message content to. Contents are appended and written in UTF8
    format.
 
    .PARAMETER WriteHost
    Determine whether the message should be written to the console using Write-Host
 
    .PARAMETER WriteOutput
    Determine whether the message should be written as object output from the function
 
    .PARAMETER FormatBlock
    Script Block to be called for reformat the original message e.g. { "test: $_" }
 
    .PARAMETER OutputBlock
    Script Block to be called with the finalised message text.
 
    .PARAMETER Default
    Defines whether this Logger Type should be the default logger type
#>

Function Update-LoggerType
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipelineByPropertyName)]
        [string]$Name,

        [Parameter(Mandatory=$false)]
        $Prefix = $null,

        [Parameter(Mandatory=$false)]
        [Nullable[System.ConsoleColor]]$Color = $null,

        [Parameter(Mandatory=$false)]
        $LogPath = $null,

        [Parameter(Mandatory=$false)]
        [switch]$WriteHost = $null,

        [Parameter(Mandatory=$false)]
        [switch]$WriteOutput = $null,

        [Parameter(Mandatory=$false)]
        [ScriptBlock]$FormatBlock = $null,

        [Parameter(Mandatory=$false)]
        [ScriptBlock]$OutputBlock = $null,

        [Parameter(Mandatory=$false)]
        [switch]$RemoveFormatBlock = $false,

        [Parameter(Mandatory=$false)]
        [switch]$RemoveOutputBlock = $false,

        [Parameter(Mandatory=$false)]
        [switch]$Default = $false
    )

    process
    {
        if ([string]::IsNullOrEmpty($Name))
        {
            throw New-Object ArgumentException -ArgumentList "Null or empty Name supplied to Update-LoggerType"
        }

        if (!$script:LoggerTypes.ContainsKey($Name))
        {
            throw New-Object ArgumentException -ArgumentList "Name supplied to Update-LoggerType does not exist"
        }

        Write-Verbose "Updating logger type: ${Name}"
        $loggerType = $script:LoggerTypes[$Name]

        if ($RemoveFormatBlock)
        {
            Write-Verbose "Removing Format block for type (${Name})"
            $loggerType.FormatBlock = $null
        }

        if ($RemoveOutputBlock)
        {
            Write-Verbose "Removing Output block for type (${Name})"
            $loggerType.OutputBlock = $null
        }

        foreach ($param in $PSBoundParameters.Keys)
        {
            switch ($param)
            {
                "Prefix" {
                    $loggerType.Prefix = $Prefix
                    break
                }
                "Color" {
                    $loggerType.Color = $Color
                    break
                }
                "LogPath" {
                    $loggerType.LogPath = $LogPath
                    break
                }
                "WriteHost" {
                    $loggerType.WriteHost = $WriteHost
                    break
                }
                "WriteOutput" {
                    $loggerType.WriteOutput = $WriteOutput
                    break
                }
                "FormatBlock" {
                    $loggerType.FormatBlock = $FormatBlock
                    break
                }
                "OutputBlock" {
                    $loggerType.OutputBlock = $OutputBlock
                    break
                }
                "Default" {
                    if ($Default -eq $true)
                    {
                        Write-Verbose "Setting Logger type (${Name}) as default."
                        $script:LoggerDefault = $Name
                        break
                    }

                    if ($script:LoggerDefault -eq $Name)
                    {
                        Write-Verbose "Logger type (${Name}) was default. Resetting default logger."
                        $script:LoggerDefault = $null
                    }

                    break
                }
            }
        }

        Get-LoggerType $Name
    }
}

<#
#>

Function Remove-LoggerType
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipelineByPropertyName)]
        [string]$Name = [string]::Empty
    )

    process
    {
        if ([string]::IsNullOrEmpty($Name))
        {
            throw New-Object ArgumentException -ArgumentList "Null or empty Name passed to Remove-LoggerType"
        }
    
        if (!$script:LoggerTypes.ContainsKey($Name))
        {
            Write-Verbose "Logger type does not exist: ${Name}"
            return
        }

        Write-Verbose "Removing logger type: ${Name}"
        $null = $script:LoggerTypes.Remove($Name)
    
        if ($script:LoggerDefault -ne $null -and $script:LoggerDefault -eq $Name)
        {
            Write-Verbose "Logger type (${Name}) was default. Resetting default."
            $script:LoggerDefault = $null
        }
    }
}

<#
#>

Function Get-DefaultLoggerType
{
    [CmdletBinding()]
    param()

    if ([string]::IsNullOrEmpty($script:LoggerDefault))
    {
        Write-Verbose "No Logger default defined"
        return
    }

    if (!$script:LoggerTypes.Contains($script:LoggerDefault))
    {
        # Shouldn't happen. LoggerDefault contains name, which doesn't exist.
        # reset logger default and return as though there is no default.
        Write-Verbose "Default logger doesn't exist in collection. Resetting default logger."
        $script:LoggerDefault = $null
        return
    }

    $default = $script:LoggerDefault
    Write-Verbose "Logger default: ${default}"
    Get-LoggerType $script:LoggerDefault
}

<#
#>

Function Reset-DefaultLoggerType
{
    [CmdletBinding()]
    param()

    Write-Verbose "Resetting default logger type"
    $script:LoggerDefault = $null
}

<#
#>

Function Write-Logger
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]$Message,
        
        [Parameter(Mandatory=$false)]
        [string]$Name = [string]::Empty
    )

    # Determine LoggerType name to use
    $type = $null
    if ($PSBoundParameters.Keys -contains "Type")
    {
        if ([string]::IsNullOrEmpty($Name) -or !$script:LoggerTypes.Contains($Name))
        {
            throw New-Object ArgumentException -ArgumentList "Logger type specified (${Name}) does not exist"
        }

        $type = $script:LoggerTypes[$Name]
    }

    if ($type -eq $null -and ![string]::IsNullOrEmpty($script:LoggerDefault) -and $script:LoggerTypes.Contains($script:LoggerDefault))
    {
        $type = $script:LoggerTypes[$script:LoggerDefault]
    }

    $color = $null
    $prefix = $null
    $formatBlock = $null
    $outputBlock = $null
    $writeHost = $true
    $writeOutput = $false
    $logPath = [string]::Empty

    if ($type -ne $null)
    {
        $prefix = $type.Prefix
        $color = $type.Color
        $formatBlock = $type.FormatBlock
        $outputBlock = $type.OutputBlock
        $writeHost = $type.WriteHost
        $writeOutput = $type.WriteOutput
        $logPath = $type.LogPath
    }

    # Default to Grey for colour
    if ($color -eq $null)
    {
        $color = [System.ConsoleColor]::Gray
    }

    $msg = $Message

    # Use format block to pre-process message, if defined
    if ($formatBlock -ne $null)
    {
        $msg = ($msg | ForEach-Object $formatBlock).ToString()
    }

    # Add prefix to message
    if (![string]::IsNullOrEmpty($prefix))
    {
        $msg = [string]::Format("{0}{1}", $prefix, $msg)
    }

    # Write host output, if required
    if ($writeHost)
    {
        Write-Host -ForegroundColor $color $msg
    }

    # Write object output, if required
    if ($writeOutput)
    {
        Write-Output $msg
    }

    # Write to file, if defined
    if (![string]::IsNullOrEmpty($logPath))
    {
        $msg | Out-File -Append -FilePath $logPath -Encoding UTF8
    }

    # Send message to script block, if defined
    if ($outputBlock -ne $null)
    {
        $null = $msg | ForEach-Object $outputBlock
    }
}