How to pass an array to the arguments in Start-Process in Powershell? - powershell

I am writing a script to play certain files in a player. I use Get-ChildItem to get an array of file names. Then I want to use Start-Process to play these files. However, how can I add these file names to the arguments of the player program?
I used Start-Process -FilePath "C:\Program Files\DAUM\PotPlayer\PotPlayerMini64.exe" -ArgumentList $selected_items but it seems it doesn't work and the files are not played.
Notice there are spaces in the file names.

Syntax-wise, your approach should work, but doesn't, due to an unfortunate bug, still present in PowerShell 7.2 - see GitHub issue #5576.
While passing an array of arguments to Start-Process's -ArgumentList parameter does cause the array elements to be passed as individual arguments (which is usually how external CLIs expect multiple file arguments), the necessary double-quoting around elements with spaces is not applied when the command line ultimately used for invocation is constructed behind the scenes.
Also, for robustness you should use the .FullName property of the objects stored in $selected_items, so as to ensure that full paths are passed, because - in Windows PowerShell, situationally - Get-ChildItem's output objects may stringify to the file name only - see this answer.
Workaround: Pass a single argument to -ArgumentList, in which you encode all pass-through arguments, using embedded double-quoting.
Start-Process `
-FilePath "C:\Program Files\DAUM\PotPlayer\PotPlayerMini64.exe" `
-ArgumentList ($selected_items.ForEach({ '"{0}"' -f $_.FullName }) -join ' ')
Taking a step back:
If PotPlayerMini64.exe is a Windows GUI(-subsystem) application, you don't need Start-Process at all, because even direct invocation will then act asynchronously (i.e., the program will launch, and control will return to PowerShell right away; conversely, if you wanted to wait for the program to exit, use Start-Process -Wait).
& "C:\Program Files\DAUM\PotPlayer\PotPlayerMini64.exe" $selected_Items.FullName
Note that in direct invocations such as this, PowerShell does perform the necessary double-quoting behind the scenes, on demand.
Note: I'm unclear on whether passing multiple file paths to PotPlayerMini64.exe alone also starts playback - the alternative solution in the next section may ensure that.
Alternative, clipboard-based solution:
Judging by PotPlayerMini64.exe's available command-line options[1], the following may work (I cannot personally verify):
/clipboard :Appends content(s) from clipboard into playlist and starts playback immediately.
# Copy the full names of the files of interest to the clipboard.
Set-Clipboard -Value $selected_items.FullName
# Launch the player and tell it to start playback of the files on the clipboard.
# Parameters -FilePath and -ArgumentList are positionally implied.
Start-Process 'C:\Program Files\DAUM\PotPlayer\PotPlayerMini64.exe' /clipboard
There are file-arguments-related options such as /new, /insert, and /add, but it's unclear to me whether they - or omitting them altogether, as in your attempt - automatically start playback (may depend on the application's persistent configuration).
[1] Note that this is not the official documentation; I couldn't find the latter.

You can ForEach-Object:
Get-ChildItem . | ForEach-Object {Start-Process -FilePath "C:\Program Files\DAUM\PotPlayer\PotPlayerMini64.exe" -ArgumentList $_.FullName}

You don't need start-process (plus, -argumentlist doesn't handle filenames with spaces as easily).
& "C:\Program Files\DAUM\PotPlayer\PotPlayerMini64.exe"
C:\Program` Files\DAUM\PotPlayer\PotPlayerMini64.exe
$env:path += ';C:\Program Files\DAUM\PotPlayer'; PotPlayerMini64
Even using start-process, it varies with the program. For example, Emacs can take multiple file arguments, separated by spaces. If the filename has a space, it would need to be double quoted. External programs don't know what arrays are, and start-process converts them to one string with spaces in between each element.
start emacs file1,file2,'"my file"'
get-wmiobject win32_process | ? name -eq emacs.exe | % commandline
"c:\program files\emacs\bin\emacs.exe" file1 file2 "my file"
ps emacs | % commandline # ps 7
"c:\program files\emacs\bin\emacs.exe" file1 file2 "my file"

Related

In powershell spawn notepad++ when file to open has spaces in it

$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"
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).
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`""
)

Command Line Command Output in start-process from exe file

Here is the program. I am using dell command | configure. The command-line command is as follows:
"C:\Program Files (x86)\Dell\Command Configure\X86_64>cctk.exe" --wakeonlan
In Powershell you can navigate to the folder and run:
./cctk.exe --wakeonlan
I can pipe the above command into a variable and get the information I need. This requires my shell to cd into the folder accordingly and run accordingly.
$test = ./cctk.exe --wakeonlan
This will give you an output. However when you use start-process, you get no output as this is a command-line command. A cmd screen appears and runs the command. So, I added a -nonewwindow and -wait flags. The output now appears on the screen, but I can't seem to capture it.
$test = start-process "C:\Program Files (x86)\Dell\Command Configure\X86_64\cctk.exe" -ArgumentList #("--wakeonlan") -NoNewWindow -Wait
At this point test is empty. I tried using the Out-File to capture the information as well. No success. The command outputs to the screen but nowhere else.
I also tried the cmd method where you pipe the information in using the /C flag.
$test = Start-Process cmd -ArgumentList '/C start "C:\Program Files (x86)\Dell\Command Configure\X86_64\cctk.exe" "--wakeonlan"' -NoNewWindow -Wait
However, I have tried many variations of this command with no luck. Some say C:\Program is not recognized. Some just open command prompt. The above says --wakeonlan is an unknown command.
Any pointers would help greatly.
There are various ways to run this without the added complication of start-process.
Add to the path temporarily:
$env:path += ';C:\Program Files (x86)\Dell\Command Configure\X86_64;'
cctk
Call operator:
& 'C:\Program Files (x86)\Dell\Command Configure\X86_64\cctk'
Backquote all spaces and parentheses:
C:\Program` Files` `(x86`)\Dell\Command` Configure\X86_64\cctk
To elaborate on js2010's helpful answer:
In short: Because your executable path is quoted, direct invocation requires use of &, the call operator, for syntactic reasons - see this answer for details.
To synchronously execute console applications or batch files and capture their output, call them directly ($output = c:\path\to\some.exe ... or $output = & $exePath ...), do not use Start-Process (or the System.Diagnostics.Process API it is based on) - see this answer for more information.
If you do use Start-Process, which may be necessary in special situations, such as needing to run with a different user identity:
The only way to capture output is in text files, via the -RedirectStandardOutput / -RedirectStandardError parameters. Note that the character encoding of the output files is determined by the encoding stored in [Console]::OutputEncoding[1], which reflects the current console output code page, which defaults to the system's active legacy OEM code page.
By contrast, even with -NoNewWindow -Wait, directly capturing output with $output = ... does not work, because the launched process writes directly to the console, bypassing PowerShell's success output stream, which is the one variable assignments capture.
[1] PowerShell uses the same encoding to decode output from external programs in direct invocations - see this answer for details.

How to run an executable (exe) by providing a config file in powershell

I'm trying to run an exe in the background by providing a config file (yml in my case)
Tried the below, however this is not pushing the execution to background. -
./my.exe start --config-file $my_config_file
Found 'start-process' command which are specifically used for this case. With argument list is there any way to send the config file?
Start-Process -Wait -FilePath "my.exe" -ArgumentList
Remove the -Wait argument and pass the process arguments as an array via -ArgumentList parameter:
Start-Process -FilePath "my.exe" -ArgumentList 'start', '--config-file', "`"$my_config_file`""
The strange quoting for $my_config_file is required because a path may contain spaces. Start-Process does not do automatic quoting. From the docs:
If parameters or parameter values contain a space, they need to be surrounded with escaped double quotes.
Note that you won't receive output of the started process, if that matters to you. You can redirect to a file, using parameters -RedirectStandardOutput and -RedirectStandardError, but you can't (easily) store the output in a variable.
A way to start a process in the background, while being able to receive its output, is to create a job.

Question about using PowerShell Select-String, exiftool(-k)

In a PowerShell script I'm trying to filter the output of the exiftool(-k).exe command below, using Select-String.
I've tried numerous permutations, but none work, and I always see the unfiltered output. What do I need to do to filter the output of this command?
Start-Process -FilePath "C:\PowerShell\exiftool(-k).exe" -ArgumentList test.jpg |
Select-String -pattern 'GPS' -SimpleMatch
You cannot directly receive output from a Start-Process call[1], so using it in a pipeline is pointless.
In fact, on Windows your program launched with Start-Process runs in a different, new window, which is where you saw the unfiltered output (given that no Select-String was applied there); in your calling window, Start-Process produced no output at all, and therefore nothing was sent to Select-String, and the pipeline as a whole produced no output.
Never use Start-Process to synchronously invoke a console application whose output you want to capture or redirect - simply call the application directly:
& "C:\PowerShell\exiftool(-k).exe" test.jpg | Select-String GPS -SimpleMatch
Note that &, the call operator, is needed for this invocation, because your executable path is (double-)quoted (of necessity here, because the file name contains ( and )); & is only needed for executable paths that are quoted and/or contain variable references; you wouldn't need it to call git ..., for instance.
[1] While you would see the program's output in the caller's window if you added -NoNewWindow -Wait to a Start-Process call, you still wouldn't be able to capture, pass on or redirect it.
The naming is inconvenient. Rename it to exiftool.exe and run it without start-process.
rename-item 'exiftool(-k).exe' exiftool.exe
C:\Users\bfbarton\PowerShell\exiftool.exe test-jpg | Select-String GPS
Or
$env:path += ';C:\Users\bfbarton\PowerShell'
exiftool test.jpg | Select-String GPS
The website recommends to 'rename to "exiftool.exe" for command-line use'. https://exiftool.org . Even in unix, it wouldn't work without escaping the parentheses.
There's also the option of using the call operator. Using tab completion actually does this:
& '.\exiftool(-k).exe' test.jpg | select-string gps

PowerShell run Ultraedit Script

Is it possible to run an UltraEdit macro or script from the PowerShell? Something like following:
uedit64.exe c:\temp\test.txt /s,e="c:\temp\script.js"
I have nothing special. I just want to open the log file with UltraEdit and as soon the log file is opened the UltraEdit Script should be executed on that. The following code opens the log file but does not execute the script on that.
$ultraEdit = "C:\...\UltraEdit\uedit64.exe"
$logFile = "C:\...\res.log"
$scriptFile = "C:\...\ultraEditScript.js"
Start-Process -FilePath $ultraEdit -ArgumentList "$logFile /s=`"$scriptFile`""
Absolutely! Powershell has a few different "call" operators.
https://ss64.com/ps/call.html
Take a look at the documentation for Start-process.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-6
Start-Process -FilePath "c:\pathtoexe\uedit64.exe" -ArgumentList "c:\temp\test.txt /s,e=`"c:\temp\script.js`""
Should work for you (change the path of course.
Yes, it is possible. The problem with your current example is surrounding quoting rules with arguments:
uedit64.exe c:\temp\test.txt '/s,e="c:\temp\script.js"'
This form should work. When you use commas, powershell will interpret that as an array. The safest way to pass arguments to an external executable is to use the stop-parser operator (--%) to avoid powershell's interpretation, but note this falls back to the cmd parser on Windows:
#requires -Version 3
uedit64.exe --% C:\Temp\test.txt /s,e="C:\Temp\script.js"
What the difference in parsers means is that you can't expand variables (if you wanted $path\script.js) in the arguments after the stop-parser, but you can still utilize environment variables using the batch syntax %VAR%.
As a best-practice, it's recommended to fully-qualify your path and use the call operator for clarity:
& C:\Temp\uedit64.exe
Thanks everyone,
The problem was with Select-String that split the matched lines, therefore, the script did not perform any action due to improper file structure.
These two works great :-)
1. & $ultraEdit /fni $logFile /S=$scriptFile
2. Start-Process -FilePath $ultraEdit -ArgumentList "$logFile /S=$scriptFile"