4
\$\begingroup\$

Today I have dealt with logging in PowerShell as well as with the different streams and the pipeline. Unfortunately, none of these solutions really met my needs.

My requirements are:

  • I need to output information from PowerShell to a file log.
  • The file log has a predefined structure. So it's not enough to just redirect all streams to the file.
  • I want to use mainly standard PowerShell functions such as Write-Error, Write-Warning, Write-Verbose.
  • The overhead in the code through logging should be minimal.

I have developed the following idea now:

  1. When calling a function from my script, all streams are piped to a logging function.
  2. This function separates the debug, verbose, warning and error objects from the resulting object.
  3. The resulting object is released back into the pipeline.

Here is my solution:

function Split-Streams {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)]
        $InputStream
    )
    process{
        switch($InputStream.GetType())
        {
            'System.Management.Automation.DebugRecord' { 
                                                         # Do whatever you want, like formatting an writing to a file. 
                                                         Write-Host $InputStream -ForegroundColor Gray
                                                       }
            'System.Management.Automation.ErrorRecord' {
                                                         Write-Host $InputStream -ForegroundColor Red
                                                         Write-Host ('Error function: {0}' -f $InputStream[0].InvocationInfo.MyCommand.Name) -ForegroundColor Red
                                                       }
            'System.Management.Automation.VerboseRecord' { Write-Host $InputStream -ForegroundColor Cyan }
            'System.Management.Automation.WarningRecord' { Write-Host $InputStream -ForegroundColor Yellow }
            default { return $InputStream }
        }
    }
}

function Write-Messages
{
    [CmdletBinding()]
    param()   
    Write-Debug "Debug message"
    Write-Output "Output message"
    Write-Verbose "Verbose message"
    Write-Warning "Warning message"
    Write-Error "Error message"
}

$Test2 = Write-Messages -Verbose -Debug *>&1 | Split-Streams

Write-Host $Test2 -ForegroundColor White

So now my question:

  • Is there something wrong with my solution?
  • Have I missed any problems?
\$\endgroup\$
2
  • \$\begingroup\$ Well, nothing will ever end up in a file - Write-Host writes directly to the host screen buffer \$\endgroup\$ Commented Jul 18, 2019 at 13:09
  • \$\begingroup\$ This is just an example. To show the possibilies of the consturct. you can redirect from there to whatever function you like. ie. "$InputStream | Add-Content -Path $LogFilePath -Encoding ASCII" \$\endgroup\$
    – kami
    Commented Jul 18, 2019 at 14:16

1 Answer 1

2
\$\begingroup\$
  • Is there something wrong with my solution? I don't think so.
  • Have I missed any problems?
    • Omitted Information stream, cf How to process Write-Information pipeline output when using SilentlyContinue
    • The -Debug parameter overrides the value of the $DebugPreference variable for the current command, setting the value of $DebugPreference to Inquire. Prompts to continue for every Write-Debug statement which may be considered inconvenient (or even harmful) in batch processing.

Here's my solution (partially commented script):

[CmdletBinding()]
param(
   # [switch]$debug   # alternative to [CmdletBinding()]
)

function Split-Streams {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ValueFromPipeline)]
        [ValidateNotNull()]   # avoid crashing . property dereference operator
        $InputStream,
        [switch]$revert
    )
    process {
        if ( -not $revert.IsPresent ) {
            # return an object which contains type and value of $InputStream
            [PSCustomObject] @{
                stream = $InputStream.GetType().FullName
                value  = $InputStream
            }
        }
        else {
          # basic input object validity check
          if ( ($InputStream.psobject.Properties.Name -join ',') -match 
                    "\bstream\b.*\bvalue\b|\bvalue\b.*\bstream\b" ) {
            # review split streams and handle them individually
            switch($InputStream.stream)
            {
                'System.Management.Automation.DebugRecord' { 
                    # Do whatever you want, like formatting an writing to a file. 
                    Write-Host ($InputStream.value) -ForegroundColor Gray
                }
                'System.Management.Automation.ErrorRecord' {
                    Write-Host ($InputStream.value) -ForegroundColor Red
                    Write-Host ('Error function: {0}' -f ($InputStream.value).InvocationInfo.MyCommand.Name) -ForegroundColor DarkRed
                }
                'System.Management.Automation.VerboseRecord' { 
                    Write-Host ($InputStream.value) -ForegroundColor Cyan 
                }
                'System.Management.Automation.WarningRecord' { 
                    Write-Host ($InputStream.value) -ForegroundColor Yellow 
                }
                'System.Management.Automation.InformationRecord' { 
                    Write-Host ($InputStream.value) -ForegroundColor Green
                }
                default { 
                    Write-Host "output type: $($InputStream.stream)" -ForegroundColor Blue
                    # keep original output stream unchanged
                    $InputStream.value
                }
            }
          }
        }
    }
}

function Write-Messages
{
    [CmdletBinding()]
    param()
    Write-Debug       "Debug message $DebugPreference"
    Write-Verbose     "Verbose message $VerbosePreference"
    Write-Warning     "Warning message $WarningPreference"
    Write-Error       "Error message $ErrorActionPreference"
    Write-Information "Information message $InformationPreference"

    # Write-Output: it is generally not necessary to use the cmdlet.
                      'Output message'
    #               I'm checking a more complex object than plain string
    Write-Output       $Host
}

$DebugPreferenceSave = $DebugPreference     # backup $DebugPreference
If ($PSBoundParameters['Debug']) {
    # The Debug parameter overrides the value of the $DebugPreference
    #     variable for the current command, setting the value
    #     of $DebugPreference to Inquire.
    # The following setting suppresses asking whether you want to continue
    # even if examined `Write-Messages` is a third-party black box.
    $DebugPreference = 'Continue'
}

Write-Messages -Verbose *>&1 | Split-Streams | Split-Streams -revert

$DebugPreference = $DebugPreferenceSave     # restore $DebugPreference

Alternatively to the (sample) usage in the one pipeline:

Write-Messages -Verbose *>&1 | Split-Streams | Split-Streams -revert

(cf above code) you can call it as follows:

$test3 = Write-Messages -Verbose *>&1 | Split-Streams
# preprocess the $test3 variable here (optional)
$test3 | Split-Streams -revert
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.