3

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.

user973223
  • 127
  • 1
  • 8
  • instead of trying t run a cmdlet inside a $Var, simply use and `if` structure to test the value of your trigger $Var. – Lee_Dailey Mar 08 '19 at 15:36
  • Try 'cmd /c echo'. – Kory Gill Mar 08 '19 at 15:39
  • 3
    What are you actually trying to accomplish? There are likely better ways to do it in powershell than relying on esoteric syntax/behaviors. – Maximilian Burszley Mar 08 '19 at 15:43
  • Looks like you're missing the invoke operator (&) – Mike Shepard Mar 08 '19 at 15:46
  • I don't want to just use an if because I don't just want to not run it, I also want what _would_ have been run to be echoed. This is also why $DO_OR_ECHO & my_dangerous_command wouldn't do what I want. – user973223 Mar 08 '19 at 16:00
  • Look at cmdletbinding and -whatif. That's the powershell way to do what you want. – Kory Gill Mar 08 '19 at 16:58
  • @KoryGill For the purpose of their question, I highly doubt they know or understand advanced function features. Additionally, supporting common parameters for external executables is difficult; it'd be easier to just use `Write-Host` – Maximilian Burszley Mar 08 '19 at 17:00

1 Answers1

3

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]

    • Passing the elements of an array ($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.

    • However, it wouldn't be too hard to reconstruct at least an equivalent command line that works syntactically and shows the original argument boundaries.

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.

    • For instance, 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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Why not do this a more proper way in powershell with cmdletbinding and support -whatif ? That's why it exists. – Kory Gill Mar 08 '19 at 16:19
  • @KoryGill: It's worth mentioning `-WhatIf`, but it's not the right tool for the job - please see my update. – mklement0 Mar 08 '19 at 17:02
  • Isn't splatting the arguments a better approach? *Presumably* it handles appropriately quoting them, etc., i.e., `& $cmd @cmdArgs` – Maximilian Burszley Mar 08 '19 at 17:12
  • 1
    Good feedback and without knowing more about overall problem hard to know best design overall. – Kory Gill Mar 08 '19 at 17:39
  • 1
    @TheIncorrigible1: You're right, it's the better choice here (answer updated), though it really only makes a difference with respect to `--%`, not with respect to quoting - please see the bottom section I've just added to the answer. – mklement0 Mar 08 '19 at 17:49