$GAMCheck = invoke-command -ScriptBlock { C:\GAMADV-XTD3\GAM.exe version checkrc }
If ($GAMCheck) {
$current = $GAMCheck.split(":")[19].Trim()
$latest = $GAMCheck.split(":")[21].Trim()
If ($LASTEXITCODE -eq 1) {
Try {
$NeedUpGradeCode = $LASTEXITCODE
$client = new-object System.Net.WebClient
$client.DownloadFile("https://github.com/taers232c/GAMADV-XTD3/releases/download/v$latest/GAMadv-xtd3-$latest-windows-x86_64.msi", "C:\Temp\GAMadv-xtd3-$latest-windows-x86_64.msi")
Start-Process -Filepath "C:\Temp\GAMadv-xtd3-$latest-windows-x86_64.msi" -ArgumentList "/passive" | Wait-Process -Timeout 75
Remove-Item "C:\Temp\GAMadv-xtd3-$latest-windows-x86_64.msi"
$GAMCheck = $null
$GAMCheck = invoke-command -ScriptBlock { C:\GAMADV-XTD3\GAM.exe version checkrc }
$newCurrent = $GAMCheck.split(":")[19].Trim()
$resultsarray = [PSCustomObject]#{
CurrentVersion = $current
LatestVersion = $latest
NeedUpgradeCode = $NeedUpGradeCode
Upgraded = $true
NewCurrent = $newCurrent
AfterUpgradeCode = $LASTEXITCODE
}
}
Catch {
Write-Warning "Problem with site or command. Maybe go to https://github.com/taers232c/GAMADV-XTD3/releases and download the current GAM and then install GAM in C:\GAMADV-XTD3\ again"
}
}
}
lately I have been noticing that the | Wait-process 75 above is causing an error.
If I run the command with out it everything is fine.
Is there another way to wait for the install ?
To launch a process with Start-Process and wait for it to exit, use the -Wait switch.
Piping a Start-Process call to Wait-Process would only work as intended if you included the -PassThru switch, which makes Start-Process - which by default produces no output - emit a System.Diagnostics.Process instance representing the newly launched process, on whose termination Wait-Process then waits.
Note that, surprisingly, the behavior of these two seemingly equivalent approaches is not the same, as discussed in GitHub issue #15555.
Related
I am trying to install software (executable) on several servers with various versions of PowerShell.
Normally below code works with no issue on PS4 and up. On PS3 it does not install anything, nor does it produce any errors on the remove server eventviewer. It treated as success by printing out "... -- installation succeeded" and exits. I've googled about and read that perhaps Start-Process is the culprit in PS3.
Begin {
$uncpath="\\remoteserveruncpath\" #"
$exe_parameter1 = "centralserver.com"
$creds = Get-Credential -Message "Password: " -Username "$($env:userdnsdomain)\$($env:username)"
}
Process {
$dnshostname = "server1","server2","server3"
ForEach ($server in $dnshostname) {
Invoke-Command -ComputerName $server -ScriptBlock {
param($server_int,$exe_parameter1_int,$uncpath_int,$creds_int)
(New-Object -ComObject WScript.Network).MapNetworkDrive('Z:',"$($uncpath_int)", $false, "$($creds_int.Username)", "$($creds_int.GetNetworkCredential().Password)")
$arguments = "/param_1=$exe_parameter1_int /param_2=$($server_int.ToLower()) /start-program=1 /S"
If((Start-Process "Z:\installer.exe" -ArgumentList $arguments -Wait -Verb RunAs).ExitCode -ne 0) {
Write-Host "$server_int -- installation succeeded"
} else {
Write-Error "$server_int -- installation failed"
}
} -ArgumentList $server,$exe_parameter1,$uncpath,$creds;
}
}
Any advice? Many thanks!
Without -PassThru, Start-Process produces no output, so accessing .ExitCode effectively returns $null, always.
And since $null -ne 0 is always $true, your code always indicates success.
In order to get the installer command's true exit code, you therefore need to use the following (note the addition of -PassThru):
if ((Start-Process -PassThru 'Z:\installer.exe' -ArgumentList $arguments -Wait -Verb RunAs).ExitCode -ne 0) { ... }
I'm trying to run some of my custom code asynchronously in Powershell. The following tries to check for updates in a background thread:
Function CheckUpdates($manager)
{
. "$PSScriptRoot\..\commands\upgradecmd.ps1";
$upgradeCmd = New-Object UpgradeCmd -ArgumentList $manager;
[bool]$upgradesAvailable = $false;
try
{
$Global:silentmode = $true;
$upgradeCmd.preview = $true;
Start-Job -ArgumentList $upgradeCmd -ScriptBlock { param($upgradeCmd) $upgradeCmd.Run(); };
Get-Job | Receive-Job;
$upgradesAvailable = $upgradeCmd.upgradesAvailable;
}
finally
{
$Global:silentmode = $false;
}
if ($upgradesAvailable)
{
WriteWarning "Upgrades detected.";
WriteWarning "Please, run the upgrade command to update your Everbot installation.";
}
}
The problem is that inside the job (in the ScriptBlock), PS doesn't recognize anything about my custom "Run()" method, so it doesn't know how to call it. I've tried to "include" the class in the job using the -InitializationScript parameter with little success.
After searching the web, it seems that the way to do this is using PS Jobs, there's no thread handling in PS or something like "async". The point is that I just want to run a method of some class of my PS code asynchronously.
Why don't you dot source inside the scriptblock?
Function CheckUpdates($manager)
{
try
{
$Global:silentmode = $true
$Scriptblock = {
param($manager)
. "<absolutepath>\upgradecmd.ps1"; #better to replace this with absolute path
$upgradeCmd = New-Object UpgradeCmd -ArgumentList $manager;
[bool]$upgradesAvailable = $false
$upgradeCmd.preview = $true
$upgradeCmd.Run()
$upgradesAvailable = $upgradeCmd.upgradesAvailable;
Return $upgradesAvailable
}
Start-Job -ArgumentList $manager -ScriptBlock $Scriptblock
$upgradesAvailable = #(Get-Job | Wait-Job | Receive-Job) #This will probably not be so cut and dry but you can modify your code based on the return value
}
finally
{
$Global:silentmode = $false;
}
if ($upgradesAvailable)
{
WriteWarning "Upgrades detected.";
WriteWarning "Please, run the upgrade command to update your Everbot installation.";
}
}
I have added a Wait-Job as well before receiving it so your script would wait for the jobs to finish.
I'm currently working on a PowerShell script that includes the line
$process1 = Start-Process -FilePath ($exePath1) -PassThru -RedirectStandardError ($logPath1)
Which starts a long-running process and redirects the process' StandardError to a log file. My problem is that this also apparently interferes with ExitCode.
$process1.ExitCode
returns null after the process has exited. If I remove "RedirectStandardError ($logPath1)" then ExitCode returns the value my dummy-program is expected to return.
Should I be doing something different? I'm hoping to be able to start the process (redirecting the StandardError to the log file for diagnostics), wait a few seconds to make sure it doesn't crash, and retrieve the ErrorCode in the event that it does.
If you need to wait for the process and you're not running it as a different user:
$StartParams = #{
FilePath = $exePath1
RedirectStandardError = $logPath1
PassThru = $True
Wait = $True
}
$ReturnCode = (Start-Process #StartParams).ExitCode
Since it's long-running, here's an alternative method with PSJobs:
$Job = Start-Job -ScriptBlock {
$StartParams = #{
FilePath = $exePath1
RedirectStandardError = $logPath1
PassThru = $True
Wait = $True
}
(Start-Process #StartParams).ExitCode
}
If ($Job.State -eq 'Completed')
{
$ReturnCode = Receive-Job -Job $Job
}
I am using the following code to build some projects using PowerShell but this is taking nearly 30 minutes of time and some times it is taking more than that too, here is the PowerShell script I had when I execute it locally it is getting build in
$sourceDirectory = "D:\Service"
$SolutionToBuild = #("Solution1.sln","Solution2.sln","Solution3.sln","Solution4.sln")
$projectFiles = Get-ChildItem -Path "$sourceDirectory" -Filter *.sln -Recurse
foreach($solution in $SolutionToBuild)
{
foreach($projectFile in $projectFiles)
{
if($projectFile.Name -eq $solution)
{
write-host $projectFile.Name;
$SlnFilePath = $projectFile.FullName
$BuildParameters = """$SlnFilePath"" /Build Release|x86"
$CleanParameters = """$SlnFilePath"" /Clean Release|x86"
Start-Process -FilePath $vsPath -ArgumentList $CleanParameters -Wait
Start-Process -FilePath $vsPath -ArgumentList $BuildParameters -Wait
break;
}
}
}
So can one let me know why this taking much time
A minor change to the code will affect the total performance. Instead of using Start-Process, you may use the following code:
$buildProcInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo -ArgumentList "devenv.exe", $buildArgs
try { $buildProcess = [System.Diagnostics.Process]::Start($buildProcInfo) }
catch {
Write-Host "Error"
# exit?
}
$buildProcess.WaitForExit()
So here's a sample of what I'm trying to do:
Invoke-Command [Connection Info] -ScriptBlock {
param (
[various parameters]
)
Start-Process [some .exe] -Wait
} -ArgumentList [various parameters]
It connects to the other machine just fine, and launches the process fine. The problem is it doesn't wait for the process to complete before moving on. This causes issues. Any ideas?
Quick edit: why does the -Wait parameter fail when running the process remotely?
I ran into this once before, and IIRC, the workaround was:
Invoke-Command [Connection Info] -ScriptBlock {
param (
[various parameters]
)
$process = Start-Process [some .exe] -Wait -Passthru
do {Start-Sleep -Seconds 1 }
until ($Process.HasExited)
} -ArgumentList [various parameters]
This is the problem with Powershell version 3, but not version 2 where -Wait works as it should.
In Powershell 3 .WaitForExit() does the trick for me:
$p = Start-Process [some .exe] -Wait -Passthru
$p.WaitForExit()
if ($p.ExitCode -ne 0) {
throw "failed"
}
Just Start-Sleep until .HasExited - doesn't set .ExitCode, and it's usually good to know how your .exe finished.
You can also work around this by using the System.Diagnostics.Process class. If you do not care about the output you can just use:
Invoke-Command [Connection Info] -ScriptBlock {
$psi = new-object System.Diagnostics.ProcessStartInfo
$psi.FileName = "powershell.exe"
$psi.Arguments = "dir c:\windows\fonts"
$proc = [System.Diagnostics.Process]::Start($psi)
$proc.WaitForExit()
}
If you do care you can do something similar to the following:
Invoke-Command [Connection Info] -ScriptBlock {
$psi = new-object System.Diagnostics.ProcessStartInfo
$psi.FileName = "powershell.exe"
$psi.Arguments = "dir c:\windows\fonts"
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$proc = [System.Diagnostics.Process]::Start($psi)
$proc.StandardOutput.ReadToEnd()
}
This will wait for the process to complete and then return the standard output stream.