Functions/Start-ScriptLogger.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
<#
    .SYNOPSIS
        Start the script logger in the current PowerShell session.
 
    .DESCRIPTION
        Start the script logger in the current PowerShell session. By starting
        the logger, a global log configuration for the current PowerShell
        session will be set. This configuration is customizable with the
        available paramters.
        With the format parameter, the logfile format can be defined. The format
        definition will be used to call the System.String.Format() method. The
        following values are used as arguments:
        - {0} Timestamp as datetime value.
        - {1} NetBIOS computer name.
        - {2} Current session username.
        - {3} Log entry level.
        - {4} Message.
 
    .INPUTS
        None.
 
    .OUTPUTS
        ScriptLogger.Configuration. Configuration of the script logger instance.
 
    .EXAMPLE
        PS C:\> Start-ScriptLogger
        Initialize the logger with default values.
 
    .EXAMPLE
        PS C:\> Start-ScriptLogger -Name 'MyLogger' -Path 'C:\my.log'
        Start a custom named logger instance.
 
    .EXAMPLE
        PS C:\> Start-ScriptLogger -Path 'C:\test.log' -Format '{3}: {4}' -Level 'Verbose' -SkipEventLog -HideConsoleOutput
        Log all message with verbose level or higher to the log file but skip
        the event log and the consule output. In addition, use a custom format
        for the log file content.
 
    .NOTES
        Author : Claudio Spizzi
        License : MIT License
 
    .LINK
        https://github.com/claudiospizzi/ScriptLogger
#>

function Start-ScriptLogger
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        # The logger name.
        [Parameter(Mandatory = $false)]
        [System.String]
        $Name = 'Default',

        # The path to the log file.
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [System.String]
        $Path,

        # This parameter defines, how the log output will be formated.
        [Parameter(Mandatory = $false)]
        [ValidateScript({$_ -f (Get-Date), $Env:ComputerName, $Env:Username, 'Verbose', 'Message'})]
        [System.String]
        $Format = '{0:yyyy-MM-dd} {0:HH:mm:ss} {1} {2} {3,-11} {4}',

        # The event log level. All log messages equal to or higher to the level
        # will be logged. This is the level order: Verbose, Information, Warning
        # and Error.
        [Parameter(Mandatory = $false)]
        [ValidateSet('Verbose', 'Information', 'Warning', 'Error')]
        [System.String]
        $Level = 'Verbose',

        # Define the encoding which is used to write the log file. The possible
        # options are the same as on the used Out-File cmdlet.
        [Parameter(Mandatory = $false)]
        [ValidateSet('Unicode', 'UTF7', 'UTF8', 'UTF32', 'ASCII', 'BigEndianUnicode', 'Default', 'OEM')]
        [System.String]
        $Encoding = 'UTF8',

        # Allow the auto log rotation. The period is attached to the log file.
        [Parameter(Mandatory = $false)]
        [ValidateSet('None', 'Hourly', 'Daily', 'Monthly', 'Yearly')]
        [System.String]
        $Rotation = 'None',

        # Do not write the log messages into the log file. By default, all
        # messages are written to the specified or default log file.
        [Parameter(Mandatory = $false)]
        [Switch]
        $NoLogFile,

        # Skip the event log output. By default, all log messages will be
        # written into the "Windows PowerShell" event log.
        [Parameter(Mandatory = $false)]
        [Switch]
        $NoEventLog,

        # Hide the PowerShell console output. By default, all log messages are
        # shown on the console.
        [Parameter(Mandatory = $false)]
        [Switch]
        $NoConsoleOutput,

        # If specified, the created script logger object will be returned.
        [Parameter(Mandatory = $false)]
        [Switch]
        $PassThru
    )

    # If the Path parameter was not specified, add a default value. If possible,
    # use the last script called this function. Else use the temp path.
    if (-not $PSBoundParameters.ContainsKey('Path') -or [System.String]::IsNullOrEmpty($Path))
    {
        $lastScriptPath = Get-PSCallStack | Select-Object -Skip 1 -First 1 -ExpandProperty 'ScriptName'

        if (-not [System.String]::IsNullOrEmpty($lastScriptPath))
        {
            $Path = $lastScriptPath + '.log'
        }
        else
        {
            $Path = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'PowerShell.log'
        }
    }

    # If log rotation is enable, add the current period to the path
    switch ($Rotation)
    {
        'Hourly'
        {
            $Path = $Path.Insert($Path.LastIndexOf('.'), [System.String]::Format('.{0:yyyyMMddHH}', (Get-Date)))
        }
        'Daily'
        {
            $Path = $Path.Insert($Path.LastIndexOf('.'), [System.String]::Format('.{0:yyyyMMdd}', (Get-Date)))
        }
        'Monthly'
        {
            $Path = $Path.Insert($Path.LastIndexOf('.'), [System.String]::Format('.{0:yyyyMM}', (Get-Date)))
        }
        'Yearly'
        {
            $Path = $Path.Insert($Path.LastIndexOf('.'), [System.String]::Format('.{0:yyyy}', (Get-Date)))
        }
    }

    # Create the log file folder, if it does not exist
    $parent = Split-Path -Path $Path -Parent
    if (-not (Test-Path -Path $parent))
    {
        try
        {
            New-Item -Path $parent -ItemType 'Directory' -ErrorAction 'Stop' | Out-Null
        }
        catch
        {
            throw "ScriptLogger failed to create the log folder: $parent"
        }
    }

    # Create an empty log file, if it does not exist
    if (-not (Test-Path -Path $Path))
    {
        try
        {
            New-Item -Path $Path -ItemType 'File' -ErrorAction 'Stop' | Out-Null
        }
        catch
        {
            throw "ScriptLogger failed to create the log file: $Path"
        }
    }

    # Only work with absolute path, makes error handling easier
    $Path = (Resolve-Path -Path $Path).Path

    if ($PSCmdlet.ShouldProcess('ScriptLogger', 'Start'))
    {
        Write-Verbose "Start script logger '$Name'"

        $Script:Loggers[$Name] = [PSCustomObject] @{
            PSTypeName    = 'ScriptLogger.Configuration'
            Name          = $Name
            Enabled       = $true
            Path          = $Path
            Format        = $Format
            Level         = $Level
            Encoding      = $Encoding
            Rotation      = $Rotation
            LogFile       = -not $NoLogFile.IsPresent
            EventLog      = -not $NoEventLog.IsPresent
            ConsoleOutput = -not $NoConsoleOutput.IsPresent
        }

        # Return logger object
        if ($PassThru.IsPresent)
        {
            Write-Output $Script:Loggers[$Name]
        }
    }
}