diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
index 6f8b8d4d9a..813e316171 100644
--- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs
@@ -785,13 +785,6 @@ namespace Microsoft.PowerShell.Commands
throw new InvalidOperationException(RemotingErrorIdStrings.AsJobAndDisconnectedError);
}
- if (this.InvokeAndDisconnect &&
- (this.ComputerName == null || this.ComputerName.Length == 0) &&
- (this.ConnectionUri == null || this.ConnectionUri.Length == 0))
- {
- throw new InvalidOperationException(RemotingErrorIdStrings.InvokeDisconnectedWithoutComputerName);
- }
-
if (MyInvocation.BoundParameters.ContainsKey("SessionName") && !this.InvokeAndDisconnect)
{
throw new InvalidOperationException(RemotingErrorIdStrings.SessionNameWithoutInvokeDisconnected);
diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx
index c31b32e13d..e7d7bd7637 100644
--- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx
+++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx
@@ -911,9 +911,6 @@ Do you want to continue?
The JobIdentifier provided must not be null. Please provide a valid JobIdentifier.
-
- No computer names or connection Uris were specified. You must provide a computer name or connection Uri when invoking a command with the -Disconnected switch.
-
The Wait-Job cmdlet cannot finish working, because one or more jobs are blocked waiting for user interaction. Process interactive job output by using the Receive-Job cmdlet, and then try again.
diff --git a/test/powershell/engine/Job/Jobs.Tests.ps1 b/test/powershell/engine/Job/Jobs.Tests.ps1
index dd28559df3..3303c803a1 100644
--- a/test/powershell/engine/Job/Jobs.Tests.ps1
+++ b/test/powershell/engine/Job/Jobs.Tests.ps1
@@ -1,38 +1,285 @@
Describe 'Basic Job Tests' -Tags 'CI' {
-
BeforeAll {
- $job = Start-Job {1}
- }
+ # Make sure we do not have any jobs running
+ Get-Job | Remove-Job -Force
+ $timeBeforeStartedJob = Get-Date
+ $startedJob = Start-Job -Name 'StartedJob' -ScriptBlock { 1 + 1 } | Wait-Job
+ $timeAfterStartedJob = Get-Date
- It 'Test job creation' {
- $job | should not be $null
- }
+ function script:ValidateJobInfo($job, $state, $hasMoreData, $command)
+ {
+ $job.State | Should BeExactly $state
+ $job.HasMoreData | Should Be $hasMoreData
- It 'Test job State' {
- Wait-Job $job -Timeout 60
- $job.JobStateInfo.State -eq 'Completed' | should be $true
- }
-
- It 'Job output test' {
- Receive-Job $job -wait | should be 1
- }
-
- It "Create job with native command" {
- try {
- $nativeJob = Start-job { powershell -c 1+1 }
- $nativeJob | Wait-Job
- $nativeJob.State | Should BeExactly "Completed"
- $nativeJob.HasMoreData | Should Be $true
- Receive-Job $nativeJob | Should BeExactly 2
- Remove-Job $nativeJob
- { Get-Job $nativeJob -ErrorAction Stop } | ShouldBeErrorId "JobWithSpecifiedNameNotFound,Microsoft.PowerShell.Commands.GetJobCommand"
- }
- finally {
- Remove-Job $nativeJob -Force -ErrorAction SilentlyContinue
+ if($command -ne $null)
+ {
+ $job.Command | Should BeExactly $command
+ }
}
}
AfterAll {
- Remove-Job $job -Force
+ $startedJob | Remove-Job -Force -ErrorAction SilentlyContinue
}
-}
\ No newline at end of file
+
+ Context 'Basic tests' {
+
+ AfterEach {
+ Get-Job | Where-Object { $_.Id -ne $startedJob.Id } | Remove-Job -ErrorAction SilentlyContinue -Force
+ }
+
+ It 'Can start, wait and receive a Job' {
+ $job = Start-Job -ScriptBlock { 1 + 1 }
+ $result = $job | Wait-Job | Receive-Job
+ ValidateJobInfo -job $job -state 'Completed' -hasMoreData $false -command ' 1 + 1 '
+ $result | Should Be 2
+ }
+
+ It 'Can run nested jobs' {
+ $job = Start-Job -ScriptBlock { Start-Job -ScriptBlock { 1 + 1 } | Wait-Job | Receive-Job }
+ ValidateJobInfo -job $job -state 'Running' -hasMoreData $true -command ' Start-Job -ScriptBlock { 1 + 1 } | Wait-Job | Receive-Job '
+ $result = $job | Wait-Job | Receive-Job
+ $result | Should Be 2
+ }
+
+ It 'Can get errors messages from job' {
+ $job = Start-Job -ScriptBlock { throw 'MyError' } | Wait-Job
+ Receive-Job -Job $job -ErrorVariable ev -ErrorAction SilentlyContinue
+ $ev[0].Exception.Message | Should BeExactly 'MyError'
+ }
+
+ It 'Can get warning messages from job' {
+ $job = Start-Job -ScriptBlock { Write-Warning 'MyWarning' } | Wait-Job
+ Receive-Job -Job $job -WarningVariable wv -WarningAction SilentlyContinue
+ $wv | Should BeExactly 'MyWarning'
+ }
+
+ It 'Can get verbose message from job' {
+ $job = Start-Job -ScriptBlock { Write-Verbose -Verbose 'MyVerbose' } | Wait-Job
+ $VerboseMsg = $job.ChildJobs[0].verbose.readall()
+ $VerboseMsg | Should BeExactly 'MyVerbose'
+ }
+
+ It 'Can get progress message from job' {
+ $job = Start-Job -ScriptBlock { Write-Progress -Activity 1 -Status 2 } | Wait-Job
+ $ProgressMsg = $job.ChildJobs[0].progress.readall()
+ $ProgressMsg[0].Activity | Should BeExactly 1
+ $ProgressMsg[0].StatusDescription | Should BeExactly 2
+ }
+
+ It "Create job with native command" {
+ try {
+ $nativeJob = Start-job { powershell -c 1+1 }
+ $nativeJob | Wait-Job
+ $nativeJob.State | Should BeExactly "Completed"
+ $nativeJob.HasMoreData | Should Be $true
+ Receive-Job $nativeJob | Should BeExactly 2
+ Remove-Job $nativeJob
+ { Get-Job $nativeJob -ErrorAction Stop } | ShouldBeErrorId "JobWithSpecifiedNameNotFound,Microsoft.PowerShell.Commands.GetJobCommand"
+ }
+ finally {
+ Remove-Job $nativeJob -Force -ErrorAction SilentlyContinue
+ }
+ }
+ }
+
+ Context 'Wait-Job tests' {
+
+ BeforeAll {
+ $waitJobTestCases = @(
+ @{ parameters = @{ Name = $startedJob.Name } ; property = '-Name'},
+ @{ parameters = @{ Id = $startedJob.Id } ; property = '-Id'},
+ @{ parameters = @{ Job = $startedJob } ; property = '-Job'},
+ @{ parameters = @{ InstanceId = $startedJob.InstanceId } ; property = '-InstanceId'},
+ @{ parameters = @{ State = $startedJob.State } ; property = '-State'}
+ )
+ }
+
+ AfterEach {
+ Get-Job | Where-Object { $_.Id -ne $startedJob.Id } | Remove-Job -ErrorAction SilentlyContinue -Force
+ }
+
+ It 'Can wait for jobs to complete using ' -TestCases $waitJobTestCases {
+ param($parameters)
+ $job = Wait-Job @parameters
+ ValidateJobInfo -job $job -state 'Completed' -hasMoreData $true -command ' 1 + 1 '
+ }
+
+ It 'Can wait for any job to complete' {
+ $jobs = 1..3 | ForEach-Object { $seconds = $_ ; Start-Job -ScriptBlock { Start-Sleep -Seconds $using:seconds ; $using:seconds} }
+ $waitedJob = Wait-Job -Job $jobs -Any
+ ValidateJobInfo -job $waitedJob -state 'Completed' -hasMoreData $true -command ' Start-Sleep -Seconds $using:seconds ; $using:seconds'
+ $result = $waitedJob | Receive-Job
+ ## We check for $result to be less than 4 so that any of the jobs completing first will considered a success.
+ $result | Should BeLessThan 4
+ ## Check none of the jobs threw errors.
+ $jobs.Error | Should BeNullOrEmpty
+ }
+
+ It 'Can timeout waiting for a job' {
+ $job = Start-Job -ScriptBlock { Start-Sleep -Seconds 10 }
+ $job | Wait-Job -TimeoutSec 1
+ ValidateJobInfo -job $job -state 'Running' -hasMoreData $true -command ' Start-Sleep -Seconds 10 '
+ }
+ }
+
+ Context 'Receive-job tests' {
+ It 'Can Receive-Job with state change events' {
+ $result = Start-Job -Name 'ReceiveWriteEventsJob' -ScriptBlock { 1 + 1 } | Receive-Job -Wait -WriteEvents
+ $result.Count | Should Be 3
+ $result[0] | Should Be 2
+ $result[1].GetType().FullName | Should BeExactly 'System.Management.Automation.JobStateEventArgs'
+ }
+
+ It 'Can Receive-Job with job object and result' {
+ $result = Start-Job -ScriptBlock { 1 + 1 } | Receive-Job -Wait -WriteJobInResults
+ $result.Count | Should Be 2
+ ValidateJobInfo -job $result[0] -command ' 1 + 1 ' -state 'Completed' -hasMoreData $false
+ $result[1] | Should Be 2
+ $result[0] | Remove-Job -Force -ErrorAction SilentlyContinue
+ }
+
+ It 'Can Receive-Job and autoremove' {
+ $result = Start-Job -Name 'ReceiveJobAutoRemove' -ScriptBlock { 1 + 1 } | Receive-Job -Wait -AutoRemoveJob
+ $result | Should Be 2
+ { Get-Job -Name 'ReceiveJobAutoRemove' -ErrorAction Stop } | ShouldBeErrorId 'JobWithSpecifiedNameNotFound,Microsoft.PowerShell.Commands.GetJobCommand'
+ }
+
+ It 'Can Receive-Job and keep results' {
+ $job = Start-Job -ScriptBlock { 1 + 1 } | Wait-Job
+ $result = Receive-Job -Keep -Job $job
+ $result | Should Be 2
+ $result2 = Receive-Job -Job $job
+ $result2 | Should Be 2
+ $result3 = Receive-Job -Job $job
+ $result3 | Should BeNullOrEmpty
+ $job | Remove-Job -Force -ErrorAction SilentlyContinue
+ }
+
+ It 'Can Receive-Job with NoRecurse' {
+ $job = Start-Job -ScriptBlock { 1 + 1 }
+ $result = Receive-Job -Wait -NoRecurse -Job $job
+ $result | Should BeNullOrEmpty
+ $job | Remove-Job -Force -ErrorAction SilentlyContinue
+ }
+
+ It 'Can Receive-Job using ComputerName' {
+ $jobName = 'ReceiveUsingComputerName'
+ $job = Start-Job -ScriptBlock { 1 + 1 } -Name $jobName | Wait-Job
+ $result = Receive-Job -ComputerName localhost -Job $job
+ $result | Should Be 2
+ $job | Remove-Job -Force -ErrorAction SilentlyContinue
+ }
+
+ It 'Can Receive-Job using Location' {
+ $jobName = 'ReceiveUsingLocation'
+ $job = Start-Job -ScriptBlock { 1 + 1 } -Name $jobName | Wait-Job
+ $result = Receive-Job -Location localhost -Job $job
+ $result | Should Be 2
+ $job | Remove-Job -Force -ErrorAction SilentlyContinue
+ }
+
+ It 'Can receive a job with -wait switch' {
+ $job = Start-Job -ScriptBlock { 1 + 1 }
+ $result = $job | Receive-Job -Wait
+ ValidateJobInfo -job $job -state 'Completed' -hasMoreData $false -command ' 1 + 1 '
+ $result | Should Be 2
+ }
+ }
+
+ Context 'Get-Job tests' {
+ BeforeAll {
+ $getJobTestCases = @(
+ @{ parameters = @{ Name = $startedJob.Name } ; property = 'Name'},
+ @{ parameters = @{ Id = $startedJob.Id } ; property = 'Id'},
+ @{ parameters = @{ InstanceId = $startedJob.InstanceId } ; property = 'InstanceId'},
+ @{ parameters = @{ State = $startedJob.State } ; property = 'State'}
+ )
+
+ $getJobSwitches = @(
+ @{ parameters = @{ Before = $timeAfterStartedJob }; property = '-Before'},
+ @{ parameters = @{ After = $timeBeforeStartedJob }; property = '-After'},
+ @{ parameters = @{ HasMoreData = $true }; property = '-HasMoreData'}
+ )
+
+ $getJobChildJobs = @(
+ @{ parameters = @{ IncludeChildJob = $true }; property = '-IncludeChildJob'},
+ @{ parameters = @{ ChildJobState = 'Completed' }; property = '-ChildJobState'}
+ )
+ }
+
+ AfterEach {
+ Get-Job | Where-Object { $_.Id -ne $startedJob.Id } | Remove-Job -ErrorAction SilentlyContinue -Force
+ }
+
+ It 'Can Get-Job with ' -TestCases $getJobTestCases {
+ param($parameters)
+ $job = Get-Job @parameters
+ ValidateJobInfo -job $job -state 'Completed' -hasMoreData $true -command ' 1 + 1 '
+ }
+
+ It 'Can Get-Job with ' -TestCases $getJobSwitches {
+ param($parameters)
+ $job = Get-Job @parameters
+ ValidateJobInfo -job $job -state 'Completed' -hasMoreData $true -Name 'StartedJob'
+ }
+
+ It 'Can Get-Job with ' -TestCases $getJobChildJobs {
+ param($parameters)
+ $jobs = Get-Job @parameters
+ $jobs.Count | Should Be 2
+ ValidateJobInfo -job $jobs[0] -state 'Completed' -hasMoreData $true -Name 'StartedJob'
+ ValidateJobInfo -job $jobs[1] -state 'Completed' -hasMoreData $true
+ }
+ }
+
+
+ Context 'Remove-Job tests' {
+ # The test pattern used here is different from other tests since there is a scoping issue in Pester.
+ # If BeforeEach is used then $removeJobTestCases does not bind when the It is called.
+ # This implementation works around the problem by using a BeforeAll and creating a job inside the It.
+ BeforeAll {
+ $removeJobTestCases = @(
+ @{ property = 'Name'}
+ @{ property = 'Id'}
+ @{ property = 'InstanceId'}
+ @{ property = 'State'}
+ )
+ }
+
+ It 'Can Remove-Job with ' -TestCases $removeJobTestCases {
+ param($property)
+ $jobToRemove = Start-Job -ScriptBlock { 1 + 1 } -Name 'JobToRemove' | Wait-Job
+ $splat = @{ $property = $jobToRemove.$property }
+ Remove-Job @splat
+ Get-Job $jobToRemove -ErrorAction SilentlyContinue | Should BeNullOrEmpty
+ }
+ }
+
+ Context 'Stop-Job tests' {
+ # The test pattern used here is different from other tests since there is a scoping issue in Pester.
+ # If BeforeEach is used then $stopJobTestCases does not bind when the It is called.
+ # This implementation works around the problem by using a BeforeAll and creating a job inside the It.
+ BeforeAll {
+ $stopJobTestCases = @(
+ @{ property = 'Name'}
+ @{ property = 'Id'}
+ @{ property = 'InstanceId'}
+ @{ property = 'State'}
+ )
+ # '-Seconds 100' is chosen to be substantially large, so that the job is in running state when Stop-Job is called.
+ $jobToStop = Start-Job -ScriptBlock { Start-Sleep -Seconds 100 } -Name 'JobToStop'
+ }
+
+ It 'Can Stop-Job with ' -TestCases $stopJobTestCases {
+ param($property)
+ $splat = @{ $property = $jobToStop.$property }
+ Stop-Job @splat
+ ValidateJobInfo -job $jobToStop -state 'Stopped' -hasMoreData $false
+ }
+
+ AfterAll {
+ Remove-Job $jobToStop -Force -ErrorAction SilentlyContinue
+ }
+ }
+}
diff --git a/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1 b/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1
index a2f3c91d49..5c5918bc3c 100644
--- a/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1
+++ b/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1
@@ -1,4 +1,3 @@
-
Describe "New-PSSession basic test" -Tag @("CI") {
It "New-PSSession should not crash powershell" {
try {
@@ -10,41 +9,78 @@ Describe "New-PSSession basic test" -Tag @("CI") {
}
}
-Describe "JEA session Transcprit script test" -Tag @("Feature", 'RequireAdminOnWindows') {
- It "Configuration name should be in the transcript header" -Pending {
+Describe "JEA session Transcript script test" -Tag @("Feature", 'RequireAdminOnWindows') {
+ BeforeAll {
+ $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
+
+ if ( ! $IsWindows )
+ {
+ $PSDefaultParameterValues["it:skip"] = $true
+ }
+ else
+ {
+ Enable-PSRemoting -SkipNetworkProfileCheck
+ }
+ }
+
+ AfterAll {
+ $global:PSDefaultParameterValues = $originalDefaultParameterValues
+ }
+
+ It "Configuration name should be in the transcript header" {
[string] $RoleCapDirectory = (New-Item -Path "$TestDrive\RoleCapability" -ItemType Directory -Force).FullName
[string] $PSSessionConfigFile = "$RoleCapDirectory\TestConfig.pssc"
[string] $transScriptFile = "$RoleCapDirectory\*.txt"
try
{
New-PSSessionConfigurationFile -Path $PSSessionConfigFile -TranscriptDirectory $RoleCapDirectory -SessionType RestrictedRemoteServer
- Register-PSSessionConfiguration -Name JEA -Path $PSSessionConfigFile -Force -ErrorAction SilentlyContinue
- $scriptBlock = {Enter-PSSession -ComputerName Localhost -ConfigurationName JEA; Exit-PSSession}
- & $scriptBlock
+ Register-PSSessionConfiguration -Name JEA -Path $PSSessionConfigFile -Force -ErrorAction SilentlyContinue
+ $scriptBlock = {Enter-RemoteSession -ComputerName Localhost -ConfigurationName JEA; Exit-PSSession}
+ # Invoke the script block in a different PowerShell instance so that when TestDrive tries to delete $RoleCapDirectory,
+ # the transcription has finished and the files are not locked.
+ [powershell]::Create().AddScript($scriptBlock).Invoke()
$headerFile = Get-ChildItem $transScriptFile | Sort-Object LastWriteTime | Select-Object -Last 1
$header = Get-Content $headerFile | Out-String
- $header | Should BeLike "Configuration Name: JEA"
+ $header | Should Match "Configuration Name: JEA"
}
finally
{
Unregister-PSSessionConfiguration -Name JEA -Force -ErrorAction SilentlyContinue
}
}
-
+
}
-
Describe "JEA session Get-Help test" -Tag @("CI", 'RequireAdminOnWindows') {
- It "Get-Help should work in JEA sessions" -Pending {
+ BeforeAll {
+ $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
+
+ if ( ! $IsWindows )
+ {
+ $PSDefaultParameterValues["it:skip"] = $true
+ }
+ else
+ {
+ Enable-PSRemoting -SkipNetworkProfileCheck
+ }
+ }
+
+ AfterAll {
+ $global:PSDefaultParameterValues = $originalDefaultParameterValues
+ }
+
+ It "Get-Help should work in JEA sessions" {
[string] $RoleCapDirectory = (New-Item -Path "$TestDrive\RoleCapability" -ItemType Directory -Force).FullName
[string] $PSSessionConfigFile = "$RoleCapDirectory\TestConfig.pssc"
try
{
New-PSSessionConfigurationFile -Path $PSSessionConfigFile -TranscriptDirectory $RoleCapDirectory -SessionType RestrictedRemoteServer
- Register-PSSessionConfiguration -Name JEA -Path $PSSessionConfigFile -Force -ErrorAction SilentlyContinue
- $scriptBlock = {Enter-PSSession -ComputerName Localhost -ConfigurationName JEA; Get-Help Get-Command; Exit-PSSession}
- $helpContent = & $scriptBlock
- $helpContent | Should Not Be $null
+ Register-PSSessionConfiguration -Name JEA -Path $PSSessionConfigFile -Force -ErrorAction SilentlyContinue
+ $scriptBlock = {Enter-RemoteSession -ComputerName Localhost -ConfigurationName JEA; Get-Help Get-Command; Exit-PSSession}
+ # Invoke the script block in a different PowerShell instance so that when TestDrive tries to delete $RoleCapDirectory,
+ # the transcription has finished and the files are not locked.
+ $helpContent = [powershell]::Create().AddScript($scriptBlock).Invoke()
+ $helpContent | Should Not Be $null
}
finally
{
@@ -52,5 +88,195 @@ Describe "JEA session Get-Help test" -Tag @("CI", 'RequireAdminOnWindows') {
Remove-Item $RoleCapDirectory -Recurse -Force -ErrorAction SilentlyContinue
}
}
-
-}
\ No newline at end of file
+}
+
+Describe "Remoting loopback tests" -Tags @('CI', 'RequireAdminOnWindows') {
+ BeforeAll {
+
+ $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
+
+ if ( ! $IsWindows )
+ {
+ $PSDefaultParameterValues["it:skip"] = $true
+ }
+ else
+ {
+ Enable-PSRemoting -SkipNetworkProfileCheck
+ $endPoint = (Get-PSSessionConfiguration -Name "PowerShell.$(${PSVersionTable}.GitCommitId)").Name
+ $disconnectedSession = New-RemoteSession -ConfigurationName $endPoint -ComputerName localhost | Disconnect-PSSession
+ $closedSession = New-RemoteSession -ConfigurationName $endPoint -ComputerName localhost
+ $closedSession.Runspace.Close()
+ $openSession = New-RemoteSession -ConfigurationName $endPoint
+
+ $ParameterError = @(
+ @{
+ parameters = @{
+ 'InDisconnectedSession' = $true
+ 'AsJob' = $true
+ 'ScriptBlock' = {1}
+ 'ComputerName' = 'localhost'
+ 'ConfigurationName' = $endpoint
+ }
+ expectedError = 'System.InvalidOperationException,Microsoft.PowerShell.Commands.InvokeCommandCommand'
+ title = 'Cannot use InDisconnectedState and AsJob together'
+ },
+ @{
+ parameters = @{
+ 'ScriptBlock' = {1}
+ 'SessionName' = 'SomeSessionName'
+ }
+ expectedError = 'System.InvalidOperationException,Microsoft.PowerShell.Commands.InvokeCommandCommand'
+ title = 'Cannot use SessionName without InDisconnectedSession'
+ },
+ @{
+ parameters = @{
+ 'ScriptBlock' = { 1 }
+ 'Session' = $disconnectedSession
+ 'ErrorAction' = 'Stop'
+ }
+ expectedError = 'InvokeCommandCommandInvalidSessionState,Microsoft.PowerShell.Commands.InvokeCommandCommand'
+ title = 'Cannot use Invoke-Command on a disconnected session'
+ }
+ @{
+ parameters = @{
+ 'ScriptBlock' = { 1 }
+ 'Session' = $closedSession
+ 'ErrorAction' = 'Stop'
+ }
+ expectedError = 'InvokeCommandCommandInvalidSessionState,Microsoft.PowerShell.Commands.InvokeCommandCommand'
+ title = 'Cannot use Invoke-Command on a closed session'
+ }
+ )
+
+ function script:ValidateSessionInfo($session, $state)
+ {
+ $session.ComputerName | Should BeExactly 'localhost'
+ $session.ConfigurationName | Should BeExactly $endPoint
+ $session.State | Should Be $state
+ }
+ }
+ }
+
+ AfterAll {
+ $global:PSDefaultParameterValues = $originalDefaultParameterValues
+
+ if($isWindows)
+ {
+ Remove-PSSession $disconnectedSession,$closedSession,$openSession -ErrorAction SilentlyContinue
+ }
+ }
+
+ It 'Can connect to default endpoint' {
+ $session = New-RemoteSession -ConfigurationName $endPoint
+
+ try
+ {
+ ValidateSessionInfo -session $session -state 'Opened'
+ }
+ finally
+ {
+ $session | Remove-PSSession -ErrorAction SilentlyContinue
+ }
+ }
+
+ It 'Can execute command in a disconnected session' {
+ $session = Invoke-RemoteCommand -InDisconnectedSession -ComputerName 'localhost' -ScriptBlock { 1 + 1 } -ConfigurationName $endPoint
+ try
+ {
+ ValidateSessionInfo -session $session -state 'Disconnected'
+
+ $result = Receive-PSSession -Session $session
+ $result | Should Be 2
+ $result.PSComputerName | Should BeExactly 'localhost'
+ }
+ finally
+ {
+ $session | Remove-PSSession -ErrorAction SilentlyContinue
+ }
+ }
+
+ It 'Can disconnect and connect to PSSession' {
+ $session = New-RemoteSession -ConfigurationName $endPoint
+ try
+ {
+ ValidateSessionInfo -session $session -state 'Opened'
+ Disconnect-PSSession -Session $session
+
+ ValidateSessionInfo -session $session -state 'Disconnected'
+ Connect-RemoteSession -Session $session
+
+ $result = Invoke-Command -Session $session -ScriptBlock { 1 + 1 }
+ $result | Should Be 2
+ $result.PSComputerName | Should BeExactly 'localhost'
+ }
+ finally
+ {
+ $session | Remove-PSSession -ErrorAction SilentlyContinue
+ }
+ }
+
+ It "" -TestCases $ParameterError {
+ param($parameters, $expectedError)
+
+ { Invoke-Command @parameters } | ShouldBeErrorId $expectedError
+ }
+
+ It 'Can execute command if one of the sessions is available' {
+ try
+ {
+ $result = Invoke-Command -Session $openSession,$disconnectedSession,$closedSession -ScriptBlock { 1+1 } -ErrorAction SilentlyContinue
+ }
+ catch
+ {
+ if($_.FullyQualifiedErrorId -ne 'InvokeCommandCommandInvalidSessionState,Microsoft.PowerShell.Commands.InvokeCommandCommand')
+ {
+ # We expect the error from $disconnectedSession and $closedSession. Hence, throw otherwise.
+ throw $_
+ }
+ }
+
+ $result.Count | Should Be 1
+ $result | Should Be 2
+ }
+
+ It 'Can execute command without creating new scope' {
+ Invoke-Command -NoNewScope -ScriptBlock { $sameScopeVariable = 'SetInCurrentScope' }
+ $sameScopeVariable | Should BeExactly 'SetInCurrentScope'
+ }
+
+ It 'Can execute command from a file' {
+ $fileName = "$testdrive/remotingscript.ps1"
+ '1 + 1' | Out-File $fileName
+ $result = Invoke-Command -FilePath $fileName -Session $openSession
+ $result | Should Be 2
+ }
+
+ It 'Can invoke-command as job' {
+ $result = Invoke-Command -ScriptBlock { 1 + 1 } -Session $openSession -AsJob | Receive-Job -AutoRemoveJob -Wait
+ $result | Should Be 2
+ }
+
+ It 'Can connect to all disconnected sessions by name' {
+ $connectionNames = @("DiscPSS$(Get-Random)", "DiscPSS$(Get-Random)")
+ $connectionNames | ForEach-Object { $null = New-RemoteSession -ComputerName localhost -ConfigurationName $endpoint -Name $_ | Disconnect-PSSession}
+
+ Connect-RemoteSession -ComputerName localhost -Name $connectionNames -ConfigurationName $endpoint
+ $sessions = Get-PSSession -Name $connectionNames
+ try
+ {
+ $sessions | ForEach-Object {
+ ValidateSessionInfo -session $_ -state 'Opened'
+ }
+ }
+ finally
+ {
+ $sessions | Remove-PSSession -ErrorAction SilentlyContinue
+ }
+ }
+
+ It 'Can pass values through $using' {
+ $number = 100
+ $result = Invoke-Command -Session $openSession -ScriptBlock { $using:number }
+ $result | Should Be 100
+ }
+}
diff --git a/test/tools/Modules/HelpersRemoting/HelpersRemoting.psd1 b/test/tools/Modules/HelpersRemoting/HelpersRemoting.psd1
index 46fe8e3902..ec53921ddd 100644
--- a/test/tools/Modules/HelpersRemoting/HelpersRemoting.psd1
+++ b/test/tools/Modules/HelpersRemoting/HelpersRemoting.psd1
@@ -16,6 +16,6 @@ Copyright = 'Copyright (C) Microsoft Corporation, All rights reserved.'
Description = 'Temporary module for remoting tests'
-FunctionsToExport = 'New-RemoteRunspace', 'New-RemoteSession'
+FunctionsToExport = 'New-RemoteRunspace', 'New-RemoteSession', 'Enter-RemoteSession', 'Invoke-RemoteCommand', 'Connect-RemoteSession'
}
diff --git a/test/tools/Modules/HelpersRemoting/HelpersRemoting.psm1 b/test/tools/Modules/HelpersRemoting/HelpersRemoting.psm1
index f51240b21a..d80cbb4017 100644
--- a/test/tools/Modules/HelpersRemoting/HelpersRemoting.psm1
+++ b/test/tools/Modules/HelpersRemoting/HelpersRemoting.psm1
@@ -8,7 +8,6 @@ if ($IsWindows) {
try { $Script:AppVeyorRemoteCred = Import-Clixml -Path "$env:TEMP\AppVeyorRemoteCred.xml" } catch { }
}
-
function New-RemoteRunspace
{
$wsmanConInfo = [System.Management.Automation.Runspaces.WSManConnectionInfo]::new()
@@ -29,16 +28,30 @@ function New-RemoteRunspace
return $remoteRunspace
}
-
-function New-RemoteSession
+function CreateParameters
{
param (
- [string] $Name,
+ [string] $ComputerName,
+ [string[]] $Name,
[string] $ConfigurationName,
- [switch] $CimSession,
- [System.Management.Automation.Remoting.PSSessionOption] $SessionOption)
+ [System.Management.Automation.Remoting.PSSessionOption] $SessionOption,
+ [System.Management.Automation.Runspaces.PSSession[]] $Session)
- $parameters = @{ ComputerName = "."; }
+ if($ComputerName)
+ {
+ $parameters = @{ComputerName = $ComputerName}
+ }
+ else
+ {
+ if($Session)
+ {
+ $parameters = @{Session = $Session}
+ }
+ else
+ {
+ $parameters = @{ComputerName = '.'}
+ }
+ }
if ($Name) {
$parameters["Name"] = $Name
@@ -52,7 +65,8 @@ function New-RemoteSession
$parameters["SessionOption"] = $SessionOption
}
- if ($Script:AppVeyorRemoteCred)
+ ## If a PSSession is provided, do not add credentials.
+ if ($Script:AppVeyorRemoteCred -and (-not $Session))
{
Write-Verbose "Using Global AppVeyor Credential" -Verbose
$parameters["Credential"] = $Script:AppVeyorRemoteCred
@@ -62,6 +76,18 @@ function New-RemoteSession
Write-Verbose "Using Implicit Credential" -Verbose
}
+ return $parameters
+}
+function New-RemoteSession
+{
+ param (
+ [string] $Name,
+ [string] $ConfigurationName,
+ [switch] $CimSession,
+ [System.Management.Automation.Remoting.PSSessionOption] $SessionOption)
+
+ $parameters = CreateParameters -Name $Name -ConfigurationName $ConfigurationName -SessionOption $SessionOption
+
if ($CimSession) {
$session = New-CimSession @parameters
} else {
@@ -70,3 +96,50 @@ function New-RemoteSession
return $session
}
+
+function Invoke-RemoteCommand
+{
+ param (
+ [string] $ComputerName,
+ [scriptblock] $ScriptBlock,
+ [string] $ConfigurationName,
+ [switch] $InDisconnectedSession)
+
+ $parameters = CreateParameters -ComputerName $ComputerName -ConfigurationName $ConfigurationName
+
+ if($ScriptBlock)
+ {
+ $parameters.Add('ScriptBlock', $ScriptBlock)
+ }
+
+ if($InDisconnectedSession)
+ {
+ $parameters.Add('InDisconnectedSession', $InDisconnectedSession.IsPresent)
+ }
+
+ Invoke-Command @parameters
+}
+
+function Enter-RemoteSession
+{
+ param(
+ [string] $Name,
+ [string] $ConfigurationName,
+ [System.Management.Automation.Remoting.PSSessionOption] $SessionOption)
+
+ $parameters = CreateParameters -Name $Name -ConfigurationName $ConfigurationName -SessionOption $SessionOption
+ Enter-PSSession @parameters
+}
+
+function Connect-RemoteSession
+{
+ param(
+ [string] $ComputerName,
+ [string[]] $Name,
+ [System.Management.Automation.Runspaces.PSSession[]] $Session,
+ [string] $ConfigurationName
+ )
+
+ $parameters = CreateParameters -ComputerName $ComputerName -Name $Name -Session $Session -ConfigurationName $ConfigurationName
+ Connect-PSSession @parameters
+}