Conditionally Redirecting Output from Inside a Powershell Script File
If you look at documentation and examples about output redirection in Powershell, you'll often find code showing you how to redirect e.g. Write-Warning
on the command line level like shown here:
.\script.ps1 3> warnings.log
Usually, it's best practice to let the caller of a script decide how to apply output redirection. But there might be cases where letting a script itself decide might have some appeal. One example: Some of my scripts are not only used interactively but also being run as scheduled tasks. To be able to check possible warnings from such a script run after its completion I wanted to redirect the output of Write-Warning
. The straightforward way to do this is to create a scheduled task definition including the command line arguments necessary for output redirection:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File C:\scripts\get-allUserGroupMemberships.ps1 3> warnings.log
But this scheduled definition will redirect the warnings to the same file on every execution, thus overwriting the data from the last run. Overwriting could easily be avoided by using 3>> warnings.log
to append to the now cumulative log file. But what if I wanted to have a separate log file for every (e.g. daily) execution of this script?
Following the documentation above and looking at the more advanced examples it became clear to me that output redirection in Powershell is not limited to passing command line arguments to a script being executed. Instead, every single code block or function call can be augmented with output redirection parameters.
Now how to apply output redirection on the code block or function call level to the use case I intended it for? A simple implementation would be a script with a parameter $WarnRedirect
. If called without a value for this parameter (e.g. from a console session) the main body of the script would call its 'worker function' without redirection parameters. If there is a value for this parameter, output redirection will be applied:
[CmdletBinding()]
param(
[string] $WarnRedirect = ""
)
function Write-A-Warning {
[CmdletBinding()]
param()
Write-Warning "`nWarning message $(Get-Date)`n"
}
$cmd = "Write-A-Warning"
if ($WarnRedirect) {
$WarningLogFile = "Warnings-" + $(Get-Date -Format "yyyyMMdd-HHmm") + ".log"
$cmd += " 3>$WarningLogFile"
}
Invoke-Expression $cmd
If you want to avoid the use of Invoke-Expression
the last few script lines (its main code block) could be also written as follows:
if ($WarnRedirect) {
$WarningLogFile = "Warnings-" + $(Get-Date -Format "yyyyMMdd-HHmm") + ".log"
Write-A-Warning 3>$WarningLogFile
} else {
Write-A-Warning
}
Of course, there are other ways of achieving the same result of getting one warning file per scheduled script execution. One such implementation would be to create a parameterless 'wrapper script' and use this in the definition of the scheduled task. The wrapper script could then construct and execute a Powershell command line for the 'worker script' with the appropriate redirection arguments, including a date-dependent output file name. But my own tastes tend to try to avoid a proliferation of script files, thus opting for the approach presented here.