PowerShell has a built-in feature called Set-PSDebug -Trace 1
that provides similar functionality to batch file's ECHO ON. When enabled, it will display each command before execution:
Set-PSDebug -Trace 1
Get-ChildItem
Remove-Item temp.txt
Set-PSDebug -Trace 0
For more detailed logging including both commands and output, use the Start-Transcript
cmdlet:
Start-Transcript -Path "C:\logs\script_log_$(Get-Date -Format 'yyyyMMdd').txt"
# Your commands here
Get-Process | Where-Object { $_.CPU -gt 100 }
Stop-Transcript
For more control over command echoing, create a custom function:
function Invoke-EchoCommand {
param(
[scriptblock]$ScriptBlock,
[switch]$ShowCommand
)
if ($ShowCommand) {
Write-Host "PS> $($ScriptBlock.ToString())" -ForegroundColor Cyan
}
& $ScriptBlock
}
# Usage example:
Invoke-EchoCommand -ScriptBlock { Get-Service | Where-Object Status -eq 'Running' } -ShowCommand
For complex scripts, you can implement AST (Abstract Syntax Tree) parsing to echo commands:
function Trace-ScriptBlock {
param(
[scriptblock]$ScriptBlock
)
$ast = $ScriptBlock.Ast
$commands = $ast.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
foreach ($cmd in $commands) {
Write-Host "Executing: $($cmd.Extent.Text)" -ForegroundColor Yellow
Invoke-Expression $cmd.Extent.Text
}
}
# Usage:
Trace-ScriptBlock {
Get-ChildItem
Get-Process | Sort-Object CPU -Descending | Select-Object -First 5
}
In PowerShell 7, you can use the PSHost
trace source for more granular control:
$tracePS = [System.Management.Automation.TraceSource]::New("PSHost")
$tracePS.Listeners.Add([System.Management.Automation.ConsoleTraceListener]::new())
$tracePS.Switch = [System.Diagnostics.SourceLevels]::All
# This will now trace command execution
Get-ChildItem
For production scripts, consider combining multiple approaches:
# Start transcript
$logPath = "C:\logs\script_execution_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
Start-Transcript -Path $logPath
# Enable tracing
Set-PSDebug -Trace 1
try {
# Main script execution
Get-ChildItem -Path $env:USERPROFILE\Documents -File
$services = Get-Service | Where-Object Status -eq 'Running'
$services | Export-Csv -Path "running_services.csv" -NoTypeInformation
}
finally {
# Clean up
Set-PSDebug -Trace 0
Stop-Transcript
}
When transitioning from batch scripting to PowerShell, many developers miss the simple echo on
functionality that displays commands as they execute. This visibility is crucial for:
- Debugging complex scripts
- Creating self-documenting automation
- Training purposes when sharing scripts
- Audit trails in production environments
PowerShell offers several built-in ways to achieve command echoing:
1. Using Set-PSDebug
# Enable command tracing
Set-PSDebug -Trace 1
# Your script commands here
Get-Process | Where-Object { $_.CPU -gt 100 }
# Disable tracing when done
Set-PSDebug -Trace 0
2. The Transcript Feature
# Start recording session
Start-Transcript -Path "C:\scripts\log.txt" -IncludeInvocationHeader
# Commands will show with timestamps
New-Item -ItemType Directory -Path "C:\temp\test"
Stop-Service -Name "Spooler"
# End recording
Stop-Transcript
For more control over output formatting:
Wrapper Function Approach
function Invoke-EchoCommand {
param (
[scriptblock]$ScriptBlock
)
Write-Host -ForegroundColor Cyan "PS > $($ScriptBlock.ToString())"
& $ScriptBlock
}
# Usage example:
Invoke-EchoCommand { Get-ChildItem C:\Windows -Recurse -Depth 2 }
PowerShell 7+ Enhanced Method
# Requires PS 7+
$PSNativeCommandUseErrorActionPreference = $true
$script:MyInvocation.MyCommand.ScriptContents | ForEach-Object {
Write-Host "Executing: $_"
Invoke-Expression $_
}
- Combine command echoing with proper error handling using
try/catch
- Use different colors for commands vs output with
Write-Host -ForegroundColor
- For production scripts, consider logging to both console and file
- Be mindful of sensitive information when echoing commands
While command echoing is valuable, it does add overhead. In performance-critical scripts:
- Only enable echoing during development/debugging
- Use conditional logic based on a
$Debug
parameter - Consider using the lighter-weight
Set-PSDebug
approach