0

Windows 11 Power shell

I'm trying to run the following IF statement in power shell - basically to check if a file exists on encrypted drive P, if it doesn't then create the encrypted drive. I get an error "/v is an invalid switch" which suggests PowerShell associates this switch with the start command not the veracrypt.exe command. Ideas how to get this to run??? P is the encrypted drive. If I run the command between the {} brackets on a separate line it runs ok, just get the error when its combined into the IF statement.

$path = "P:\check.txt"
if(-not(Test-Path -path $path)) {cmd /c start /WAIT "" "C:\Program Files\VeraCrypt\Veracrypt.exe" /v "C:\Location_of_Veracrypt_Container" /l P: /p Password /q /hash SHA-512 /nowaitdlg}
4
  • 1
    any particular reason to start your binary using cmd as opposed to just Veracrypt.exe /v "C:\Location_of_Veracrypt_Container" /l P: /p Password /q /hash SHA-512 /nowaitdlg ? If the reason is it doesn't wait for the process to finish you should use Start-Process -Wait Commented Nov 14 at 20:54
  • NO reason other than inexperience. I tried using Start-Process and got the folowing error statements below. Still seems like the switches I need to run with VeraCrypt.exe get associated with the Start command once they makeup the 2nd half of the IF statement. Commented Nov 14 at 21:17
  • PS C:\Users\Jeff> if(-not(Test-Path -path $path)) {Start-Process -Wait "C:\Program Files\VeraCrypt\Veracrypt.exe" /v "C:\Location_of_Veracrypt_Container" /l P: /p Password /q /hash SHA-512 /nowaitdlg} Start-Process : A positional parameter cannot be found that accepts argument 'D:\User Data\System DP'. At line:1 char:34 Commented Nov 14 at 21:18
  • @Cholokom, I don't think the presence of if makes a difference: your cmd /c ... call is broken in itself, as explained at the top of my answer - omitting the unnecessary start /WAIT "" part bypasses the problem, as long as you enclose the exe call in "..." overall. While a Start-Process solution is also possible (but more cumbersome), note that your attempt in the previous comment mistakenly passes the pass-through arguments individually; instead, pass them as an array (,-separated) or, preferably, as a single string; again, see my answer. Commented Nov 15 at 19:04

2 Answers 2

3

tl;dr

  • Your immediate problem is that, due to a PowerShell bug,[0] PowerShell omits the "" argument from the cmd /c start /WAIT call, which means that "C:\Program Files\VeraCrypt\Veracrypt.exe" becomes the title of the window created by start, and /v is interpreted as yet another start switch - which doesn't exist, hence the Invalid switch error. However, because start need not be used in your use case, as explained below, this problem isn't worth fixing.

  • Your question suggests that you want to execute a GUI application synchronously (in a blocking fashion) from PowerShell.
    For console applications, direct invocation is the right solution (no need for cmd.exe), as shown in Bill Stewart's answer, because PowerShell (invariably) executes them synchronously.

  • Synchronous execution of GUI application can be achieved in of three ways (leaving direct use of .NET APIs aside), all of which are detailed in the next sections:

    • Delegating to cmd /c " ... ", without additional use of start /WAIT

    • Using direct invocation in combination with piping to a dummy command,
      & ... | Write-Output

    • Using the Start-Process cmdlet with its -Wait switch.

    • Caveat:

      • These techniques only work for GUI applications that do not delegate to a different process on startup and then exit right away.

      • Start-Process -Wait, unlike the other two techniques, is more robust in that it also waits for any child processes launched by the application (startup) process to terminate.

      • However, even Start-Process -Wait isn't sufficient for GUI applications that delegate to a non-child process, which appears to be the case at least with some modern UWP / Windows App SDK applications such as the built-in calculator application, calc.exe, as well as for the Microsoft Edge browser.


Your use of start /WAIT suggests that the target executable is a GUI application whose termination you want to wait for, i.e. you want to invoke it synchronously (in a blocking fashion).

However, you do not need start /WAIT, because cmd /c itself ensures synchronous execution.[1]

Additionally, to avoid problems with cmd.exe's parsing of multiple "..."-enclosed arguments, enclose the executable command line overall in "..." too, i.e. pass a single double-quoted argument to the /c parameter, with embedded double-quoted arguments.[2]

Therefore:

# Synchronously executes Veracrypt.exe and reflects its exit code in $LASTEXITCODE
# Note the overall "..." enclosure of what follows /c
cmd /c " "C:\Program Files\VeraCrypt\Veracrypt.exe" /v "C:\Location_of_Veracrypt_Container" /l P: /p Password /q /hash SHA-512 /nowaitdlg "

Note:

  • The above should execute Veracrypt.exe synchronously and report its exit code back to PowerShell, which the latter reflects in the automatic $LASTEXITCODE variable.

  • While use of cmd.exe involves an extra child process (compared to the PowerShell-native solutions discussed in the next section), that extra overhead is unlikely to matter in practice.

  • Conversely, the cmd /c approach offers one advantage over the PowerShell-native solutions: it gives you full control over how the arguments are quoted, which notably matters with GUI-subsystem applications such as msiexec.exe that situationally require arguments to be partially double-quoted (e.g. INSTALLLOCATION="C:\foo bar"; see this answer for more.


Taking a step back:

You do not need cmd.exe for synchronous execution of GUI applications in PowerShell:

  • PowerShell's own Start-Process cmdlet is roughly equivalent to cmd.exe's internal start command and also has a -Wait switch to ensure synchronous execution.

    • While its use - demonstrated further below - is more cumbersome than the simpler direct-invocation alternative discussed next, there may still be a reason to prefer it:
      Unlike the simpler alternative and the cmd /c technique, Start-Process also waits for child processes of the immediately launched process to terminate, which also makes it work for applications with short-lived start-up processes that delegate to a child process; however, note that even that isn't enough for some modern UWP / Windows App SDK applications such as the built-in calculator application, calc.exe; it does, however, work for the Windows 11 version of notepad.exe, for instance, but not (reliably) for applications that delegate to preexisting and therefore (non-child) processes, such as Microsoft Edge.
  • However, a simpler alternative is to use direct invocation and pipe (|) the invocation to a dummy command, which not only makes the call synchronous, but also directly populates $LASTEXITCODE, as with the cmd /c technique (by contrast, with Start-Process you'd have to also use -PassThru, and examine the returned process-information object's .ExitCode property).
    This approach is shown next.

Using direct invocation with |:

# Synchronously executes Veracrypt.exe and reflects its exit code in $LASTEXITCODE
# Note the pipe operator (|) at the end of the first line, and the use of
# dummy command Write-Output as the receiving command.
& "C:\Program Files\VeraCrypt\Veracrypt.exe" /v "C:\Location_of_Veracrypt_Container" /l P: /p Password /q /hash SHA-512 /nowaitdlg |
  Write-Output

Note:

  • It is use of |, the pipeline operator (at the end of the Veracrypt.exe call, scrolled out of view) that makes direct invocation of a GUI application synchronous,[3] which would otherwise be asynchronous (invariably so, unlike in cmd.exe)[4] Additionally, as noted, it records the application process' exit code in the automatic $LASTEXITCODE variable.

    • Note that this contrasts with console application, which in direct invocation are invariably launched synchronously.

    • Typically, GUI applications produce no output to the parent process' console, so the use of Write-Output as the receiving command will have no effect.
      However, some GUI applications explicitly attach to their parent process' console in order to print diagnostic output to it, which Write-Output would surface; if that is undesired, pipe to Out-Null instead.

  • Because the executable path is quoted, PowerShell requires use of &, the call operator, to invoke it, purely for syntactic reasons, as explained in this answer.
    Its use is optional for literal, unquoted executable names/paths (whereas variable-based names/paths require & too).


Using Start-Process:

# Synchronously executes Veracrypt.exe and returns a process-information object,
# due to use of -Wait and -PassThru.
# $processInfo.ExitCode will contain the exit code.
$processInfo =
  Start-Process -Wait -PassThru "C:\Program Files\VeraCrypt\Veracrypt.exe" @"
/v "C:\Location_of_Veracrypt_Container" /l P: /p Password /q /hash SHA-512 /nowaitdlg
"@

Note:

  • Even though Start-Process technically accepts an array of arguments (separated with ,), it is ultimately easier to pass the list of arguments encoded in a single string, as shown, due to a long-standing bug - see this answer for more.

  • A here-string (@"<newline>...<newline>"@) is used to allow use of embedded " chars. without escaping.


[0] See this answer for background information. The short of it is that Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and final version is 5.1) has bugs with respect to passing the empty string as an argument as well as arguments with embedded double quotes. While this was mostly fixed in PowerShell (Core) 7 v7.3+, the old, broken behavior is by default selectively retained for certain applications that exhibit nonstandard behavior, notably including cmd.exe and batch files.

[1] Note: Only a directly launched process is waited for, so this technique won't work for applications that ultimately launch via a child process or delegate to a preexisting process. By contrast, the -Wait switch of PowerShell's Start-Process also waits for child processes; see later in this post.

[2] Note that, due to cmd.exe's idiosyncratic parsing, embedded " chars. do not require escaping.

[3] Note that alternatively using >, the redirection operator, does not ensure synchronous execution. This surprising discrepancy has been reported in GitHub issue #26455.

[4] cmd.exe's behavior depends on whether the invocation occurs interactively in a session, in which case it is asynchronous, vs. in a batch file / via cmd /c (or cmd /k), in which case it is synchronous

Sign up to request clarification or add additional context in comments.

Comments

3

If your goal is simply to invoke Veracrypt.exe and you don't care if the script waits for it to finish before continuing, you can just use this:

    if ( -not (Test-Path "P:\check.txt") ) {
      & "C:\Program Files\VeraCrypt\Veracrypt.exe" /v "C:\Location_of_Veracrypt_Container" /l P: /p "Password" /q /hash SHA-512 /nowaitdlg
    }

PowerShell is a shell, after all. It can execute commands without involving cmd.exe. (One of the core functions of a shell is running programs.)

One thing to note in the above is the use of the invocation operator (aka call operator), &. This is needed in this case because the path and filename of the executable you are running contains whitespace, and the call operator tells PowerShell "the string that follows the & character is a command to execute." (If you leave out the & character, PowerShell will evaluate the command name as a quoted string rather than a command to execute.)

See help about_Operators - there's a section on the call operator. Current link to doc page:

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.5#call-operator-

7 Comments

That's good advice for invoking console applications, which execute synchronously. However, the use of start /WAIT in the question as well as the /nowaitdlg argument suggests that the target application is a GUI application that the OP is trying to execute synchronously, which your solution won't do.
That the OP is attempting synchronous execution is a guess, isn't it, based on the use of start /wait in the original question. If synchronous execution is not a requirement, then this solution should work with no issues.
That synchronous execution is the intent is implied by start /WAIT - it isn't a guess. And given that we are dealing with a GUI-subsystem application ("Windows only has GUI version (WinMain entry point, no console)" - github.com/veracrypt/VeraCrypt/issues/1554) explicit waiting is required with a native PowerShell approach. Generally speaking, users are likely to desire synchronous invocation when invoking a CLI (even if it happens to be provided by a GUI application), as it typically involves an automated action whose success should be checked on return.
The OP doesn't state explicitly in the question that synchronous execution is a requirement of his script. It is implied, yes, but not stated explicitly. I inferred that synchronous execution is not required because the script he posted contains no other code than an attempt to execute a command line. Yes, that's an assumption on my part. IMO, both assumptions have similar weight. This is why I posted the answer using the & operator. If the OP doesn't like that it executes asynchronously, he could post a comment on this answer to that effect.
Using start /WAIT is a deliberate act, so your inference is questionable. Posts on SO are never just about the OP - and chances are they may never come back. The bottom line is: your answer demonstrably behaves differently than what the OP tried to do (without the - non-obvious - syntax problem, their command would execute synchronously), and to not at least acknowledge that as a prominent part of your answer is a disservice to future readers.
We'll just have to agree to disagree.
For others reading, I added a clarifying statement at the top of my answer. Hope this helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.