10

My question may seem duplicate of PowerShell "echo on", but it is not.

I am not interested in capturing the command output, but in the command line itself of every command executed by the script, including the native commands.

This is what "echo on" in cmd does and this is what I am looking for. Set-PSDebug -Trace 1 does not do it and neither passing the -Verbose flag.

So far I have not see a way except outputing them myself, which is a huge pain in itself.

So, can Powershell do what "echo on" does in cmd?

EDIT 1

Not ideal, but I would accept an answer suggesting to use a wrapper function which would receive a command (native or powershell) with parameters and run the command while faithfully logging the respective command line. Of course, the wrapper function code should be part of the answer.

EDIT 2

The following trivial example demonstrates why Set-PSDebug -Trace 1 does not do it:

tasklist `
    /fi "status eq running" | Select-Object -First 4

Please, observe:

C:\> cat C:\temp\1.ps1
tasklist `
    /fi "status eq running" | Select-Object -First 4
C:\> Set-PSDebug -Trace 1
C:\> C:\temp\1.ps1
DEBUG:    1+  >>>> C:\temp\1.ps1
DEBUG:    1+  >>>> tasklist `

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
csrss.exe                      756 Console                    1      2,816 K
C:\>

EDIT 3

For comparison, observe an equivalent script in cmd with echo on:

C:\>type c:\temp\1.cmd
@echo on
tasklist ^
    /fi "status eq running" |findstr/n ^^|findstr "^[1-4]:"
C:\>c:\temp\1.cmd

C:\>tasklist     /fi "status eq running"   | findstr/n ^  | findstr "^[1-4]:"
1:
2:Image Name                     PID Session Name        Session#    Mem Usage
3:========================= ======== ================ =========== ============
4:csrss.exe                      756 Console                    1      2,328 K

C:\>

EDIT 4

start-transcript does not do it either:

C:\WINDOWS\system32> cat c:\temp\1.ps1
tasklist `
    /fi "status eq running" | Select-Object -First 4 | Out-Default
C:\WINDOWS\system32> Start-Transcript
Transcript started, output file is ~\Documents\PowerShell_transcript.L-PF0TBKV7.Sr1ntThx.20190611143800.txt
C:\WINDOWS\system32> c:\temp\1.ps1

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
csrss.exe                      756 Console                    1      2,936 K
C:\WINDOWS\system32> Stop-Transcript
Transcript stopped, output file is ~\Documents\PowerShell_transcript.L-PF0TBKV7.Sr1ntThx.20190611143800.txt
C:\WINDOWS\system32> cat ~\Documents\PowerShell_transcript.L-PF0TBKV7.Sr1ntThx.20190611143800.txt
**********************
Windows PowerShell transcript start
Start time: 20190611143800
Username: xyz\me
RunAs User: xyz\me
Configuration Name:
Machine: L-PF0TBKV7 (Microsoft Windows NT 10.0.16299.0)
Host Application: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Process ID: 25508
PSVersion: 5.1.16299.1004
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.16299.1004
BuildVersion: 10.0.16299.1004
CLRVersion: 4.0.30319.42000
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
**********************
Transcript started, output file is ~\Documents\PowerShell_transcript.L-PF0TBKV7.Sr1ntThx.20190611143800.txt
C:\WINDOWS\system32
>
PS>c:\temp\1.ps1

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
csrss.exe                      756 Console                    1      2,936 K
C:\WINDOWS\system32
>
PS>Stop-Transcript
**********************
Windows PowerShell transcript end
End time: 20190611143810
**********************
C:\WINDOWS\system32>

As you can see it does not contain the command line.

mark
  • 59,016
  • 79
  • 296
  • 580
  • Can you give an example script where `Set-PSDebug -Trace 1` or even `-Trace 2` is not sufficient? and/or an example of what functionality `echo on` can do that PowerShell can't replicate or an example of what you are trying to get out? – HAL9256 Jun 11 '19 at 16:52
  • Done - see **EDIT 2** – mark Jun 11 '19 at 17:14
  • Take a look at 'Start-Transscript', maybe that's what you are looking for – D.J. Jun 11 '19 at 18:17
  • Nope - see **EDIT 4** – mark Jun 11 '19 at 18:41
  • It looks like `Set-PSDebug` only returns one line of execution at a time. i.e. if you remove the backtick, and execute everything on the same line, it will list the full line. But, @mark does point out the deficiency that `Set-PSDebug` has is that it will only output the first line, on command completion, that actually causes execution. It won't list the full line-by-line execution of things, including things that "don't matter" to the execution i.e. close brackets `}` and comments don't show up. – HAL9256 Jun 11 '19 at 20:37
  • 1
    That is not a deficiency. That is a problem. Carrying over long lines is a legitimate technique that does not fool "echo on". Hence the title of the question - is there "echo on" alternative in Powershell? – mark Jun 11 '19 at 21:34
  • 1
    The short answer is no. – Bill_Stewart Jun 11 '19 at 22:02

3 Answers3

3

Firstly, the reason you're dissatisfied with the built-in options is because you're going against the grain; your requirement is like asking how to put sacks of gravel in the back of a Porsche. Powershell comes with Verbose and Debug output streams and a fantastic debugger.

If you have any ability to influence coding standards, look at splatting as an alternative to backtick-continuations.

If you can count on versions of Windows that are not years past EoL, consider Get-ScheduledTask | Where-Object State -eq 'Ready' instead of tasklist.

That said, yes, what you want is possible. Here's a script that will echo across line continuations:

# Echo.ps1

function Disable-Echo
{
    param
    (
        [Parameter(Mandatory)]
        [string]$Path
    )

    $Path = ($Path | Resolve-Path -ErrorAction Stop).Path

    Get-PSBreakpoint -Script $Path | Remove-PSBreakpoint
}

function Enable-Echo
{
    param
    (
        [Parameter(Mandatory)]
        [string]$Path
    )

    $Path = ($Path | Resolve-Path -ErrorAction Stop).Path

    Disable-Echo $Path

    $Ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$null, [ref]$null)
    $Statements = $Ast.BeginBlock, $Ast.ProcessBlock, $Ast.EndBlock |
        Select-Object -ExpandProperty Statements |
        Write-Output |
        Where-Object {$_.Extent}

    foreach ($Statement in $Statements)
    {
        $Action = {
            $Text = $Statement.Extent.Text
            $Text = $Text -replace '`\r?\n' # Concatenate lines that are escaped with backtick
            # Alternative to remove superfluous whitespace:
            # $Text = $Text -replace '\s+`\r?\n\s*', ' '
            Write-Host "ECHO: $Text" -ForegroundColor Yellow
            continue   # or 'break' to stop on the line
        }.GetNewClosure()   # Create a closure, to capture the value of $Statement

        $Params = @{
            Script = $Path
            Line   = $Statement.Extent.StartLineNumber
            Column = $Statement.Extent.StartColumnNumber
            Action = $Action
        }
        $null = Set-PSBreakpoint @Params
    }
}

Sample script:

# foo.ps1

gci `
    -Name `
    -File `
    -Filter Victor.*

gci -File -Name *.md; gci -File -Name *.psd1

Usage:

# Without echo
❯ .\foo.ps1
Victor.build.ps1
Victor.psd1
Victor.psm1
README.md
Victor.psd1

❯ . .\Echo.ps1
❯ Enable-Echo .\foo.ps1
❯ .\foo.ps1
ECHO: gci     -Name     -File     -Filter Victor.*
Victor.build.ps1
Victor.psd1
Victor.psm1
ECHO: gci -File -Name *.md
README.md
ECHO: gci -File -Name *.psd1
Victor.psd1

Tested on PSv5 and PSv7. Should work on PSv2, although the sample foo.ps1 is PSv3+ (IIRC).

This will not echo calls to other scripts. For that, you'd probably want to do more AST inspection, identify CommandAsts that call scripts, and recursively enable echo on those scripts too. Alternatively, there might be joy in Set-PSBreakpoint -Variable - the $$ variable might be a good place to start - but this would likely be a PITA to work with as it would invoke while you're trying to debug the echo function. You could inspect Get-PSCallStack to skip the action while you're at the command prompt.

FSCKur
  • 920
  • 7
  • 16
  • Output is not for debugging. The code runs in a build pipeline, I just want to see the commands being executed. I have similar questions for linux - https://stackoverflow.com/questions/75091531/what-is-the-idiomatic-bash-way-to-echo-the-executed-commands-which-include-pipes and https://stackoverflow.com/questions/74695700/how-to-conveniently-echo-commands-run-from-bash-script-with-a-custom-prefix-when – mark Feb 01 '23 at 21:49
  • Understandable. I did try to solve the requirement you stated - does it work for you? – FSCKur Feb 02 '23 at 09:46
  • I like the idea, I may use it. – mark Feb 02 '23 at 12:55
0

I expect four answers and you have already mentioned three that do not work for you (Set-PSDebug, Start-Transaction, -Verbose). As much as they may be viable but not in the format you are looking for, I will not talk more of them.

For the third option, try using Get-History. Now, this will not print out each command as you execute it (only when you call it) like I assume you want. It will also likely not print out each of the lines inside another script (you would want a trace but you did not like that because it prints out more than just the execution).

You can try asking around the PowerShell repository but I do not expect you to find what you are seeking.

carrvo
  • 511
  • 5
  • 11
-1

If Event logs is an option, start tracing by enabling this Group Policy.
Administrative Templates -> Windows Components -> Windows PowerShell

See Microsoft Docs - Script Tracing and Logging

Then you would of course need to parse the Event logs accordingly...

Dennis
  • 871
  • 9
  • 29