PowerShell / PowerShell

PowerShell for every system!
https://microsoft.com/PowerShell
MIT License
44.8k stars 7.25k forks source link

Add the ability to redirect the same stream to multiple files #21381

Open amoorcroft-nortech opened 6 months ago

amoorcroft-nortech commented 6 months ago

Summary of the new feature / enhancement

We'd like the ability to redirect the same stream to multiple files, splitting warnings, errors, while still sending all streams * (inc. warning and error stream) to a combined file.

The background behind the question is a PS build script we'd like to capture output for (which writes to several streams). We'd like to turn the captured output into several files, with everything.log containing all possible output to provide context for build errors/warnings, while the separate error.log and warning.log would provide a quick reference for emerging issues.

Some examples of things we've tried: .\Script.ps1 2> "warnings.log" 3> "errors.log" *> "everything.log" .\Script.ps1 *>&1 | Tee-Object "everything.log" 2> "error.log" 3> "warning.log"

I couldn't find any existing questions/documentation on this, hence I'm raising as a feature request.

Thanks in advance!

Proposed technical implementation details (optional)

No response

rhubarb-geek-nz commented 6 months ago

No need for new PowerShell feature, the functionality is there if you know how to use it

I have just written a 'Super Tee-Object', Invoke-PipelineBroadcast which will send the contents of a single pipeline to multiple parallel script blocks.

Basically, write a script block for each output you want to capture. the Invoke-PipelineBroadcast will write to each script using a steppable pipeline for each.

Example code

$logRules = (
        ( 'stdout.log', { $True } ),
        ( 'stderr.log', { $_ -is [System.Management.Automation.ErrorRecord] } ),
        ( 'information.log', { $_ -is [System.Management.Automation.InformationRecord] } ),
        ( 'warning.log', { $_ -is [System.Management.Automation.WarningRecord] } ),
        ( 'debug.log', { $_ -is [System.Management.Automation.DebugRecord] } ),
        ( 'verbose.log', { $_ -is [System.Management.Automation.VerboseRecord] } )
)

Invoke-Command -ScriptBlock {
        $VerbosePreference = 'Continue'
        $DebugPreference = 'Continue'
        Write-Output 'write to the output'
        Write-Error 'write to error' 2>&1
        Write-Information 'write information'
        Write-Debug 'write debug'
        Write-Verbose 'write verbose information to the pipeline with a unique record type in order to identify it and filter'
        Write-Warning 'this is your final warning'
} *>&1 | Invoke-PipelineBroadcast -ScriptBlock ((,{
        param([string]$file,[ScriptBlock]$rule)
        Where-Object -FilterScript $rule > $file
})*$logRules.Count) -ArgumentList $logRules

The pipelines will all run until the original pipeline is closed. I have put that in the PSGallery

rhubarb-geek-nz commented 6 months ago

I have a question regarding the error output....

In the above example, if I remove the 2>&1 from the Write-Error line then the error output disappears completely.

Where does it go?

The *>&1 for the Invoke-Command does not seem to capture it to send to Invoke-PipelineBroadcast, otherwise it would appear in the stdout.log which shows everything.

If I put the 2>error.log on the Invoke-Command line then the Write-Error output does appear in error.log.

But I can't get the error stream to go into the success pipeline unless the "2>&1" was on Write-Error itself.

Raised #21396

rhubarb-geek-nz commented 6 months ago

Simpler test example with all streams following from issue with Invoke-Command redirection

$logRules = (
        ( 'all.log', { $True } ),
        ( 'information.log', { $_ -is [System.Management.Automation.InformationRecord] } ),
        ( 'warning.log', { $_ -is [System.Management.Automation.WarningRecord] } ),
        ( 'debug.log', { $_ -is [System.Management.Automation.DebugRecord] } ),
        ( 'error.log', { $_ -is [System.Management.Automation.ErrorRecord] } ),
        ( 'verbose.log', { $_ -is [System.Management.Automation.VerboseRecord] } )
)

& {
        $VerbosePreference = 'Continue'
        $DebugPreference = 'Continue'
        Write-Output 'write to the output'
        Write-Error 'write to error'
        Write-Information 'write information'
        Write-Debug 'write debug'
        Write-Verbose 'write verbose information to the pipeline with a unique record type in order to identify it and filter'
        Write-Warning 'this is your final warning'
} *>&1 | Invoke-PipelineBroadcast -ScriptBlock ((,{
        param([string]$file,[ScriptBlock]$rule)
        Where-Object -FilterScript $rule > $file
})*$logRules.Count) -ArgumentList $logRules