powershell Pipe Operators - windows

I executed the following three powershell commands. The first two commands returned no results, and the third command returned results. The main difference between the three commands is the use of the wait argument and parentheses.
PS C:\Users> Start-Process -FilePath 'msiexec' -ArgumentList '/I C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' -PassThru | Foreach-Object -Process { $_.exitcode }
PS C:\Users> Start-Process -FilePath 'msiexec' -ArgumentList '/I C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' -PassThru -wait | Foreach-Object -Process { $_.exitcode }
PS C:\Users> (Start-Process -FilePath 'msiexec' -ArgumentList '/I C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' -PassThru -Wait) | Foreach-Object -Process { $_.exitcode }
1619
I test another two commands, and the difference between them was the use of parentheses. Both commands returned results no matter with parentheses.
PS C:\Users> Start-Process -FilePath 'msiexec' -ArgumentList '/I C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' -PassThru | Foreach-Object -Process { $_.id }
22980
PS C:\Users> (Start-Process -FilePath 'msiexec' -ArgumentList '/I C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet' -PassThru -Wait) | Foreach-Object -Process { $_.id }
8064
Thanks for explanation for -wait parameter. I still confused with the difference caused by parentheses. Hope for more replies.

The main difference between the three commands is the use of the -Wait argument and parentheses
To build on Mathias R. Jessen's helpful comment:
It is primarily the use of Start-Process's -Wait switch that is required:
Without -Wait, Start-Process runs asynchronously.
Without -PassThru, Start-Process produces no output.
While -PassThru makes Start-Process output a System.Diagnostics.Process instance representing the newly launched process, unless -Wait is also present that instance's .ExitCode property has no value yet, because the launched process typically hasn't exited yet.
Additionally, parentheses ((...)) are required too, because Start-Process emits the System.Diagnostics.Process instance representing the newly launched process to the pipeline (as received by ForEach-Object) right away, and then waits for the process to exit. By using (...), the grouping operator, you're forcing a wait for Start-Process itself to exit, at which point the Process' instance .ExitCode property is available, thanks to -Wait.
In general, wrapping a command in (...) forces collecting its output in full, up front - which includes waiting for it to exit - before the results are passed through the pipeline (as opposed to the streaming (one-by-on output) behavior that is the default, which happens while the command is still running).
Therefore, the following works - but see the bottom section for a simpler alternative:
# Note: With (...), you could also pipe the output to ForEach-Object, as in
# your question, but given that there's by definition only *one*
# output object, that is unnecessary.
(
Start-Process -PassThru -Wait -FilePath 'msiexec' -ArgumentList '/i C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet'
).ExitCode
It follows from the above that using two separate statements would work too (given that any statement runs to completion before executing the next):
$process = Start-Process -PassThru -Wait -FilePath 'msiexec' -ArgumentList '/i C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet'
$process.ExitCode
msiexec.exe is unusual in that:
it is a GUI(-subsystem) executable (as opposed to a console(-subsystem) executable), which therefore - even when invoked directly - runs asynchronously.
yet it reports a meaningful process exit code that the caller may be interested in, requiring the caller to wait for its exit (termination) in order to determine this exit code.
As an aside: For invoking console applications, Start-Process is not the right tool in general, except in unusual scenarios - see this answer.
An simpler alternative to using msiexec with Start-Process -PassThru -Wait is to use direct invocation via cmd /c, which ensures both (a) synchronous invocation and (b) that PowerShell reflects msiexec's exit code in its automatic $LASTEXITCODE variable:
cmd /c 'msiexec /i C:\Users\Downloads\Everything-1.4.1.1015.x64.msi -quiet'
$LASTEXITCODE # output misexec's exit code
Note: If the msiexec command line needs to include PowerShell variable values, pass an expandable (double-quoted) string ("...") to cmd /c instead and - as with verbatim (single-quoted) string ('...') strings - use embedded double quoting around embedded arguments, as necessary.

Related

Opening another powershell terminal from the current script to execute the current script output

$argList = "-file `"C:\Users\bdl\Desktop\jhansi\PowerShell_Scripts\dialog.ps1`""
Start powershell -argumentlist $argList -NoNewWindow
I am trying to open another powershell terminal from the current script to execute the current script output. Another powershell terminal is opening but it is blinking continuously. The above two lines of code i have written but it is blinking. please tell me where is the mistake in the above two lines.
You can try something like this:
$ArgList = "C:\Users\bdl\Desktop\jhansi\PowerShell_Scripts\dialog.ps1"
Start-Process -FilePath PowerShell -ArgumentList $ArgList -NoNewWindow -Wait
You can also check if the script called in PowerShell returns successfully or not by adding an exit code into it (Exit 0 means it succeed and Exit 1 if it fails) with:
$Exe = (Start-Process -FilePath PowerShell -ArgumentList $ArgList -NoNewWindow -Wait -PassThru).ExitCode
If ($Exe -ne 0)
{
Write-Host "An error has occured while running the script."
}
As the exit code other than 0 means the script didn't finish properly.

Powershell - Start-Process ArgumentList accepts only single variable with spaces

I'm trying to start my script from Explorer. I've found the solution and it works if script doesn't have any parameters.
$file = [System.IO.Directory]::GetCurrentDirectory() + "\Trees.ps1"
Start-Process powershell -verb runas -ArgumentList "-ExecutionPolicy UnRestricted -File `"$($file)`""
However, if I add additionall parameters just like the first it ceases to work. Ie. this code throws "The string is missing the terminator: '." error.
$file = [System.IO.Directory]::GetCurrentDirectory() + "\Trees.ps1"
$Context = [System.IO.Directory]::GetCurrentDirectory()
Start-Process powershell -verb runas -ArgumentList "-ExecutionPolicy UnRestricted -Context '"$($Context)'" -File `"$($file)`""
I'm using this way of expecting variable:
[Parameter(Mandatory=$True)]
[string]$Context,
What can I do to pass more then one variable with spaces in ArgumentList?
I suspect that I should pass arguments for file content in other way than when just passing file name, but couldn't find solution.
In addition to the back ticks issue that Theo noted, -context should follow -file.
-File
Runs the specified script in the local scope ("dot-sourced"), so that the
functions and variables that the script creates are available in the
current session. Enter the script file path and any parameters.
File must be the last parameter in the command, because all characters
typed after the File parameter name are interpreted
as the script file path followed by the script parameters.
So your command line would be
Start-Process powershell -verb runas -ArgumentList "-ExecutionPolicy UnRestricted -File `"$($file)`" -Context $($Context)"
Since you are using an external process call, you will need to use inside double quotes instead of single quotes. You can escape double quotes simply by adding another double quote ("").
Start-Process powershell -verb runas -ArgumentList "-ExecutionPolicy UnRestricted -File ""$file"" -Context ""$Context"""

Suppressing The Command Window Opening When Using Start-Process

I'm trying to find a way to get PowerShell not to spawn a command window when running an executable using Start-Process.
If I call the executable directly within the script (e.g. .\program.exe) then the program runs (with its arguments) and the output is returned to the PowerShell window.
If I use Start-Process the program spawns a command window where the program runs and returns it's output.
If I try and use the -NoNewWindow switch of Start-Process the script then errors out saying it can't find the exe file.
I would prefer to use Start-Process to have access to the -Wait switch, as the programs and configurations the script makes can take some time individually to finish, and I don't want later commands starting up.
This code runs the executable in a separate command window:
Start-Process DeploymentServer.UI.CommandLine.exe -ArgumentList "download --autoDownloadOn --autoDownloadStartTime $StartTime --autoDownloadEndTime $EndTime" -Wait
This code runs the exe within the PowerShell console:
.\DeploymentServer.UI.CommandLine.exe download --autoDownloadOn --autoDownloadStartTime $StartTime --autoDownloadEndTime $EndTime
If I add the -NoNewWindow to the Start-Process code
Start-Process DeploymentServer.UI.CommandLine.exe -ArgumentList "download --autoDownloadOn --autoDownloadStartTime $StartTime --autoDownloadEndTime $EndTime" -Wait -NoNewWindow
I get the following error:
Start-Process : This command cannot be executed due to the error: The system
cannot find the file specifie
At C:\Temp\SOLUS3Installv1.3.ps1:398 char:22
+ Start-Process <<<< DeploymentServer.UI.CommandLine.exe -ArgumentList "download --autoDownloadStartTime $StartTime --autoDownloadEndTime $EndTime" -Wait -NoNewWindow
+ CategoryInfo : InvalidOperation: (:) [Start-Process], InvalidOperationException
+ FullyQualifiedErrorId : InvalidOperationException,Microsoft.PowerShell.Commands.StartProcessCommand
You should prefix the executable name with the current directory when you use the -NoNewWindow switch:
Start-Process .\DeploymentServer.UI.CommandLine.exe -ArgumentList "download --autoDownloadOn --autoDownloadStartTime $StartTime --autoDownloadEndTime $EndTime" -Wait -NoNewWindow
Background information:
The first thing Start-Process tries to do is to resolve the value of the -FilePath parameter by PowerShell rules. If it succeeds, it replaces the value value passed with the full path to the command. If not, it leaves the value untouched.
In the Windows API there are two ways to start a new process: CreateProcess and ShellExecute. ShellExecute is the default, but if you use a cmdlet parameter that requires CreateProcess (for example, -NoNewWindow), then CreateProcess will be used. The difference between them, which matters for this question, is that when looking for a command to execute, CreateProcess uses the current process' working directory, while ShellExecute uses the specified working directory (which Start-Process by default passes based on the current filesystem-provider location, unless explicitly specified via -WorkingDirectory).
PS Test:\> 1..3 |
>> ForEach-Object {
>> New-Item -Path $_ -ItemType Directory | Out-Null
>> Add-Type -TypeDefinition #"
>> static class Test {
>> static void Main(){
>> System.Console.WriteLine($_);
>> System.Console.ReadKey(true);
>> }
>> }
>> "# -OutputAssembly $_\Test.exe
>> }
PS Test:\> [IO.Directory]::SetCurrentDirectory((Convert-Path 2))
PS Test:\> Set-Location 1
PS Test:\1> Start-Process -FilePath Test -WorkingDirectory ..\3 -Wait # Use ShellExecute. Print 3 in new windows.
PS Test:\1> Start-Process -FilePath .\Test -WorkingDirectory ..\3 -Wait # Use ShellExecute. Print 1 in new windows.
PS Test:\1> Start-Process -FilePath Test -WorkingDirectory ..\3 -Wait -NoNewWindow # Use CreateProcess.
2
PS Test:\1> Start-Process -FilePath .\Test -WorkingDirectory ..\3 -Wait -NoNewWindow # Use CreateProcess.
1
PowerShell does not update the current process' working directory when you change the current location for the FileSystem provider, so the directories can differ.
When you type:
Start-Process DeploymentServer.UI.CommandLine.exe -Wait -NoNewWindow
Start-Process cannot resolve DeploymentServer.UI.CommandLine.exe by PowerShell rules, since it does not look in the current FileSystem location by default. And it uses CreateProcess, since you specify -NoNewWindow switch. So, it ends up looking for DeploymentServer.UI.CommandLine.exe in the current process' working directory, which does not contains this file and thus causes an error.

Start a detached background process in PowerShell

I have a Java program which I would like to launch as a background process from a PowerShell script, similar to the way a daemon runs on Linux. The PowerShell script needs to do a couple of things:
Run the program as a separate and detached process in the background, meaning the parent window can be closed and the process keeps running.
Redirect the program's standard output and standard error to files.
Save the PID of the background process to a file so it can be terminated later by another script.
I have a shell script on Linux which starts the program like so:
$ java -jar MyProgram.jar >console.out 2>console.err &
I'm hoping to replicate the same behavior on Windows using a PowerShell script. I have tried using Start-Process with various combinations of options, as well as creating System.Diagnostics.ProcessStartInfo and System.Diagnostics.Process objects, but so far I am not having any luck. PowerShell starts the program as a background process, but the program abruptly terminates when the DOS window which started the PowerShell session is closed. I would like it to start in the background and be independent of the command window which started it.
The output redirection has also been troublesome, as it seems that the output and error streams can only be redirected in the process is being run in the same window (e.g., using -NoNewWindow).
Is this sort of thing possible in PowerShell?
Use jobs for this:
Start-Job -ScriptBlock {
& java -jar MyProgram.jar >console.out 2>console.err
}
Another option would be Start-Process:
Start-Process java -ArgumentList '-jar', 'MyProgram.jar' `
-RedirectStandardOutput '.\console.out' -RedirectStandardError '.\console.err'
Consider using the task scheduler for this. Define a task and set it without any triggers. That will allow you to simply "Run" (manually trigger) the task.
You can set up and/or trigger scheduled tasks using the ScheduledTasks powershell module, or you can use the GUI.
This is an old post but since I have it working fine thought it might help to share. Its the call to 'java' instead of 'javaw' that is likely your issue. Ran it out myself using my JEdit java program through powershell to launch it.
#Requires -Version 3.0
$MyDriveRoot = (Get-Location).Drive.Root
$JEditDir = $($mydriveroot + "jEdit") ;# Should be C:\jEdit or wherever you want. JEdit is a sub-directory.
$jEdit = $($JEditDir + "\jedit.jar" )
$jEditSettings = $($JEditDir + "\settings")
$JEditLogs = $($JEditDir + "\logs")
Start-Process -FilePath javaw -ArgumentList ( '-jar',"$jEdit", '-settings="$JEditSettings"' ) -RedirectStandardOutput "$JEditLogs\console.out" -RedirectStandardError "$JEditLogs\console.err"
Which you can turn into a little function and then an alias to make it easy to launch in Powershell.
If ( ( Test-Path $jedit) ) {
Function Start-JEdit() {
Start-Process -FilePath javaw -ArgumentList ( '-jar',"$jEdit", '-settings="$($mydriveroot + "jEdit\settings")"' ) -RedirectStandardOutput "$JEditLogs\console.out" -RedirectStandardError "$JEditLogs\console.err"
}
New-Alias -Name jedit -Force Start-JEdit -Description "Start JEdit programmers text editor"
}
Try this with PowerShell:
Start-Process cmd -Args /c,"java -jar MyProgram.jar" `
-WindowStyle Hidden -RSI console.out -RSE console.err
OR
Start-Process cmd -Args /c,"java -jar MyProgram.jar >console.out 2>console.err" `
-WindowStyle Hidden
This will start a detached cmd window that is hidden, and will redirect the std streams accordingly.
Old question, but since I had the same goal, I used answer from #use to acheive it.
So here is my code :)
$NAME_TASK = "myTask"
$NAME_TASKPATH = "\myPath\"
if ($args[0] -eq "-task") {
# Code to be run "detached" here...
Unregister-ScheduledTask -TaskName $NAME_TASK -TaskPath $NAME_TASKPATH -Confirm:$False
Exit
}
$Task = (Get-ScheduledTask -TaskName $NAME_TASK -TaskPath $NAME_TASKPATH -ErrorAction 'SilentlyContinue')
if ($Task) {
Write-Host "ERR: Task already in progress"
Exit 1
}
$A = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy bypass -NoProfile -Command ""$PSCommandPath -task $args"""
Register-ScheduledTask -TaskName $NAME_TASK -TaskPath $NAME_TASKPATH -Action $A | Start-ScheduledTask
The solution is to combine Start-Process with nohup:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-7.3#example-9-create-a-detached-process-on-linux
(Note: This is NOT for Windows.)

Start-Process Variables In -ArgumentList

I was wondering if someone with more expertise could help me with a little problem I'm having with using Variables in -ArgumentList when using Start-Process.
If I run the Exe without using Start-Process
.\DeploymentServer.UI.CommandLine.exe register --estNumber $Number --postcode $PostCode --password $Password
everything works fine, the command runs and the software is registered.
If I try
Start-Process .\DeploymentServer.UI.CommandLine.exe -ArgumentList "register --estNumber $Number --postcode $PostCode --password $Password" -Wait -NoNewWindow
or
$Arguments = "register --estNumber $Number --postcode $PostCode --password $Password"
Start-Process .\DeploymentServer.UI.CommandLine.exe -ArgumentList $Arguments -NoNewWindow -Wait
the command runs but is unable to register, stating that it can not match the details provided. So I'm assuming the issue lies either in the passing of the arguments to Start-Process, or -ArgumentList interpreting the variables in the string. Am I missing something really simple here? Possibly to do with the $ in the -ArgumentList?
You have a space in your $postcode, so you need to put the argument in quotes:
Start-Process .\DeploymentServer.UI.CommandLine.exe -ArgumentList "register --estNumber $Number --postcode `"$PostCode`" --password $Password" -Wait -NoNewWindow
I encountered several posts online saying to wrap a Start-Process -ArgumentList argument containing spaces in 2 layers of doubles quotes, escaping the inner double quotes with a back tick `, but at least in the context I needed it, that didn't work. I found a conceptually similar solution which did work, however, i.e. a set of single quotes on the outside and a "traditional" backslash escape sequence on inner double quotes. I was guided to using this approach per this PowerShell issue post:
https://github.com/PowerShell/PowerShell/issues/5576
This example works for me (running a PS Start-Process command from cmd.exe):
C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe Start-Process -FilePath 'C:\Program Files (x86)\Example\Test.exe' -ArgumentList 'arg1','\"arg 2 w spaces\"','arg3'

Resources