2

$npp = "C:\Program Files\Notepad++\notepad++.exe";

$myfiles = @(
    "C:\bad boys\file1.txt",
    "C:\bad boys\file2.txt",
    "C:\bad boys\file3.txt"
)

foreach ($file in $myfiles) {
    Start-Process -FilePath $npp -ArgumentList "$file" -PassThru  -NoNewWindow | out-null
}

This almost works... except, It doesn't open in notepad++ because it sees the space in the file name and thinks this is where the file path ends... thus, i am unable to open my file list. Any Ideas how to fix? What i get instead is notepad++ asking many times if I want to create the file "C:\bad"

pico
  • 1,660
  • 4
  • 22
  • 52
  • Just single quotes should do it I think – Santiago Squarzon Feb 25 '22 at 20:13
  • 1
    @SantiagoSquarzon, no, when you use `Start-Process`, only double-quoting is supported (unless a given target executable happens to recognize single-quoting on its command line, but that is the exception). – mklement0 Feb 25 '22 at 21:31

2 Answers2

5

tl;dr

While Joel Coehoorn's helpful answer provides an effective solution to your Start-Process problem (which stems from the bug detailed below), you can simplify your code to:

foreach ($file in $myfiles) {
  # Note: | Out-Null is a trick that makes calling *GUI* applications
  #       *synchronous* (makes PowerShell wait for them to exit).
  & $npp $file | Out-Null
}

You're seeing a long-standing bug in Start-Process that causes it to blindly space-concatenate its -ArgumentList (-Args) arguments without using required embedded double-quoting for arguments with spaces when forming the single string encoding all arguments that is passed to the target executable behind the scenes.

  • See GitHub issue #5576, which also discusses that a fix will require a new parameter so as not to break backward compatibility.

For that reason, the required embedded double-quoting must be performed manually as shown in Joel's answer.

When passing multiple arguments, it is ultimately easier to pass a single string to
-ArgumentList, with embedded double-quoting
as necessary - essentially by formulating a string similar to how you would pass multiple arguments from cmd.exe:

E.g., if you were to pass two file paths with spaces to Notepad++ at once, you would do:

Start-Process -Wait -FilePath $npp -ArgumentList "`"C:\bad boys\file1.txt`" `"C:\bad boys\file2.txt`""

Alternatively, since your argument string doesn't require string interpolation, you could use a verbatim (single-quoted) string instead, which avoids the need for escaping the embedded " as `":

Start-Process -Wait -FilePath $npp -ArgumentList '"C:\bad boys\file1.txt" "C:\bad boys\file2.txt"'

Using a here-string is yet another option that avoids the need to escape, and can additionally make the call more readable (also works with single quotes (@'<newline>...<newline>'@):

Start-Process -Wait -FilePath $npp -ArgumentList @"
  "C:\bad boys\file1.txt" "C:\bad boys\file2.txt"
"@

Also note the overall simplification of the Start-Process call:

  • Use of -Wait to ensure synchronous execution (waiting for Notepad++ to exit before continuing).

    • It looks like this is what you tried to do by combining -PassThru with piping to Out-Null, but that doesn't actually work, because that only waits for Start-Process itself to exit (which itself - unlike the launched process - executes synchronously anyway).
  • The omission of the unnecessary -NoNewWindow parameter, which only applies to starting console applications (in order to prevent opening a new console window); Notepad++ is a GUI application.

Note that the only good reason to use Start-Process here - rather than direct invocation - is the need for synchronous execution: Start-Process -Wait makes launching GUI applications synchronous (too), whereas with direct invocation only console applications execute synchronously.

If you didn't need to wait for Notepad++ to exit, direct invocation would make your quoting headaches would go away, as the required embedded quoting is then automatically performed behind the scenes:[1]

foreach ($file in $myfiles) {
  & $npp $file # OK, even with values with spaces
}

However, the | Out-Null trick can be used effectively in direct invocation to make calling GUI applications synchronous[2], which leads us to the solution at the top:

foreach ($file in $myfiles) {
  & $npp $file | Out-Null  # Wait for Notepad++ to exit.
}

[1] However, up to at least PowerShell 7.2.x, other quoting headaches can still arise, namely with empty-string arguments and arguments whose values contain " chars. - see this answer.

[2] Out-Null automatically makes PowerShell wait for the process in the previous pipeline segment to exit, so as to ensure that all input can be processed - and it does so irrespective of whether the process is a console-subsystem or GUI-subsystem application. Since GUI applications are normally detached from the calling console and therefore produce no output there, Out-Null has no ill effects. In the rare event that a GUI application does explicitly attach to the calling console and produce output there, you can use | Write-Output instead (which also works if there's no output, but is perhaps more confusing).

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

Try quotes around the file paths within the string data:

$myfiles = @( 
    "`"C:\bad boys\file.txt`"", 
    "`"C:\bad boys\file2.txt`"",
    "`"C:\bad boys\file3.txt`""
)
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794