Reputation: 179
I want to test a powershell script I'm writing that contains a command with side-effects I don't want to actually occur.
In bash, I could write
> DO_OR_ECHO=$([ "$MY_DEBUG_FLAG" ] && echo 'echo' || echo)
> $DO_OR_ECHO my_dangerous_command args
but trying to do a similar construct in powershell doesn't seem to work, even if I hardcode it.
> $DO_OR_ECHO='echo'
> $DO_OR_ECHO my_dangerous_command args
Unexpected token 'my_dangerous_command' in expression or statement.
What am I doing wrong? Is there a better way to do it? I want a construct that will either execute a command, or simply print it (so that I can see what would have been executed), based on a boolean.
Upvotes: 3
Views: 220
Reputation: 437743
Use a function:
function doOrEcho() {
$cmdLine = $args
if ($MY_DEBUG_FLAG) { # echo
"$cmdLine"
} else { # execute
# Split into command (excecutable) and arguments
$cmd, $cmdArgs = $cmdLine
if ($cmdArgs) {
& $cmd $cmdArgs
} else {
& $cmd
}
}
}
Then invoke it as follows:
doOrEcho my_dangerous_command args
Limitations:
Only works with calls to external programs, not to PowerShell cmdlets or functions (because you cannot relay named arguments - e.g. -Path .
- that way).[1]
$cmdArgs
) to an external program as individual arguments is a PowerShell technique called splatting; see this answer for more information, which also notes the general caveat that an empty string cannot be passed as an argument as-is in PowerShell versions up to 7.2.x, wether passed directly or as part of an array used for splatting.As in your Bash solution, the echoed command will not reflect the original and/or necessary quoting for expanded arguments, so argument boundaries may be lost.
To demonstrate it (using a call to the ls
Unix utility in PowerShell Core):
# Default behavior: *execute* the command.
PS> doOrEcho ls -1 $HOME
# ... listing of files in $HOME folder, 1 item per line.
# With debug flag set: only *echo* the command, with arguments *expanded*
PS> $MY_DEBUG_FLAG = $true; doOrEcho ls -1 $HOME
ls -1 /home/jdoe # e.g.
Kory Gill points out that PowerShell has the -WhatIf
common parameter whose purpose is to preview operations without actually performing them.
However, -WhatIf
is not the right solution for the task at hand:
Only cmdlets and advanced functions can implement this parameter (based on functionality provided by PowerShell) - doing so requires quite a bit more effort than the simple function above.
The intent behind -WhatIf
is to show what data the command will operate on / what changes it will make to the system, whereas the intent behind the function above is to echo the command itself.
Remove-Item foo.txt -WhatIf
would show something like this:What if: Performing operation "Remove File" on Target "C:\tmp\foo.txt".
While you could technically still use -WhatIf
in this use case, you'd then either have to use -WhatIf
ad hoc to turn on echoing (doOrEcho -WhatIf ...
) or set the $WhatIfPreference
preference variable to $true
- but doing so would then affect all cmdlets and functions that support -WhatIf
while the variable is set; additionally, -WhatIf
output is wordy, as shown above.
Arguably, it is Invoke-Command
, PowerShell's generic command-invocation cmdlet, that should support -WhatIf
as in the function above, but it doesn't.
[1] PowerShell has built-in magic to allow relaying even named arguments, but that only works when splatting the automatic $args
variable as a whole, i.e. with @args
.
Upvotes: 3