Start-Command PowerShell v3.0, does nothing - powershell

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

Related

Wait-Process does not always work with Start-Process

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

Get fail message from a function on powershell script

I am not quite sure how to explain my problem, but I have a function that installs Office, imagine the person that runs this script does not have internet connection or does not have enough space on her hard drive. I have the XML file set to hide the setup interface so the user can't see the installation process. Just to be clear all my code works fine, just want add this feature so that if something goes wrong while the user runs the script I know where the error was.
This is my function:
Function Install-Office365OfficeProducts{
Write-Host ""
Start-Sleep -Seconds 5
Write-Host "Installing Office 365 ProPlus..."
# Installing Office 365 ProPlus
Install-Office365Product -path "$PSScriptRoot\setup.exe" -xmlPath "$PSScriptRoot\InstallO365.xml"
This is what I have tried:
if (Install-Office365OfficeProducts -eq 0) {
Write-Host "FAILED"}
I am very confused, I thought that a function that runs with no error returns 1 and when it runs with errors returns 0.
Also have tried to put the code like this:
try {
Install-Office365Product -path "$PSScriptRoot\setup.exe" -xmlPath "$PSScriptRoot\InstallO365.xml"
} catch {
Write-Host "Failed!"
}
EDIT:
Basically i want to be shown an error if the Office setup is not finished...
#Thomas
Function Install-Office365Product{
Param (
[string]$path,
[string]$xmlPath
)
$arguments = "/configure `"$xmlPath`""
try{
Start-Process -FilePath "$path" -ArgumentList "$arguments" -Wait -NoNewWindow -ErrorAction Stop
}catch{
Write-Host "It was not possible to install the product!"
}
}
Your try/catch-block inside Install-Office365OfficeProducts is useless, because Install-Office365Product will not throw anything, except you pass wrong arguments. The try/catch-block inside Install-Office365Product will most likely also not catch anything. But you can of course evaluate the return code of your installer called with Start-Process:
function Install-Office365Product {
Param (
[string]$path,
[string]$xmlPath
)
$arguments = "/configure `"$xmlPath`""
$process = Start-Process -FilePath "$path" -ArgumentList "$arguments" -Wait -PassThru -NoNewWindow
if ($process.ExitCode -eq 0) {
Write-Host "Installation successful"
} else {
Write-Host "Installation failed"
}
}
Instead of writing to stdout, you can of course also throw an exception and handle it later in a higher function.

PowerShell script executing batch script on remote server

I am executing a PowerShell script which executes a batch script at remote server. But in PowerShell script I am not able to handle any failure that could occur in batch script. The batch script is having exit %ERROR_CODE% at the end.
Please let me know how I can catch any error occurred in batch script in the calling PowerShell script.
My PowerShell script is like:
$DBServer = $args[0]
$CustName = $args[1]
$FullBackupPath = $args[2]
$command = "cmd.exe /c DbBackupBatch.cmd " + $FullBackupPath + " " + $CustName
$script = 'Invoke-Expression -Command "' + $command + '"'
$scriptblock = [scriptblock]::Create($script)
try {
Invoke-Command -ComputerName $DBServer -Authentication NegotiateWithImplicitCredential -ErrorAction Stop -ScriptBlock $scriptblock
exit 0
} catch {
$message = $_.Exception.Message
Write-Host $_.Exception.Message
# While executing a Java programs, we get message as below -
# Picked up JAVA_TOOL_OPTIONS: -Xms512m -Xmx512m
# This message is treated as error message by PowerShell, though it is not an error
if (($message.Length -lt 50) -and ($message.Contains('Picked up JAVA_TOOL_OPTIONS:'))) {
exit 0
} else {
Write-Host $_.Exception.Message
exit 1
}
}
Give this a whirl:
$remoteReturnValue = Invoke-Command -ComputerName "DV1IMPSSDB01" -Authentication NegotiateWithImplicitCredential -ScriptBlock {
$cmd = Start-Process "cmd.exe" -Wait -PassThru -ArgumentList "/c timeout 5"
$cmdExitCode = $cmd.ExitCode
if ($cmdExitCode -eq 0) {
return "Success"
}
else {
return "Wuh-oh, we have had a problem... exit code: $cmdExitCode"
}
}
Write-Host $remoteReturnValue -ForegroundColor Magenta
Whatever you're trying to do in PowerShell, Invoke-Expression is practically always the wrong approach. PowerShell can execute batch files all by itself, so you can run DbBackupBatch.cmd directly, without Invoke-Expression and even without cmd /c.
Try something like this:
$DBServer = $args[0]
$CustName = $args[1]
$FullBackupPath = $args[2]
try {
Invoke-Command -ComputerName $DBServer -ScriptBlock {
$output = & DbBackupBatch.cmd $args[0] $args[1] 2>&1
if ($LastExitCode -ne 0) { throw $output }
} -ArgumentList $FullBackupPath, $CustName -Authentication NegotiateWithImplicitCredential
} catch {
Write-Host $_.Exception.Message
exit 1
}
exit 0

Call a remote script from another with multiple parameters not working

I am trying to create a script that will take input (hardcoded values for now) and call an install PS script and run it on multiple servers. I am using a PSSession and Invoke-Command(see below). The below runs, but does nothing. It doesn't seem to call the other script. Beyond getting it to actually install, I need to know if it was successful or not. I'm pretty novice at Powershell, so any hints/help/suggestions would be great. The below is wrapped in a ForEach to loop the servers with $Computer
Try
{
$session = New-PSSession -ComputerName App02 -Credential $cred
$sourceInstall = $sourceFolder + 'Install\Install.ps1'
Invoke-Command -Session $session -ScriptBlock{param($serviceName, $installFolder, $sourceFolder, $Action, $username, $password) $sourceInstall} -ArgumentList ($ServiceName, $installFolder, $sourceFolder, $Action, $username, $password)
}
Catch
{
$Filename = "Error.txt"
Write-Output "ERROR: Partial Service Deployment. See error log file(s)"
Add-Content $Filename $_.Exception.Message
}
Get-PSSession | Remove-PSSession
You can use it without $Using statement in any version of PowerShell.But pass that too as an argument.
Eg:-
Invoke-Command -ScriptBlock
param($Name)
& $Command $Name
} -ArgumentList 'Get-Process','Notepad'
But you have to pass the arguments positional when using the call operator '&'
Get-Help About_Parameters
https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_parameters
Regards,
Kvprasoon

Powershell start-process -wait parameter fails in remote script block

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.