Find exit code for executing a cmd command through PowerShell - powershell

I am using a silent installation command to install software. I am running this command from PowerShell 3.0.
$silentInstall = C:\Users\Admin\Documents\Setup-2.0.exe exe /s /v"EULAACCEPTED=\"Yes\" /l*v c:\install.log /qn"
Invoke-Expression $silentInstall
This runs the command which installs the software, but it does not wait for it to complete and goes ahead with the next lines of code. I want to have control over the installation so that I would know if it's completed or not.
How do I get an error code for the Invoke-Expression cmdlet so that I can get to know if the cmd executed successfully or not?

It depends on how the EXE file runs - sometimes it will kick off a separate process and return immediately, and in such cases this usually works -
$p = Start-Process -FilePath <path> -ArgumentList <args> -Wait -NoNewWindow -PassThru
$p.ExitCode
Otherwise this usually works -
& <path> <args>
$LASTEXITCODE
Or sometimes this -
& cmd.exe /c <path> <args>
$LASTEXITCODE

It looks like you're running an MSI installer. When running from the console, control is immediately returned while MSI forks a new process to run the installer. There is no way to change this behavior.
What you'll probably need to do is use Get-Process to find a process named msiexec, and wait for it to finish. There is always an msiexec process running, which handles starting new installers, so you'll need to find the msiexec process that started after your install began.
$msiexecd = Get-Process -Name 'msiexec'
C:\Users\Admin\Documents\Setup-2.0.exe exe `
/s `
/v"EULAACCEPTED=\"Yes\" /l*v c:\install.log /qn"
$myMsi = Get-Process -Name 'msiexec' |
Where-Object { $_.Id -ne $msiexecd.Id }
$myMsi.WaitForExit()
Write-Verbose $myMsi.ExitCode

You shouldn't need to use Invoke-Expression:
& C:\Users\Admin\Documents\Setup-2.0.exe /s /vEULAACCEPTED=Yes /l*v C:\install.log /qn

Related

Is there a way for powershell to get the output from a console app and save it in a text file? [duplicate]

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.)

Invoke-Expression to Launch .ps1 scripts

I am trying to make a service that scans a folder. When it detects certain files it renames them and then creates variables for the rest of the script. This all works fine, but I am trying to find a way to run this script so that the subsequent windows that it launches are minimized. I've tried the below, but the 2 scripts still launch a window each that flashes on the screen.
I have tried converting the file into an EXE and a service but then it refuses to work. Hoping for a quick one-liner if anyone can help?
Set-Location -Path C:\scripts\ICVT
while ($true) {
invoke-expression 'cmd /c start powershell -WindowStyle Minimized -Command {.\file_checker.ps1}';
Start-Sleep 5
invoke-expression 'cmd /c start powershell -WindowStyle Minimized -Command {.\kill_checker.ps1}';
Start-Sleep 5
}
Sounds like you need to use Start-Job to run the script in the background. Simply replace the Invoke-Expression with Start-Job:
Set-Location -Path C:\scripts\ICVT
while ($true) {
Start-Job -FilePath '.\file_checker.ps1}';
Start-Sleep 5
Start-Job -FilePath '.\kill_checker.ps1}';
Start-Sleep 5
}
It will also accept script blocks as well. See about_jobs for more information on running and managing background jobs.

Calling a powershell script from InstallShield project

I'm having a weird problem.
I have an InstallShield project (which creates setup.exe) that contains a custom action item - calling a powershell script.
All the script does is to install 3 adobe reader updates (1 exe file and 2 msp files) on top of the already installed Adobe Reader 11.0.0.
When I'm calling the script my self - it works fine.
However, after the setup.exe finishes, it seems like only one update (the exe file) was really installed (the adobe reader version after the install is 11.00.10 which is the result of running only the exe file..).
All 3 adobe updates sit in the same folder and the powershell script first sets it location to this folder.
When running the updates manually after the installation - it also works fine and updates it to 10.00.22 (what it should be).
Any ideas why is this happening?
Here's my powershell script:
Set-Location "C:\myProject\adobeUpdates"
Start-Process .\AdbeRdr11010_en_US.exe -ArgumentList '/q /norestart /sPB /rs /msi' -WindowStyle hidden -Wait
ping 1.1.1.1 -n 1 -w 10000 # Tried to add a delay but wasn't helpful
Start-Process -FilePath “AdbeRdrUpd11021.msp” -ArgumentList '/qn' -Wait
Start-Process -FilePath “AdbeRdrUpd11022_incr.msp” -ArgumentList '/qn' -Wait
Thank you very much
Solved it, this is the working script:
Set-Location "C:\myProject\adobeUpdates"
Start-Process .\AdbeRdr11010_en_US.exe -ArgumentList '/q /norestart /sPB /rs /msi' -WindowStyle hidden -Wait
ping 1.1.1.1 -n 1 -w 10000
Start-Process .\AdbeRdrUpd11021.msp -ArgumentList '/qn' -Wait
Start-Process .\AdbeRdrUpd11022_incr.msp -ArgumentList '/qn' -Wait
I'm not sure what is the different and would love someone to explain but anyway it works just fine now.

Invoke-Expression Not Running Command

I am pulling in a text file that was previously created that simply lists the KB numbers for all currently installed Windows Updates. I am trying to pull this file in and call wusa.exe and pass it the KB number to perform an uninstallation.
$a = Get-Content c:\hotfixid.txt
foreach ($kb in $a) {
$command = 'cmd /c wusa.exe /uninstall /kb:' + $kb.Trim().Substring(2) + ' /quiet /norestart'
#Write-Host $command
Write-Host "Currently uninstalling $kb"
Invoke-Expression -Command:$command
}
If I use
Write-Host $command
and copy that directly to a run dialog box in Windows, it completes successfully.
What happens when I run it in a PowerShell script is that it only outputs the Write-Host portions one after the other in about 2 seconds. I do not see any command windows opening and I don't see it actually DOING anything. I am running the PowerShell script 'As Administrator' with an unrestricted execution policy. I have also tried adding 'runas' to the $command to call the CMD window each time with administrative privileges and it made no difference. Calling it via Invoke-Command as well makes no difference.
PowerShell can run most commands directly w/o too much trouble.
Using Invoke-Expression just complicates matters, as does Invoke-Command or Start-Process, because you need to get the quoting right, and pass the arguments in a slightly unnatural way.
You can even skip running cmd.exe most of the time.
Try the following:
wusa.exe /uninstall "/kb:$($kb.Trim().Substring(2))" /quiet /norestart

PowerShell - Start-Process and Cmdline Switches

I can run this fine:
$msbuild = "C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe"
start-process $msbuild -wait
But when I run this code (below) I get an error:
$msbuild = "C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe /v:q /nologo"
start-process $msbuild -wait
Is there a way I can pass parameters to MSBuild using start-process? I'm open to not using start-process, the only reason I used it was I needed to have the "command" as a variable.
When I have
C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe /v:q /nologo
on a line by itself, how does that get handled in Powershell?
Should I be using some kind of eval() kind of function instead?
you are going to want to separate your arguments into separate parameter
$msbuild = "C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe"
$arguments = "/v:q /nologo"
start-process $msbuild $arguments
Using explicit parameters, it would be:
$msbuild = 'C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe'
start-Process -FilePath $msbuild -ArgumentList '/v:q','/nologo'
EDIT: quotes.
Warning
If you run PowerShell from a cmd.exe window created by Powershell, the 2nd instance no longer waits for jobs to complete.
cmd> PowerShell
PS> Start-Process cmd.exe -Wait
Now from the new cmd window, run PowerShell again and within it start a 2nd cmd window:
cmd2> PowerShell
PS> Start-Process cmd.exe -Wait
PS>
The 2nd instance of PowerShell no longer honors the -Wait request and ALL background process/jobs return 'Completed' status even thou they are still running !
I discovered this when my C# Explorer program is used to open a cmd.exe window and PS is run from that window, it also ignores the -Wait request.
It appears that any PowerShell which is a 'win32 job' of cmd.exe fails to honor the wait request.
I ran into this with PowerShell version 3.0 on windows 7/x64
I've found using cmd works well as an alternative, especially when you need to pipe the output from the called application (espeically when it doesn't have built in logging, unlike msbuild)
cmd /C "$msbuild $args" >> $outputfile
Unless the OP is using PowerShell Community Extensions which does provide a Start-Process cmdlet along with a bunch of others. If this the case then Glennular's solution works a treat since it matches the positional parameters of pscx\start-process : -path (position 1) -arguments (positon 2).