Out-Default Considered Harmful!

TL;DR: Don’t use Out-Default within a PowerShell cmdlet/function, unless you REALLY need to go to the console, otherwise use Write-Output.

Working with a client trying to narrow down a very quirky, but potentially damaging issue with Windows Update.

After spending several hours on the issue, we realized that we really didn’t have enough data, and it was suggested we programmatically search the WindowsUpdate.log, on a subset of machines, to search the presence of a specific string. If we find the string, then the machine is marked for further investigation.

New Log file format

For whatever reason, back in 2015, Microsoft decided to change the WindowsUpdate.log file format to a new format using the Event Tracing for Windows system.

See the blog here (the comments at the end of the blog are not kind).

The new system uses the Event Tracing for Windows system, and requires a convoluted Set of steps necessary to decode the data and write to a log file. Took me about an hour just to determine what the steps were to construct the command line arguments to extract a single *.etl file. In addition you must also connect to the Microsoft Symbol Servers to decode the data.

Thankfully Microsoft has included a PowerShell module and cmdlet to perform the operations… or so I thought…

WindowsUpdate PowerShell Module

Included in Windows 10 is a PowerShell module called WindowsUpdate. It’s not really that complex, the script is included, and you can see what it does:

C:\Windows\system32\WindowsPowerShell\v1.0\Modules\WindowsUpdate\WindowsUpdateLog.psm1

The cmdlet get-WindowsUpdateLog really just parses the c:\windows\Logs\WindowsUpdate\*.etl files and places all the information in a single log file, on the desktop by default. 

Honestly, I didn’t like the way the module connected to the Microsoft Symbol Servers, so I spent a while trying to figure out how to work around that, unfortunately the TraceRPT.exe tool couldn’t parse the file without the Symbols, and it frustrated me for other reasons. So I decided to use the PowerShell module as-is.

We wrote a PowerShell script and tried it out, but I noticed that the get-WindowsUpdateLog cmdlet was writing a lot data to the Console. I tried piping the output to null:

get-WindowsUpdateLog | out-null

But it didn’t work. A quick scan of the script source revealed that the author elected to write all output to Out-Default. Not Write-Host, not Write-Output, not Write-Verbose. To Out-Default

Why is that a problem?

Out-Default

Turns out that Out-Default is just a default handler for host output, not pipeline output. In the case of get-WindowsUpdate, it was just acting as a default wrapper around write-host. The background of why you would *NOT* want output from a cmdlet or script to go to the console, please Jeff Snover’s blog post on the matter: https://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/

That’s fine if we KNOW that we want the output to go to the console, but what if we want the output from a cmdlet to go to the pipeline? Well in that case get-WindowsUpdate is forcing output to the console no matter what. 

During a code review, I would have recommended using Write-Output instead, that would have redirected all output to the pipeline, allowing the out-null hack above to work.

SCCM Configuration Items and console output

The challenge is that if we elected to place this compliance script into a System Center Configuration Manager – Configuration Item script, it could lead to some undefined results.

For what ever reason, the SCCM team decided to key a PowerShell script’s success based on the console output. If it passes, the script would have called:

Write-Host "Compliant"

and have the Configuration Item search for the output “Compliant”. This is a case where we *KNOW* we want the script to write to the console. But we can’t have anything else in the script write to the console. Nothing! Otherwise it would be marked as a failure.

Personally, I would have also designed Configuration Item’s to measure pass/fail based on the process exit code directly.

The Hack

OK, Super! We have a PowerShell script that insists on writing output to the console, and a controller that get’s confused by non-deterministic console output. Sigh…

Time to write a hack. I developed a solution, and afterwards came across the same answer posted to StackExchange/SuperUser.com, so I’ll include that here.

https://superuser.com/questions/1058117/powershell-v5-suppress-out-default-output-in-nested-functions

Essentially the goal is to remove or replace the out-default cmdlet with our own function, PowerShell allows this action, I don’t usually recommend doing that, but it works in this case.

The Code


<#
.SYNOPSIS
Search WindowsUpdate Logs
.DESCRIPTION
Searches the Windows Update Log for a string
.NOTES
Ready to be used within a
Copyright Keith Garner, All rights reserved.
.LINK
https://keithga.wordpress.com/2018/04/03/out-default-considered-harmful
#>
[cmdletbinding()]
param(
[parameter(Mandatory=$true)]
$SearchString,
$CleanWU,
$ETLPath = "$env:WinDir\Logs\WindowsUpdate"
)
$WULog = New-TemporaryFile
# Hack Hack to work arround Windows Update SCCM Config Item interop issue.
if ( -not ( test-path alias:out-default ) ) { new-alias Out-Default Write-Verbose -Scope global }
Get-WindowsUpdateLog -LogPath $WULog -ETLPath $ETLPath
remove-item alias:Out-Default -force -EA SilentlyContinue
$WUResults = Select-String -path $WULog -Pattern $SearchString -AllMatches
if ( $WUResults ) {
write-host "Not Compliant $($WUResults.Count) $env:computerName"
$wuresults | out-string -Width 200 | write-verbose
}
else {
write-host "Compliant"
}
if ( $CleanWU ) {
write-verbose "Cleanup"
remove-item -Recurse -Force -Path $env:temp\WindowsUpdateLog
}

-k

 

 

Leave a comment