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 +}