3

I have the following PS command that returns a string:

(get-date -Uformat %s).remove(10, 1).substring(5,9)

I want to call the application myapp.exe from the current directory with the result of that PS command as an argument, and I want to do it in a single line (no intermediate variable). What's the syntax for that?

In other words, I'm looking for a Powershell equivalent for $(<command>) in bash.

Violet Giraffe
  • 32,368
  • 48
  • 194
  • 335

3 Answers3

3
./myapp.exe (get-date -Uformat %s).remove(10, 1).substring(5,9)
  • ./myapp.exe (or .\myapp.exe) invokes executable myapp.exe located in the current directory.

    • If myapp.exe is instead located in a directory listed in $env:PATH, do not use the ./ \ .\ prefix.
    • Either way, the resulting process runs in the current directory (filesystem-provider location).
  • Arguments passed to executables are (fittingly) parsed in argument mode (see Get-Help about_Parsing) and how a given (whitespace-separated) argument is interpreted depends on its first character:

    • Since the first character is (, the argument is evaluated as an expression (parsed in expression mode); whatever that expression evaluates to is passed to the the executable.
      The other special characters in the first position are $, @, as well as ', and " (see next point).

      • Note that while $(...) - the subexpression operator (see Get-Help about_Operators) - would also work in this case, it is overkill if only a single expression ((...)) is to be evaluated - $(...) is only needed if you want to pass the output from multiple statements.
    • The content of a '...' argument (single-quoted) is treated as a literal, whereas the content of a "..." argument is an expandable string (loosely speaking, a string in which $-prefixed tokens are interpolated).

    • Any other argument (one that is unquoted and doesn't start with (, @, or $), by and large, is implicitly treated like an expandable string, i.e., as if you had enclosed it in "...".

    • For a comprehensive overview of how arguments that aren't explicitly enclosed in "..." or '...' are parsed in argument mode, see this answer of mine.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

i think you are looking for this:

command ((get-date -Uformat %s).remove(10, 1).substring(5,9))

but your command should work even without ()

4c74356b41
  • 69,186
  • 6
  • 100
  • 141
1

You can use Invoke-Expression, however, keep in mind that there are security considerations to ponder when using this cmdlet, as it may open you up to code-injection attacks. Also note that Invoke-Expression will usually succeed even when the underlying command fails with an error.

$command = "myapp.exe '$((get-date -Uformat %s).remove(10, 1).substring(5,9))'"
Invoke-Expression $command

If you want to check the success of your command, check $LASTEXITCODE for a suitable exitcode and continue/fail based on that. Note that $LASTEXITCODE only works when checking the result of a program, not a cmdlet.

Avoiding Invoke-Expression

You can avoid the use of Invoke-Expression by building an array of arguments to pass to your command line application, like so (in your case, it would just be one argument but this can be expanded to build out more complex commands with more than one argument as well, just add additional array elements for each switch, parameter, or value):

$cmdargs = @(
  ( GetDdate -Uformat %s ).Remove( 10, 1 ).Substring( 5,9 )
)
myapp.exe $cmdargs

You can still track success of the command by checking the value of $LASTEXITCODE.

Keeping it simple

In your case, you can just call myapp.exe (get-date -Uformat %s).remove(10, 1).substring(5,9) which will insert the date string as an argument. But the above techniques (building an array is the preferred method to Invoke-Expression) becomes useful when you want to build a more complex command based on a number of conditions.

codewario
  • 19,553
  • 20
  • 90
  • 159
  • There is rarely a good reason to use `Invoke-Expression`; it should be avoided, because more robust solutions are usually available (as is the case here) and because - due to its ability to invoke arbitrary commands passed as strings - it can be a security risk, as you state. Avoiding it unless absolutely necessary is a good habit to form. Here, the target expression can simply be passed as-is to the the target executable (`myapp.exe (get-date ...)`). – mklement0 Jul 06 '18 at 22:18
  • For programs, you *can* build a string and execute them with `& $command`. But for cmdlets, I don't know of a better way to conditionally build a command out than build a string and call it with `Invoke-Expression`. – codewario Jul 06 '18 at 22:31
  • `&` can be used with cmdlet/function/script names [in quoted strings / stored in variables] too. Try `& Get-Date`, `$cmd = 'Get-Date'; & $cmd`, `& 'Get-Date'`. Do note that only the _command name / file path_ is the argument to `&`, whereas the _arguments_ are passed separately, as usual. – mklement0 Jul 06 '18 at 22:33
  • I did not know that, I thought `&` was specifically for program execution. Learn something new every day. – codewario Jul 06 '18 at 23:20
  • Glad to hear it. All _commands_ (cmdlets, functions, scripts, external programs, aliases, script blocks) are created equal with respect to `&` (and `.`), and, with the exception of script blocks, with respect to direct invocation. Recommending `Invoke-Expression` in this scenario is a bad idea in my opinion, so please consider revising your answer. – mklement0 Jul 07 '18 at 00:11
  • I disagree, if only *because* it it's a common, yet insecure solution to this problem. The answer is valid *and* outlines the security implication. Though I may add to the answer later with an alternative solution as well, if I get the time tonight. – codewario Jul 07 '18 at 00:24
  • Perhaps it is common, but it is ill-advised. Yes, you mention the security implications, but the point is that for the scenario at hand there's no good reason to bring `Invoke-Expression` into the conversion to begin with - except in order to advise against its use. – mklement0 Jul 07 '18 at 01:51
  • Finally got around to adding an alternative solution. I left the `Invoke-Expression` bit to serve as a warning. – codewario Jul 09 '18 at 18:53
  • I appreciate the effort, but from the perspective of the question asked - how to pass an expression as an argument to a directly invoked console program - the structure of your answer is backward: The _last_ approach (covered by the other answers) should go first, _then_ it's worth mentioning use of an _array_; again, no good reason to even mention `Invoke-Expression`, except to say [that it is considered harmful](https://blogs.msdn.microsoft.com/powershell/2011/06/03/invoke-expression-considered-harmful/). – mklement0 Jul 09 '18 at 19:07
  • Well you are always free to write your own answer. Can't please everyone. – codewario Jul 09 '18 at 19:11
  • That's true. I wrote my own answer 2 days ago (and it is - as of this writing - the accepted one). That is not mutually exclusive with suggesting improvements to other answers, especially if they have the potential to be a useful _complement_ to the accepted answer. – mklement0 Jul 09 '18 at 19:14