Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Question
Tuesday, November 19, 2013 7:19 PM
I am using the following code to run a script that exists on a remote computer:
$Sess = New-PSSession -ComputerName $Computers
Invoke-Command -Session $Sess -ScriptBlock {Start-Process -FilePath powershell.exe -ArgumentList "C:\Temp\BuildFiles\PreRequisiteInstall.ps1"} -AsJob
$Computers is an array of computer names.
This successfully runs the script on the remote computer. However, I have several start-process... -wait commands like the following:
Start-Process -FilePath powershell.exe -ArgumentList "$SourceDir\ServerRolesFeatures.ps1" -Wait
that do not wait. If I run the script directly on the local computer they wait. I'd like to be able to run this remotely on several computers and I shouldn't have to add get-process checks and balances, I would expect -Wait to wait.
All replies (17)
Tuesday, November 26, 2013 5:46 PM ✅Answered | 1 vote
Could you try the below:
$Sess = New-PSSession -ComputerName $Computers
Invoke-Command -Session $Sess -ScriptBlock {$process=Start-Process -FilePath powershell.exe -ArgumentList "C:\Temp\BuildFiles\PreRequisiteInstall.ps1" -passthru ; $process..WaitForExit()}
On each process we have this this waitforExit() method...I think that can be useful here. Perhaps you can use it in the above way or incorporate that in some other way
Hope it helps
Knowledge is Power{Shell}.
Wednesday, November 20, 2013 4:01 AM
From what I can deduce is your scripts might not be running at all on the remote machines......so they are not waiting.....because
The Start-Process cmdlet which you want to use has a variable in it.....how are you passing it to the remote sessions ?
Start-Process -FilePath powershell.exe -ArgumentList "$SourceDir\ServerRolesFeatures.ps1" -Wait
In order to pass this value to the remote session use -agrumentlist parameter on the Invoke-Command:
$SourceDir = "C:\Scripts" #for example I have it intialized in my sessionInvoke-Command -Session $Sess -ScriptBlock {param($SourceDir) Start-Process -FilePath powershell.exe -ArgumentList "$SourceDir\ServerRolesFeatures.ps1" -Wait } -ArgumentList $SourceDir
P.S. - If you are in PowerShell v3 then you can use $using:SourceDir inside the scriptblock to directly access that variable
Hope it helps
Knowledge is Power{Shell}.
Wednesday, November 20, 2013 10:06 AM
Thanks for the response! I am sorry, I was not clear in my original post. The command
Start-Process -FilePath powershell.exe -ArgumentList "$SourceDir\ServerRolesFeatures.ps1" -Wait
is one of several commands in the remote script. This is the command that I am concerned with because it is not waiting. The remote script definitely runs - I can see the Powershell process start in task manager on the remote computer, as well as 4 others running simultaneously, spawned by it at almost the same time.
Each of those child scripts logs output. When I run the script remotely, all of the log entries are out of sequence, and failures occur because subsequent scripts are dependent on preceding scripts.
If I manually execute the script on the remote computer, each child script is launched sequentially - the subsequent scripts don't start until the preceding script is finished. This is the expected behavior.
Wednesday, November 20, 2013 1:03 PM
Are you running this -AsJob?
Regards Chen V [MCTS SharePoint 2010]
Wednesday, November 20, 2013 2:06 PM
Yes, -AsJob. I also tried using Start-Job instead of Start-Process - same result.
Wednesday, November 20, 2013 2:12 PM
How about using a Wait Job?
PS C:\> $s = New-PSSession Server01, Server02, Server03
PS C:\>Invoke-Command -Session $s -ScriptBlock{Start-Job -Name Date1 -ScriptBlock {Get-Date}}
PS C:\>$done = Invoke-Command -Session $s -Command {Wait-Job -Name Date1}
PS C:\>$done.Count
3
Regards Chen V [MCTS SharePoint 2010]
Wednesday, November 20, 2013 8:10 PM
Thank you, but the problem is within the script that is executing on the remote server. In my test I only even had one server in the list of servers. Using your example I will try:
PS C:\> $s = New-PSSession Server01, Server02, Server03
PS C:\>Invoke-Command -Session $s -ScriptBlock{Start-Job -Name Date1 -ScriptBlock {start-process -filepatch powershell.exe -argumentlist "c:\temp\buildfiles\serverrolesfeatures.ps1" -wait;start-process -filepatch powershell.exe -argumentlist "c:\temp\buildfiles\IISConfig.ps1" -wait;}}
PS C:\>$done = Invoke-Command -Session $s -Command {Wait-Job -Name Date1}
because that would most accurately and succinctly represent what I'm trying to do - execute a start-process...-wait command within a script on the remote server.
Thank you!
Wednesday, November 20, 2013 8:12 PM
If you run -ASJob this the way I can think of. Let me know the output so that I can also try in other way.
Regards Chen V [MCTS SharePoint 2010]
Thursday, November 21, 2013 1:41 PM
That behaved the same way. Powershell was already running on the remote system. I ran the three commands (corrected for -filepatch typo, using only one remote server in the list of servers). When the command executed, I could see two new instances of Powershell running. The first script installs IIS, the 2nd script runs IIS configuration commands. The 2nd script completed and generated failures because it was configuring elements of a service that were not yet installed.
The 3rd command in this set is really irrelevant, but it does expose the problem. I don't care if the job started on my local computer to execute the parent script on the remote computer waits - I'd prefer that it completes and move on.
The state of $done in this case shows completed before both start-process processes have finished executing. I'm guessing the job to invoke-command the scriptblock finishes because both elements of the script-block are reporting as finished, since they are not waiting for their respective processes to finish. This is where my problem lies.
The commands executed in the scriptblock clearly have the -wait parameter, but they are not waiting. The script I'm trying to execute is behaving the same way. I can see the powershell processes that were invoked are clearly still running. The -wait parameter seems ineffective. I can run the script locally on the remote server and the script waits as expected. Running it remotely, it does not wait.
Saturday, November 23, 2013 10:01 PM
I don't mean to be a bumpety bump, but I would like to understand why this isn't working. If I have been unclear in any of my description, I am more than happy to elaborate - but I think the problem is clear. I am using invoke-command against remote computers to run a script that calls several start-process... -wait scripts and when I run it remotely this way, the start-process commands do not wait. If I run the script locally, the start-process commands wait as expected.
Thank you so much!
Tuesday, November 26, 2013 11:15 AM
Hi,
I found some useful articles for you.
An A-Z Index of Windows PowerShell 2.0 commands
New-object on remote session failing
http://powershell.org/wp/forums/topic/new-object-on-remote-session-failing/
Regards,
Mike
Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
Tuesday, November 26, 2013 2:55 PM
$SourceDir is a variable that is defined within the script that is running on the remote computer. All the commands within that script execute, they just don't wait. One of the scripts installs IIS through an Add-WindowsFeature command. The next script configures IIS, first using Import-Module WebAdministration. The second script reports errors because it is running before IIS installs and cannot import a module that does not exist. Once the 5 instances of powershell stop running, IIS is installed.
The issue has nothing to do with passing variables - the script that is executed remotely runs just fine, but the start-process commands do not wait. I repeat, they execute successfully, but they do not wait. You can see in my test above that using invoke command to call each of the child scripts behaves the same way.
Tuesday, November 26, 2013 4:33 PM
I'd like to suggest few alternatives that might work for you.
Start-Job <Job params here> | Wait-Job # this will force it to wait for it to complete
If you have PowerShell v3 then you can give workflow a shot, the "sequence" keyword will force the execution in the sequence specified. Below link might be helpful
http://blogs.technet.com/b/heyscriptingguy/archive/2012/12/26/powershell-workflows-the-basics.aspx
Hope it helps
Knowledge is Power{Shell}.
Tuesday, November 26, 2013 4:50 PM
Start-job might get around this issue, but I didn't want to shift gears and go through another round of script rewrites and testing when what I've got already works when run locally. Full code below (more than relevant, but I don't want to leave anything out).
I'm sorry if I haven't explained it clearly, but it can be difficult to include details that seem obvious to me since I've had my head wrapped around it for a while already.
Again, the command runs successfully on my computer, the remote script runs successfully on the target computer, and all of the scripts run from the main script are executed. The main script does not wait for the scripts called as 'start-process ... -wait'. When I run the main script (PreRequisiteInstall.ps1) locally, it waits as expected. The problem only manifests when I run the main script through the invoke-command on a remote computer.
Command I'm running in Powershell console on my computer:
$Sess = New-PSSession -ComputerName $Computers
Invoke-Command -Session $Sess -ScriptBlock {Start-Process -FilePath powershell.exe -ArgumentList "C:\Temp\BuildFiles\PreRequisiteInstall.ps1"} -AsJob
PreRequisiteInstall.ps1 (located in c:\temp\buildfiles on the remote computer - i.e. the 'main' script):
# Script Specific Variables
$Title = "PreRequisite Install"
$SourceDir = "c:\Temp\BuildFiles"
$CHATSVC = "ChatAccount"
# SendMail Variables
$To = "[email protected]"
$From = "$($env:COMPUTERNAME)@domain.com"
$Subject = "CHAT Server Pre-Installation Results for"
$SMTP = "mailrelay.domain.com"
# These variables are standard variables used to support various logging and error handling functions
$Error.clear()
$ErrorAction = "SilentlyContinue"
$LogDir = "c:\Temp\BuildLogs"
If (!(Test-Path $LogDir)) {New-Item $LogDir -type directory}
$OutFile = "$LogDir\SetupLog.log"
$ErrorFile="$LogDir\ErrorLog.log"
# Function to Log Useful Information. Accepts "Error" as a first argument to indicate an error to be logged to the error file.
# Any other first argument will direct output to the setup log. The second argument is the data to be logged.
Function LogInfo ($LogType, $LogData) {
If ($LogType -eq "Error") {out-file -FilePath $ErrorFile -Append -InputObject $LogData}
Else {out-file -FilePath $OutFile -Append -InputObject $LogData}
}
# Create Incremented LogFiles
If (Test-Path $OutFile) {
$i = 0
While (Test-Path "$($OutFile -replace '\.log')_v$i.log") {$i++}
Rename-Item $OutFile "$($OutFile -replace '\.log')_v$i.log"
}
If (Test-Path $ErrorFile) {
Rename-Item $ErrorFile "$($ErrorFile -replace '\.log')_v$i.log"
}
LogInfo "Info" "$((Get-Date).ToString())`t * Beginning $Title!"
LogInfo "Info" "********"
# Install OS Rules, Role Services, and Features
Start-Process -FilePath powershell.exe -ArgumentList "$SourceDir\ServerRolesFeatures.ps1" -Wait
# Configure IIS App Pool Defaults and Server Settings
Start-Process -FilePath powershell.exe -ArgumentList "$SourceDir\IISConfig.ps1" -Wait
# Install DotNet Framework 4
If (!(test-path "$($env:SYSTEMROOT)\Microsoft.NET\Framework\v4.0.30319\Microsoft.Build.Framework.dll")) {
$Result = Start-Process -FilePath "$SourceDir\dotNetFx40_Full_x86_x64.exe" -ArgumentList "/q /norestart" -PassThru -Wait -Verb RunAs
If ($Result.ExitCode -eq 0) {LogInfo "Info" "$((Get-Date).ToString())`t * .NET Framework Installed Successfully!"}
Else {Write-Error "$((Get-Date).ToString())`t * DotNet Installation Failed! Exit Code: $Result.ExitCode"}
} Else {LogInfo "Info" "$((Get-Date).ToString())`t * .NET Framework Already Installed!"}
LogInfo "Info" "********"
# SQL Native Client Installation
Start-Process -FilePath powershell.exe -ArgumentList "$SourceDir\SQLInstall.ps1" -Wait
# SQL Server Connection Check
Start-Process -FilePath powershell.exe -ArgumentList "$SourceDir\CheckSQL.ps1" -Wait
# Set Current User IE Security Setting to facilitate Consona Installation
$Result = & reg.exe add "HKCU\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_LOCALMACHINE_LOCKDOWN" /v "iexplore.exe" /t REG_DWORD /d 0 /f
If ($Result -match "Successfully") {LogInfo "Info" "$((Get-Date).ToString())`t * IE Security Setting Configured Successfully!"}
Else {Write-Error "$((Get-Date).ToString())`t * IE Security Configuration Failed! Exit Code: $Result"}
LogInfo "Info" "********"
# Add Domain CHAT Service Account to Local Admin Group
$UFound = $False
(([ADSI]"WinNT://.,computer").PSBase.Children.Find("Administrators")).PSBase.Invoke("Members") | %{
If ($_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) -match $CHATSVC) {$UFound = $True}}
If (!($UFound)) {
([ADSI]"WinNT://./Administrators,group").PSBase.Invoke("Add",([ADSI]"WinNT://domain/$CHATSVC").path)
(([ADSI]"WinNT://.,computer").PSBase.Children.Find("Administrators")).PSBase.Invoke("Members") | %{
If ($_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) -match $CHATSVC) {LogInfo "Info" "$((Get-Date).ToString())`t * CHAT Service Account Added Successfully!"}}}
Else {LogInfo "Info" "$((Get-Date).ToString())`t * Chat Service Account already added to Administrators!"}
LogInfo "Info" "********"
# Grant SeBatchLogonRight to CHAT Service Account
Start-Process -FilePath $SourceDir\ntrights.exe -ArgumentList "-u cdomain\$CHATSVC +r SeBatchLogonRight" -Wait
Get-EventLog Application -newest 10 | Where-Object {$_.Source -match "NTRights"} | %{
If ($_.Message -match "Success"){LogInfo "Info" "$((Get-Date).ToString())`t * CHAT Service Account granted SeBatchLogonRight Privilege!"}
Else {LogInfo "Error" "$((Get-Date).ToString())`t * $_.Message"}
}
# Error Handling
if ($Error.count -ne 0) {
LogInfo "Info" "$((Get-Date).ToString())`t * Error executing $($MyInvocation.MyCommand.Name)"
LogInfo "Error" "********************** Error executing $($MyInvocation.MyCommand.Name):"
for ($i=$Error.Count-1; $i -ge 0;$i--) {
If (@($Error[$i].Exception.Message) -match " * ") {LogInfo "Error" @($Error[$i].Exception.Message)}
Else {LogInfo "Error" "$((Get-Date).ToString())`t * $(@($Error[$i].Exception.Message))"}
}
}
LogInfo "Info" "$((Get-Date).ToString())`t * Finished $Title!"
# Send mail message with Setup Log attachment indicating failure or success
If (Test-Path $ErrorFile) {$Subject = "$Subject $($env:COMPUTERNAME): FAIL"}
Else {$Subject = "$Subject $($env:COMPUTERNAME): PASS"}
Send-MailMessage -To $To -From $From -Subject $Subject -Attachments $OutFile -SMTPServer $SMTP
# Completion notification with an option to restart if the script was successful overall
$WshShell = New-Object -ComObject Wscript.Shell
If (Test-Path $ErrorFile) {$WshShell.Popup("There were errors during execution of this script.`n Please check $ErrorFile for details.",0,"Error Performing Install",16)}
Else {
$Result = $WshShell.Popup("Installation Completed Successfully!`n Click OK to restart NOW, or Cancel to return to Windows.",0,"Installation Complete",32+1)
If ($Result -eq 1) {Restart-Computer -force}
}
ServerRolesFeatures.ps1 (located in c:\temp\buildfiles on the remote computer - the first script that the main script should be waiting for):
# Script Specific Variables
# Features to be added and/or removed need to be defined correctly to achieve the desired outcome.
# Feature values are defined in the code block that executes the cmdlets below.
$Title = "Server Role and Feature Installation"
# These variables are standard variables used to support various logging and error handling functions
$Error.clear()
$ErrorAction = "SilentlyContinue"
$LogDir = "c:\Temp\BuildLogs"
If (!(Test-Path $LogDir)) {New-Item $LogDir -type directory}
$OutFile = "$LogDir\SetupLog.log"
$ErrorFile="$LogDir\ErrorLog.log"
# Function to Log Useful Information. Accepts "Error" as a first argument to indicate an error to be logged to the error file.
# Any other first argument will direct output to the setup log. The second argument is the data to be logged.
Function LogInfo ($LogType, $LogData) {
If ($LogType -eq "Error") {out-file -FilePath $ErrorFile -Append -InputObject $LogData}
Else {out-file -FilePath $OutFile -Append -InputObject $LogData}
}
$Host.UI.RawUI.WindowTitle = $Title
LogInfo "Info" "$((Get-Date).ToString())`t * Beginning $Title!"
Write-Host "$Title in progress, please wait..."
Import-Module Servermanager
# Installing Server Features for Application Support - Check that Features are accurate here!
LogInfo "Info" "$((Get-Date).ToString())`t * Feature Install"
$Result = Add-WindowsFeature Web-Server,Web-WebServer,Web-Common-Http,Web-Static-Content,Web-Default-Doc,Web-Dir-Browsing,Web-Http-Errors,Web-App-Dev,Web-Asp-Net,Web-Net-Ext,Web-ASP,Web-ISAPI-Ext,Web-ISAPI-Filter,Web-Health,Web-Http-Logging,Web-Request-Monitor,Web-Security,Web-Filtering,Web-Mgmt-Tools,Web-Mgmt-Console,Web-Scripting-Tools,Web-Mgmt-Compat,Web-Metabase,Web-WMI,Web-Lgcy-Scripting,Web-Lgcy-Mgmt-Console
$Result.FeatureResult | %{
If ($_.Success) {LogInfo "Info" "$((Get-Date).ToString())`t * $_ : Success"}
Else {Write-Error -message "$((Get-Date).ToString())`t * $_ : Failure"}
}
# Removing Unnecessary Features Installed by Default - Check that Features are accurate here!
LogInfo "Info" "$((Get-Date).ToString())`t * Feature Removal"
$Result = Remove-WindowsFeature Web-CGI,Web-Includes
$Result.FeatureResult | %{
If ($_.Success) {LogInfo "Info" "$((Get-Date).ToString())`t * $_ : Success"}
Else {Write-Error -message "$((Get-Date).ToString())`t * $_ : Failure"}
}
# Error Handling
if ($Error.count -ne 0) {
LogInfo "Info" "$((Get-Date).ToString())`t * Error executing $($MyInvocation.MyCommand.Name)"
LogInfo "Error" "********************** Error executing $($MyInvocation.MyCommand.Name):"
for ($i=$Error.Count-1; $i -ge 0;$i--) {
If (@($Error[$i].Exception.Message) -match " * ") {LogInfo "Error" @($Error[$i].Exception.Message)}
Else {LogInfo "Error" "$((Get-Date).ToString())`t * $(@($Error[$i].Exception.Message))"}
}
}
LogInfo "Info" "$((Get-Date).ToString())`t * Finished $Title!"
LogInfo "Info" "********"
Tuesday, November 26, 2013 6:48 PM
The code snippet you posted was for the main script, which didn't need to wait. However, when I applied that knowledge to the start-process commands within that script, those processes now wait as intended.
Thanks for giving me the knowledge I needed to solve the problem Dexter!
Wednesday, November 27, 2013 4:33 AM
The code snippet you posted was for the main script, which didn't need to wait. However, when I applied that knowledge to the start-process commands within that script, those processes now wait as intended.
Thanks for giving me the knowledge I needed to solve the problem Dexter!
Yeah I guessed that wasn't the right way I was putting it...Glad that it was useful.
Well there are lots of ways to make things work and I happened to use this one so shared.
Cheers to Sharing :)
Regards
~Dex~
Knowledge is Power{Shell}.
Thursday, November 23, 2017 10:32 AM
You can use the -ThrottleLimit
parameter of the Invoke-Command
set to 1 in order to limit the number of sessions to 1 (disable concurrency)