When working with command-line tools like dcdiag
in PowerShell, you often need to capture both standard output (stdout) and standard error (stderr) streams separately. While the Unix-style 2>$err
syntax might seem familiar, PowerShell handles stream redirection differently.
The try-catch approach works well for terminating errors in PowerShell, but many external commands write to stderr without throwing exceptions:
try {
$output = dcdiag
}
catch {
# This won't catch non-terminating stderr output
$errorMsg = $_.Exception.Message
}
PowerShell provides several robust ways to capture stderr:
Method 1: Using the ErrorVariable Common Parameter
$output = dcdiag -ErrorVariable errVar 2>&1
if ($errVar) {
Write-Warning "Errors detected: $($errVar -join ', ')"
}
Method 2: Redirecting All Streams with 2>&1
$allOutput = dcdiag 2>&1
$errors = $allOutput | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }
Method 3: Using Start-Process for Advanced Capture
$process = Start-Process -FilePath "dcdiag" -NoNewWindow -RedirectStandardError "stderr.txt" -Wait -PassThru
$errors = Get-Content "stderr.txt"
For dcdiag
which outputs structured test results, consider this comprehensive approach:
$diagResults = dcdiag 2>&1
$testFailures = $diagResults |
Where-Object { $_ -match 'test (failed|skipped)' } |
ForEach-Object {
[PSCustomObject]@{
Test = ($_ -split ' {2,}')[0]
Result = ($_ -split ' {2,}')[-1]
}
}
- Always check
$LASTEXITCODE
after running native commands - Combine stream redirection with exit code checking
- Consider using
-ErrorAction Stop
to convert non-terminating errors
Here's a complete example for monitoring DC health:
function Test-DCHealth {
$output = dcdiag /test:DNS /test:NetLogons /test:Advertising /test:FSMOCheck 2>&1
$errors = $output | Where-Object { $_ -is [ErrorRecord] -or $_ -match 'failed' }
if ($errors -or $LASTEXITCODE -ne 0) {
Send-MailMessage -To admin@domain.com -Subject "DC Health Alert" -Body ($errors -join "n")
return $false
}
return $true
}
When working with external commands like dcdiag in PowerShell, it's crucial to understand how error streams behave differently from standard output. PowerShell handles three main streams:
- Standard output (stdout) - Success output (1> or >)
- Standard error (stderr) - Error output (2>)
- Warning stream (3>)
Many admins coming from Unix/Linux backgrounds instinctively try to use the Bash-style redirection syntax:
$result = dcdiag 2>$errorVar
This doesn't work in PowerShell because the shell interprets the entire statement differently. The correct PowerShell approach requires understanding the command's behavior.
For PowerShell cmdlets and advanced functions that throw proper exceptions, try/catch works perfectly:
try {
$test = Get-ADComputer -Identity "NonexistentPC" -ErrorAction Stop
}
catch {
$errorDetails = $_.Exception.Message
Write-Host "Error caught: $errorDetails"
}
For external executables like dcdiag, we need a different approach:
$output = & dcdiag 2>&1 | Out-String
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
Write-Warning "dcdiag exited with code $exitCode"
# Further error processing here
}
To properly separate stdout and stderr from external commands:
$stdout = New-Object System.Collections.ArrayList
$stderr = New-Object System.Collections.ArrayList
$job = Start-Job -ScriptBlock {
& dcdiag
} | Wait-Job
$stdout.AddRange(($job.ChildJobs[0].Output))
$stderr.AddRange(($job.ChildJobs[0].Error))
if ($stderr.Count -gt 0) {
Write-Host "Errors detected:" -ForegroundColor Red
$stderr | ForEach-Object { Write-Host $_ -ForegroundColor Red }
}
Here's a complete solution for monitoring Active Directory health:
function Test-DcDiag {
$outputFile = "$env:TEMP\dcdiag_$(Get-Date -Format 'yyyyMMdd').log"
# Run dcdiag and capture all output
$result = & dcdiag /q 2>&1 | Out-String
# Check for failure patterns
if ($result -match "failed test|error") {
$result | Out-File $outputFile
Send-MailMessage -To "admin@domain.com" -Subject "DC Diag Alert" -Body "Errors detected. See attached log." -Attachments $outputFile
return $false
}
return $true
}