From 4bb0b13e09df9df7e166baec9b64ea51a194acf7 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 27 Oct 2016 11:53:35 -0700 Subject: [PATCH] Refactor implicit remoting tests to work in CI --- test/powershell/Common/TestRemoting.psm1 | 67 + test/powershell/Host/HostUtilities.Tests.ps1 | 30 +- .../Implicit.Remoting.Tests.ps1 | 3551 +++++++++-------- 3 files changed, 1886 insertions(+), 1762 deletions(-) create mode 100644 test/powershell/Common/TestRemoting.psm1 diff --git a/test/powershell/Common/TestRemoting.psm1 b/test/powershell/Common/TestRemoting.psm1 new file mode 100644 index 0000000000..d075f86d2a --- /dev/null +++ b/test/powershell/Common/TestRemoting.psm1 @@ -0,0 +1,67 @@ +# +# This module include help functions for writing remoting tests +# + +$Script:AppVeyorRemoteCred = $null + +if ($IsWindows) { + try { $Script:AppVeyorRemoteCred = Import-Clixml -Path "$env:TEMP\AppVeyorRemoteCred.xml" } catch { } +} + + +function New-RemoteRunspace +{ + $wsmanConInfo = [System.Management.Automation.Runspaces.WSManConnectionInfo]::new() + + if ($Script:AppVeyorRemoteCred) + { + Write-Verbose "Using Global AppVeyor Credential" -Verbose + $wsmanConInfo.Credential = $Script:AppVeyorRemoteCred + } + else + { + Write-Verbose "Using Implicit Credential" -Verbose + } + + $remoteRunspace = [runspacefactory]::CreateRunspace($Host, $wsmanConInfo) + $remoteRunspace.Open() + + return $remoteRunspace +} + + +function New-RemoteSession +{ + param ( + [string] $Name, + [string] $ConfigurationName, + [System.Management.Automation.Remoting.PSSessionOption] $SessionOption) + + $parameters = @{ ComputerName = "."; } + + if ($Name) { + $parameters["Name"] = $Name + } + + if ($ConfigurationName) { + $parameters["ConfigurationName"] = $ConfigurationName + } + + if ($SessionOption) { + $parameters["SessionOption"] = $SessionOption + } + + if ($Script:AppVeyorRemoteCred) + { + Write-Verbose "Using Global AppVeyor Credential" -Verbose + $parameters["Credential"] = $Script:AppVeyorRemoteCred + } + else + { + Write-Verbose "Using Implicit Credential" -Verbose + } + + $session = New-PSSession @parameters + + return $session +} diff --git a/test/powershell/Host/HostUtilities.Tests.ps1 b/test/powershell/Host/HostUtilities.Tests.ps1 index 158d963cab..c0974a1f72 100644 --- a/test/powershell/Host/HostUtilities.Tests.ps1 +++ b/test/powershell/Host/HostUtilities.Tests.ps1 @@ -1,26 +1,8 @@ -function Get-RemoteRunspace { - $wc = [System.Management.Automation.Runspaces.WSManConnectionInfo]::new() - - # Use AppVeyor credentials if running in AppVeyor, rather than implicit credentials. - try - { - $appveyorRemoteCredential = Import-Clixml -Path "$env:TEMP\AppVeyorRemoteCred.xml" - } - catch { } - if ($appveyorRemoteCredential) - { - Write-Verbose "Using global AppVeyor credential"; - $wc.Credential = $appveyorRemoteCredential - } - else - { - Write-Verbose "Using implicit credentials" - } - - $remoteRunspace = [runspacefactory]::CreateRunspace($host, $wc) - $remoteRunspace.Open() - - return $remoteRunspace +if (-not (Get-Module TestRemoting -ErrorAction SilentlyContinue)) +{ + $root = git rev-parse --show-toplevel + $remotingModule = Join-Path $root test/powershell/Common/TestRemoting.psm1 + Import-Module $remotingModule } Describe "InvokeOnRunspace method argument error handling" -tags "Feature" { @@ -76,7 +58,7 @@ Describe "InvokeOnRunspace method on remote runspace" -tags "Feature" { BeforeAll { if ($IsWindows) { - $script:remoteRunspace = Get-RemoteRunspace + $script:remoteRunspace = New-RemoteRunspace } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 index 6f03982280..70ace0bb3b 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 @@ -1,50 +1,46 @@ -## -## Remoting Implicit script tests -## + +if (-not (Get-Module TestRemoting -ErrorAction SilentlyContinue)) +{ + $root = git rev-parse --show-toplevel + $remotingModule = Join-Path $root test/powershell/Common/TestRemoting.psm1 + Import-Module $remotingModule +} -$path = $PSScriptRoot -if ($path -eq $null) { $path = Split-Path $MyInvocation.InvocationName } -if ($path -eq $null) { $path = $pwd } +Describe "Implicit remoting and CIM cmdlets with AllSigned and Restricted policy" -tags "Feature" { -import-module (join-path $path ".\RemotingCommon.psm1") + BeforeAll { + + # Skip test for non-windows machines + $skipTest = !$IsWindows + if ($skipTest) { return } -Describe "Implicit remoting and CIM cmdlets with AllSigned and Restricted policy" -tags 'Innerloop', 'P1' { - - try - { - ############################################################################## - # TEST SETUP - CREATE TEMP DIRECTORY # - - $modulesDir = $env:TEMP -split ';' | select -first 1 - - $tempdir = join-path $modulesDir ([IO.Path]::GetRandomFileName()) - mkdir $tempdir | Out-Null - - $fileName = [io.path]::GetFileName($tempdir) - - - ############################################################################## # GET CERTIFICATE # - $tempName = "$env:temp\signedscript_$(get-random).ps1" - "123456" >$tempName + $tempName = "$env:TEMP\signedscript_$(Get-Random).ps1" + "123456" > $tempName $cert = $null - foreach ($thisCertificate in (dir cert:\ -rec -codesigning)) + foreach ($thisCertificate in (Get-ChildItem cert:\ -rec -codesigning)) { - $null = set-authenticodesignature $tempName -cert $thisCertificate - if ((get-authenticodesignature $tempName).Status -eq "Valid") + $null = Set-AuthenticodeSignature $tempName -Certificate $thisCertificate + if ((Get-AuthenticodeSignature $tempName).Status -eq "Valid") { $cert = $thisCertificate break } } - if ($cert -eq $null) { return } + # Skip the tests if we couldn't find a code sign certificate + # This will happen in NanoServer and IoT + if ($cert -eq $null) + { + $skipTest = $true + return + } - # ensure the cert is trusted + # Ensure the cert is trusted if (-not (Test-Path "cert:\currentuser\TrustedPublisher\$($cert.Thumbprint)")) { $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "TrustedPublisher" @@ -53,434 +49,476 @@ Describe "Implicit remoting and CIM cmdlets with AllSigned and Restricted policy $store.Close() } - - ############################################################################## - # TEST - Verifying that Import-PSSession signs the files + # + # Set process scope execution policy to 'AllSigned' # $oldExecutionPolicy = Get-ExecutionPolicy -Scope Process Set-ExecutionPolicy AllSigned -Scope Process - $s = New-PSSession + # + # Create a remote session + # - $importedModule = Import-PSSession $s Get-Variable -Prefix Remote -Certificate $cert -AllowClobber - It "Verifies that Import-PSSession works in AllSigned if Certificate is used" { - $importedModule | Should Not Be $null - } - - $importedModule | Remove-Module -Force -ErrorAction SilentlyContinue - - $caught = $false - try - { - $importedModule = Import-PSSession $s Get-Variable -Prefix Remote -AllowClobber - } - catch { $caught = $true; } - - It "Verifies security error when Certificate parameter is not used" { - $caught | Should Be $true - } + $session = New-RemoteSession } - finally - { + + AfterAll { + if ($skipTest) { return } + + if ($tempName -ne $null) { Remove-Item -Path $tempName -Force -ErrorAction SilentlyContinue } if ($oldExecutionPolicy -ne $null) { Set-ExecutionPolicy $oldExecutionPolicy -Scope Process } - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } - if ($tempdir -ne $null) { remove-item $tempdir -recurse -force -ea silentlycontinue } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + } + + # + # TEST - Verifying that Import-PSSession signs the files + # + + It "Verifies that Import-PSSession works in AllSigned if Certificate is used" -Skip:$skipTest { + try { + $importedModule = Import-PSSession $session Get-Variable -Prefix Remote -Certificate $cert -AllowClobber + $importedModule | Should Not Be $null + } finally { + $importedModule | Remove-Module -Force -ErrorAction SilentlyContinue + } + } + + It "Verifies security error when Certificate parameter is not used" -Skip:$skipTest { + try { + $importedModule = Import-PSSession $session Get-Variable -Prefix Remote -AllowClobber + throw "expect Import-PSSession to throw" + } catch { + $_.FullyQualifiedErrorId | Should Be "InvalidOperation,Microsoft.PowerShell.Commands.ImportPSSessionCommand" + } } } -Describe "Tests Import-Proxy cmdlet function works with types unavailable on the client" -tags 'Innerloop', 'P1' { +Describe "Tests Import-PSSession cmdlet works with types unavailable on the client" -tags "Feature" { - $typeDefinition = @" - namespace MyTest - { - public enum MyEnum - { - Value1 = 1, - Value2 = 2 - } - } + BeforeAll { + + # Skip test for non-windows machines for now + $skipTest = !$IsWindows + + if ($skipTest) { return } + + $typeDefinition = @" + namespace MyTest + { + public enum MyEnum + { + Value1 = 1, + Value2 = 2 + } + } "@ + # + # Create a remote session + # - try - { - $r = New-PSSession - icm -Session $r -Script { Add-Type -TypeDefinition $args[0] } -Args $typeDefinition - icm -Session $r -Script { function foo { param([MyTest.MyEnum][Parameter(Mandatory = $true)]$x) $x } } + $session = New-RemoteSession - $module = Import-PSSession -Session $r -CommandName foo -AllowClobber - - It "Verifies client-side unavailable enum is treated as an int" { - (foo -x "Value2") | Should Be 2 - } - It "Verifies client-side unavailable enum is to-string-ed appropriately" { - ((foo -x "Value2").ToString()) | Should Be "Value2" - } + Invoke-Command -Session $session -Script { Add-Type -TypeDefinition $args[0] } -Args $typeDefinition + Invoke-Command -Session $session -Script { function foo { param([MyTest.MyEnum][Parameter(Mandatory = $true)]$x) $x } } } - finally - { - if ($r -ne $null) { Remove-PSSession $r -ErrorAction SilentlyContinue } - if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + + AfterAll { + if ($skipTest) { return } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + } + + It "Verifies client-side unavailable enum is correctly handled" -Skip:$skipTest { + try { + $module = Import-PSSession -Session $session -CommandName foo -AllowClobber + + # The enum is treated as an int + (foo -x "Value2") | Should Be 2 + # The enum is to-string-ed appropriately + (foo -x "Value2").ToString() | Should Be "Value2" + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } } } +Describe "Cmdlet help from remote session" -tags "Feature" { -Describe "Cmdlet help from remote session" -tags 'Innerloop', 'P1' { + BeforeAll { + + # Skip test for non-windows machines for now + $skipTest = !$IsWindows - try - { - $s = New-PSSession - $module = import-pssession $s -name select-object -prefix my -AllowClobber - $gcmOutPut = (get-command select-myobject).Name - $getHelpOutPut = (get-help select-myobject).Name - - It "Verifies that get-help name for remote proxied commands matches the get-command name" { - $gcmOutPut | Should Be $getHelpOutPut - } + if ($skipTest) { return } + $session = New-RemoteSession } - finally - { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } - if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + + AfterAll { + if ($skipTest) { return } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } } + + It "Verifies that get-help name for remote proxied commands matches the get-command name" -Skip:$skipTest { + try { + $module = Import-PSSession $session -Name Select-Object -prefix My -AllowClobber + $gcmOutPut = (Get-Command Select-MyObject ).Name + $getHelpOutPut = (Get-Help Select-MyObject).Name + + $gcmOutPut | Should Be $getHelpOutPut + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + } } +Describe "Import-PSSession Cmdlet error handling" -tags "Feature" { -Describe "Import-Proxy Cmdlet error handling" -tags 'Innerloop', 'P1' { + BeforeAll { + + # Skip test for non-windows machines for now + $skipTest = !$IsWindows - try - { - $s = New-PSSession + if ($skipTest) { return } + $session = New-RemoteSession + } - ############################################################################## - # TEST: error message for broken aliases - - Invoke-Command $s { set-alias BrokenAlias NonExistantCommand } - $module = Import-PSSession $s -CommandName:BrokenAlias -CommandType:All -ErrorAction SilentlyContinue -ErrorVariable expectedError -AllowClobber - - It "Verifies that broken alias results in one error" { - $expectedError | Should Not Be NullOrEmpty - } - It "Verifies that broken alias error contains expected 'BrokenAlias' name" { - $expectedError[0].ToString().Contains("BrokenAlias") | Should Be $true - } - - if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue; $module = $null } - Invoke-Command $s { del alias:BrokenAlias } + AfterAll { + if ($skipTest) { return } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + } - ############################################################################## - # TEST: content and format of proxied error message (Windows 7: #319080) + It "Verifies that broken alias results in one error" -Skip:$skipTest { + try { + Invoke-Command $session { Set-Alias BrokenAlias NonExistantCommand } + $module = Import-PSSession $session -CommandName:BrokenAlias -CommandType:All -ErrorAction SilentlyContinue -ErrorVariable expectedError -AllowClobber - $module = Import-PSSession -Session:$s -Name:Get-Variable -Prefix My -AllowClobber + $expectedError | Should Not Be NullOrEmpty + $expectedError[0].ToString().Contains("BrokenAlias") | Should Be $true + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + Invoke-Command $session { Remove-Item alias:BrokenAlias } + } + } - # non-terminating error - $results = Get-MyVariable blah,pid 2>&1 - It "Verifies that returned PID is not for this session" { - ($results[1]).Value | Should Not Be $pid - } - $errorString = $results[0] | Out-String - It "Verifies error message for variable blah" { + Context "Test content and format of proxied error message (Windows 7: #319080)" { + + BeforeAll { + if ($skipTest) { return } + $module = Import-PSSession -Session $session -Name Get-Variable -Prefix My -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Test non-terminating error" -Skip:$skipTest { + $results = Get-MyVariable blah,pid 2>&1 + + ($results[1]).Value | Should Not Be $PID # Verifies that returned PID is not for this session + + $errorString = $results[0] | Out-String # Verifies error message for variable blah ($errorString -like "*VariableNotFound*") | Should Be $true } - #terminating error - $results = Get-MyVariable pid -Scope blah 2>&1 - It "Verifies that remote session pid is not returned" { - $results.Count | Should Be 1 - } - $errorString = $results[0] | Out-String - It "Verifes error message for incorrect Scope parameter argument" { + It "Test terminating error" -Skip:$skipTest { + $results = Get-MyVariable pid -Scope blah 2>&1 + + $results.Count | Should Be 1 # Verifies that remote session pid is not returned + + $errorString = $results[0] | Out-String # Verifes error message for incorrect Scope parameter argument ($errorString -like "*Argument*") | Should Be $true } - - if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue; $module = $null } - - - ############################################################################## - # TEST: ordering of a sequence of error and output messages (Windows 7: #405065) - - icm $s {function foo1{1; write-error 2; 3; write-error 4; 5; write-error 6}} - $module = Import-PSSession $s -CommandName foo1 -AllowClobber - - $icmErr = $($icmOut = icm $s { foo1 }) 2>&1 - $proxiedErr = $($proxiedOut = foo1) 2>&1 - $proxiedOut2 = foo1 2>$null - - $icmOut = "$icmOut" - $icmErr = "$icmErr" - $proxiedOut = "$proxiedOut" - $proxiedOut2 = "$proxiedOut2" - $proxiedErr = "$proxiedErr" - - It "Verifies proxied output = proxied output 2" { - $proxiedOut2 | Should Be $proxiedOut - } - It "Verifies proxied output = icm output (for mixed error and output results)" { - $icmOut | Should Be $proxiedOut - } - It "Verifies proxied error = icm error (for mixed error and output results)" { - $icmErr | Should Be $proxiedErr - } - - $icmOrder = icm $s { foo1 } 2>&1 | out-string - $proxiedOrder = foo1 2>&1 | out-string - - It "Verifies proxied order = icm order (for mixed error and output results)" { - $icmOrder | Should Be $proxiedOrder - } - - if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue; $module = $null } - - - ############################################################################## - # TEST: is WarningVariable parameter working with implicit remoting (Windows 8: #44861) - - $m = Import-PSSession $s -CommandName Write-Warning -Prefix Remote -AllowClobber - $global:myWarningVariable = @() - Write-RemoteWarning MyWarning -WarningVariable global:myWarningVariable - It "Verifies WarningVariable" { - ([string]($myWarningVariable[0])) | Should Be 'MyWarning' - } } - finally - { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } - if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue; $module = $null } + + Context "Ordering of a sequence of error and output messages (Windows 7: #405065)" { + + BeforeAll { + if ($skipTest) { return } + + Invoke-Command $session { function foo1{1; write-error 2; 3; write-error 4; 5; write-error 6} } + $module = Import-PSSession $session -CommandName foo1 -AllowClobber + + $icmErr = $($icmOut = Invoke-Command $session { foo1 }) 2>&1 + $proxiedErr = $($proxiedOut = foo1) 2>&1 + $proxiedOut2 = foo1 2>$null + + $icmOut = "$icmOut" + $icmErr = "$icmErr" + $proxiedOut = "$proxiedOut" + $proxiedOut2 = "$proxiedOut2" + $proxiedErr = "$proxiedErr" + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Verifies proxied output = proxied output 2" -Skip:$skipTest { + $proxiedOut2 | Should Be $proxiedOut + } + + It "Verifies proxied output = icm output (for mixed error and output results)" -Skip:$skipTest { + $icmOut | Should Be $proxiedOut + } + + It "Verifies proxied error = icm error (for mixed error and output results)" -Skip:$skipTest { + $icmErr | Should Be $proxiedErr + } + + It "Verifies proxied order = icm order (for mixed error and output results)" -Skip:$skipTest { + $icmOrder = Invoke-Command $session { foo1 } 2>&1 | out-string + $proxiedOrder = foo1 2>&1 | out-string + + $icmOrder | Should Be $proxiedOrder + } + } + + Context "WarningVariable parameter works with implicit remoting (Windows 8: #44861)" { + + BeforeAll { + if ($skipTest) { return } + $module = Import-PSSession $session -CommandName Write-Warning -Prefix Remote -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Verifies WarningVariable" -Skip:$skipTest { + $global:myWarningVariable = @() + Write-RemoteWarning MyWarning -WarningVariable global:myWarningVariable + ([string]($myWarningVariable[0])) | Should Be 'MyWarning' + } } } +Describe "Tests Export-PSSession" -tags "Feature" { -Describe "Tests Export-PSSession" -tags 'Innerloop', 'P1' { + BeforeAll { + + # Skip test for non-windows machines for now + $skipTest = !$IsWindows - function Get-TempModuleFile - { - [IO.Path]::Combine([IO.Path]::GetTempPath(), [Guid]::NewGuid().ToString()) - } + if ($skipTest) { return } - try - { $sessionOption = New-PSSessionOption -ApplicationArguments @{myTest="MyValue"} - $s = New-PSSession -SessionOption $sessionOption + $session = New-RemoteSession -SessionOption $sessionOption - $file = Get-TempModuleFile - $results = Export-PSSession -Session $s -CommandName Get-Variable -AllowClobber -ModuleName $file - It "Verifies Export-PSSession creates a file/directory" { - @(Get-Item $file).Count | Should Be 1 - } - It "Verifies Export-PSSession creates a psd1 file" { - ($results | ?{ $_.Name -like "*$(Split-Path -Leaf $file).psd1" }) | Should Be $true - } - It "Verifies Export-PSSession creates a psm1 file" { - ($results | ?{ $_.Name -like "*.psm1" }) | Should Be $true - } - It "Verifies Export-PSSession creates a ps1xml file" { - ($results | ?{ $_.Name -like "*.ps1xml" }) | Should Be $true - } + $file = [IO.Path]::Combine([IO.Path]::GetTempPath(), [Guid]::NewGuid().ToString()) + $results = Export-PSSession -Session $session -CommandName Get-Variable -AllowClobber -ModuleName $file $oldTimestamp = $($results | Select -First 1).LastWriteTime - - # - # error when trying to overwrite an existing directory - # - - $msg = $null - try - { - Export-PSSession -Session $s -CommandName Get-Variable -AllowClobber -ModuleName $file -EA SilentlyContinue -ErrorVariable expectedError - } - catch { } - It "Verifies that Export-PSSession fails when a module directory already exists" { - $expectedError | Should Not Be NullOrEmpty - } - $msg = [string]($expectedError[0]) - It "Verifies Error contains reference to the directory that already exists" { - ($msg -like "*$file*") | Should Be $true - } - - $newResults = Export-PSSession -Session $s -CommandName Get-Variable -AllowClobber -ModuleName $file -Force - It "Verifies that Export-PSSession returns 4 files" { - (@($newResults).Count) | Should Be 4 - } - $newResults | % { - It "Verifies that Export-PSSession creates *new* files" { - $_.LastWriteTime | Should BeGreaterThan $oldTimestamp - } - } - - # - # the module is usable when the original runspace is still around - # - - $module = import-Module $file -PassThru - It "Verifies that proxy returns remote pid" { - (Get-Variable -Name:pid).Value | Should Not Be $pid - } - Remove-Module $module -Force -ErrorAction SilentlyContinue - It "Verfies Remove-Module doesn't remove user's runspace" { - (Get-PSSession -InstanceId $s.InstanceId) | Should Not Be NullOrEmpty - } - - # - # only explicitly imported/exported commands are modifier - # - It "Verifies that no Get-Variable function before this test" { - ((Get-Item function:Get-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true - } - - set-item function:global:Get-Variable { param($Name) Microsoft.PowerShell.Utility\Get-Variable -Name:$Name } - $module = import-Module $file -PassThru -Force -Function @() - It "Verifes that our global function didn't get overwritten" { - (Get-Variable -Name:pid).Value | Should Be $pid - } - It "Verifies module.ExportedFunctions.Contains('Get-Variable')" { - ($module.ExportedFunctions.ContainsKey("Get-Variable")) | Should Be $true - } - Remove-Module $module - - Remove-Item function:global:Get-Variable -ErrorAction SilentlyContinue - Remove-Item function:script:Get-Variable -ErrorAction SilentlyContinue - Remove-Item function:Get-Variable -ErrorAction SilentlyContinue - It "Verifies no Get-Variable function after this test" { - ((Get-Item function:Get-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true - } - } - finally - { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } - if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } + AfterAll { + if ($skipTest) { return } + if ($file -ne $null) { Remove-Item $file -Force -Recurse -ErrorAction SilentlyContinue } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + } - try - { - ############################################################################## - # Export-PSSession tests continued - runspace created by the module + It "Verifies Export-PSSession creates a file/directory" -Skip:$skipTest { + @(Get-Item $file).Count | Should Be 1 + } - # the module is usable when the original runspace is no longer around + It "Verifies Export-PSSession creates a psd1 file" -Skip:$skipTest { + ($results | ?{ $_.Name -like "*$(Split-Path -Leaf $file).psd1" }) | Should Be $true + } + + It "Verifies Export-PSSession creates a psm1 file" -Skip:$skipTest { + ($results | ?{ $_.Name -like "*.psm1" }) | Should Be $true + } + + It "Verifies Export-PSSession creates a ps1xml file" -Skip:$skipTest { + ($results | ?{ $_.Name -like "*.ps1xml" }) | Should Be $true + } - $testUI = $null - try - { - if ($host.Name -eq "test host") - { - $testUI = $host.UI.GetType().GetField("externalUI", "IgnoreCase,Instance,NonPublic").GetValue($host.UI) - $oldIgnoreWrites = $testUI.IgnoreWrites - $testUI.IgnoreWrites = $true # we expect "Creating a new runspace for implicit remoting of 'Get-Variable' command..." message - } - $module = import-Module $file -PassThru -Force - It "Verifies proxy should return remote pid" { - (Get-Variable -Name:pid).Value | Should Not Be $pid - } - } - finally - { - if ($testUI) - { - $testUI.IgnoreWrites = $oldIgnoreWrites - } + It "Verifies that Export-PSSession fails when a module directory already exists" -Skip:$skipTest { + try { + Export-PSSession -Session $session -CommandName Get-Variable -AllowClobber -ModuleName $file -EA SilentlyContinue -ErrorVariable expectedError + } catch { } + + $expectedError | Should Not Be NullOrEmpty + # Error contains reference to the directory that already exists + ([string]($expectedError[0]) -like "*$file*") | Should Be $true + } + + It "Verifies that overwriting an existing directory succeeds with -Force" -Skip:$skipTest { + $newResults = Export-PSSession -Session $session -CommandName Get-Variable -AllowClobber -ModuleName $file -Force + @($newResults).Count | Should Be 3 + + # Verifies that Export-PSSession creates *new* files + $newResults | % { $_.LastWriteTime | Should BeGreaterThan $oldTimestamp } + } + + Context "The module is usable when the original runspace is still around" { + + BeforeAll { + if ($skipTest) { return } + $module = Import-Module $file -PassThru } - # let's verify if ApplicationArguments got preserved correctly - $s = & $module { $script:PSSession } - It "Verifies ApplicationArguments got preserved correctly" { - $(icm $s { $PSSenderInfo.ApplicationArguments.MyTest }) | Should Be "MyValue" - } - - # removing the module should remove the implicitly/magically created runspace - Remove-Module $module - It "Verifies Remove-Module removed automatically created runspace" { - ((Get-PSSession -InstanceId $s.InstanceId -ErrorAction SilentlyContinue) -eq $null) | Should Be $true - } - It "Verifies Runspace is closed after removing module from Export-PSSession that got initialized with an internal r-space" { - ($s.Runspace.RunspaceStateInfo.ToString()) | Should Be "Closed" - } - - ############################################################################## - # Export-PSSession tests continued - runspace created by the module with explicit session options - - try - { - if ($host.Name -eq "test host") - { - $testUI = $host.UI.GetType().GetField("externalUI", "IgnoreCase,Instance,NonPublic").GetValue($host.UI) - $oldIgnoreWrites = $testUI.IgnoreWrites - $testUI.IgnoreWrites = $true # we expect "Creating a new runspace for implicit remoting of 'Get-Variable' command..." message - } - $explicitSessionOption = New-PSSessionOption -Culture fr-FR -UICulture de-DE - $module = import-Module $file -PassThru -Force -Args $null,$explicitSessionOption - It "Verifies proxy should return remote pid" { - (Get-Variable -Name:pid).Value | Should Not Be $pid - } + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } - finally - { - if ($testUI) - { - $testUI.IgnoreWrites = $oldIgnoreWrites - } + + It "Verifies that proxy returns remote pid" -Skip:$skipTest { + (Get-Variable -Name pid).Value | Should Not Be $pid + } + + It "Verfies Remove-Module doesn't remove user's runspace" -Skip:$skipTest { + Remove-Module $module -Force -ErrorAction SilentlyContinue + (Get-PSSession -InstanceId $session.InstanceId) | Should Not Be NullOrEmpty + } + } +} + +Describe "Proxy module is usable when the original runspace is no longer around" -tags "Feature" { + BeforeAll { + # Run the tests only in FullCLR powershell because implicit credential doesn't work in AppVeyor builder + $skipTest = !$IsWindows -or $IsCoreCLR + + if ($skipTest) { return } + + $sessionOption = New-PSSessionOption -ApplicationArguments @{myTest="MyValue"} + $session = New-RemoteSession -SessionOption $sessionOption + + $file = [IO.Path]::Combine([IO.Path]::GetTempPath(), [Guid]::NewGuid().ToString()) + $null = Export-PSSession -Session $session -CommandName Get-Variable -AllowClobber -ModuleName $file + + # Close the session to test the behavior of proxy module + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue; $session = $null } + } + + AfterAll { + if ($skipTest) { return } + if ($file -ne $null) { Remove-Item $file -Force -Recurse -ErrorAction SilentlyContinue } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + } + + ## It requires 'New-PSSession' to work with implicit credential to allow proxied command to create new session. + ## Implicit credential doesn't work in AppVeyor builder, so mark all tests here '-pending'. + + Context "Proxy module should create a new session" { + BeforeAll { + if ($skipTest) { return } + $module = import-Module $file -PassThru -Force + $internalSession = & $module { $script:PSSession } + } + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Verifies proxy should return remote pid" -Pending { + (Get-Variable -Name PID).Value | Should Not Be $PID + } + + It "Verifies ApplicationArguments got preserved correctly" -Pending { + $(Invoke-Command $internalSession { $PSSenderInfo.ApplicationArguments.MyTest }) | Should Be "MyValue" + } + + It "Verifies Remove-Module removed the runspace that was automatically created" -Pending { + Remove-Module $module -Force + ((Get-PSSession -InstanceId $internalSession.InstanceId -ErrorAction SilentlyContinue) -eq $null) | Should Be $true + } + + It "Verifies Runspace is closed after removing module from Export-PSSession that got initialized with an internal r-space" -Pending { + ($internalSession.Runspace.RunspaceStateInfo.ToString()) | Should Be "Closed" + } + } + + Context "Runspace created by the module with explicit session options" { + BeforeAll { + if ($skipTest) { return } + $explicitSessionOption = New-PSSessionOption -Culture fr-FR -UICulture de-DE + $module = import-Module $file -PassThru -Force -ArgumentList $null, $explicitSessionOption + $internalSession = & $module { $script:PSSession } + } + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Verifies proxy should return remote pid" -Pending { + (Get-Variable -Name PID).Value | Should Not Be $PID } # culture settings should be taken from the explicitly passed session options - It "Verifies proxy returns modified culture" { - (Get-Variable -Name:PSCulture).Value | Should Be "fr-FR" - } - It "Verifies proxy returns modified culture" { - (Get-Variable -Name:PSUICulture).Value | Should Be "de-DE" - } + It "Verifies proxy returns modified culture" -Pending { + (Get-Variable -Name PSCulture).Value | Should Be "fr-FR" + } + It "Verifies proxy returns modified culture" -Pending { + (Get-Variable -Name PSUICulture).Value | Should Be "de-DE" + } # removing the module should remove the implicitly/magically created runspace - $s = & $module { $script:PSSession } - Remove-Module $module - It "Verifies Remove-Module removes automatically created runspace" { - ((Get-PSSession -InstanceId $s.InstanceId -ErrorAction SilentlyContinue) -eq $null) | Should Be $true - } - It "Verifies Runspace is closed after removing module from Export-PSSession that got initialized with an internal r-space" { - ($s.Runspace.RunspaceStateInfo.ToString()) | Should Be "Closed" - } - - ############################################################################## - # Export-PSSession tests continued - passing a runspace into module - - $s = New-PSSession # - $module = import-Module $file -PassThru -Force -Args $s - - It "Verifies proxy returns remote pid" { - (Get-Variable -Name:pid).Value | Should Not Be $pid - } - It "Verifies switch parameters work" { - (Get-Variable -Name:pid -ValueOnly) | Should Not Be $pid - } - It "Verifies Adding a module affects runspace's state" { - ($s.Runspace.RunspaceStateInfo.ToString()) | Should Be "Opened" - } - Remove-Module $module - It "Verifies Runspace stays opened after removing module from Export-PSSession that got initialized with an external runspace" { - ($s.Runspace.RunspaceStateInfo.ToString()) | Should Be "Opened" - } + It "Verifies Remove-Module removes automatically created runspace" -Pending { + Remove-Module $module -Force + ((Get-PSSession -InstanceId $internalSession.InstanceId -ErrorAction SilentlyContinue) -eq $null) | Should Be $true + } + It "Verifies Runspace is closed after removing module from Export-PSSession that got initialized with an internal r-space" -Pending { + ($internalSession.Runspace.RunspaceStateInfo.ToString()) | Should Be "Closed" + } } - finally - { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } - if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } - if ($file -ne $null) { Remove-Item $file -Force -Recurse -ErrorAction SilentlyContinue } + + Context "Passing a runspace into proxy module" { + BeforeAll { + if ($skipTest) { return } + + $newSession = New-RemoteSession + $module = import-Module $file -PassThru -Force -ArgumentList $newSession + $internalSession = & $module { $script:PSSession } + } + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + if ($newSession -ne $null) { Remove-PSSession $newSession -ErrorAction SilentlyContinue } + } + + It "Verifies proxy returns remote pid" -Pending { + (Get-Variable -Name PID).Value | Should Not Be $PID + } + + It "Verifies switch parameters work" -Pending { + (Get-Variable -Name PID -ValueOnly) | Should Not Be $PID + } + + It "Verifies Adding a module affects runspace's state" -Pending { + ($internalSession.Runspace.RunspaceStateInfo.ToString()) | Should Be "Opened" + } + + It "Verifies Runspace stays opened after removing module from Export-PSSession that got initialized with an external runspace" -Pending { + Remove-Module $module -Force + ($internalSession.Runspace.RunspaceStateInfo.ToString()) | Should Be "Opened" + } } } +Describe "Import-PSSession with FormatAndTypes" -tags "Feature" { -Describe "Import-Proxy with FormatAndTypes" -tags 'Innerloop', 'P1' { + BeforeAll { + # Skip test for non-windows machines for now + $skipTest = !$IsWindows - function CreateTempPs1xmlFile - { - do { - $tmpFile = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName()) + ".ps1xml"; - } while ([io.file]::exists($tmpFile)) - $tmpFile - } + if ($skipTest) { return } + $session = New-RemoteSession - function CreateTypeFile { - $tmpFile = CreateTempPs1xmlFile + function CreateTempPs1xmlFile + { + do { + $tmpFile = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName()) + ".ps1xml"; + } while ([IO.Path]::exists($tmpFile)) + $tmpFile + } + + function CreateTypeFile { + $tmpFile = CreateTempPs1xmlFile @" - + System.Management.Automation.Host.Coordinates @@ -534,862 +572,882 @@ Describe "Import-Proxy with FormatAndTypes" -tags 'Innerloop', 'P1' { "@ | set-content $tmpFile - $tmpFile - } + $tmpFile + } - function CreateFormatFile { - $tmpFile = CreateTempPs1xmlFile - @" + function CreateFormatFile { + $tmpFile = CreateTempPs1xmlFile +@" - - - MySizeView - - System.Management.Automation.Host.Size - - - - - - - - - - - - - - - Width - - - Height - - - - - - - - + + + MySizeView + + System.Management.Automation.Host.Size + + + + + + + + + + + + + + + Width + + + Height + + + + + + + + "@ | set-content $tmpFile - $tmpFile - } + $tmpFile + } - try - { - $s = New-PSSession - - ############################################################################## - # TEST: importing format file works - - $date = Get-Date - $formattingScript = { new-object System.Management.Automation.Host.Size | %{ $_.Width = 123; $_.Height = 456; $_ } | Out-String } - $typeDefinition = @" - namespace MyTest - { - public enum MyEnum - { - Value1 = 1, - Value2 = 2 - } - } -"@ - - icm -Session $s -Script { Add-Type -TypeDefinition $args[0] } -Args $typeDefinition - icm -Session $s -Script { function foo { param([MyTest.MyEnum][Parameter(Mandatory = $true)]$x) $x } } - - $originalLocalFormatting = & $formattingScript - $originalRemoteFormatting = icm $s $formattingScript - It "original local and remote formatting should be equal (sanity check)" { - $originalLocalFormatting | Should Be $originalRemoteFormatting - } - - $formatFile = CreateFormatFile - icm $s { param($file) update-formatdata $file } -args $formatFile - $modifiedRemoteFormatting = icm $s $formattingScript - It "original remote and modified remote formatting should not be equal (sanity check)" { - $originalRemoteFormatting | Should Not Be $modifiedRemoteFormatting - } - - $module = import-pssession -Session $s -CommandName @() -FormatTypeName * -AllowClobber - $importedLocalFormatting = & $formattingScript - It "modified remote and imported local should be equal" { - $modifiedRemoteFormatting | Should Be $importedLocalFormatting - } - - Remove-Module $module - $unimportedLocalFormatting = & $formattingScript - It "original local and unimported local should be equal" { - $originalLocalFormatting | Should Be $unimportedLocalFormatting - } - - ############################################################################## - # TEST: updating type table in a middle of a command has effect on serializer - - $typeFile = CreateTypeFile - - $results = icm $s -args $typeFile { param($file) - new-object System.Management.Automation.Host.Coordinates - update-typedata $file - new-object System.Management.Automation.Host.Coordinates - } - - It "Should get 2 deserialized S.M.A.H.Coordinates objects" { - ($results.Count) | Should Be 2 - } - It "First object shouldn't have the additional ETS note property" { - ($results[0].MyTestLabel -eq $null) | Should Be $true - } - It "Second object should have the additional ETS note property" { - ($results[1].MyTestLabel) | Should Be 123 - } - - ############################################################################## - # TEST: implicit remoting works even when types.ps1xml is missing on the client - - $typeDefinition = @" - namespace MyTest - { - public class Root - { - public Root(string s) { text = s; } - public Son Son = new Son(); - public string text; - } - - public class Son - { - public Grandson Grandson = new Grandson(); - } - - public class Grandson - { - public string text = "Grandson"; - } - } -"@ - - icm -Session $s -Script { Add-Type -TypeDefinition $args[0] } -Args $typeDefinition - icm -Session $s -Script { function foo { new-object MyTest.Root "root" } } - icm -Session $s -Script { function bar { param([Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]$Son) $Son.Grandson.text } } - - $m = import-pssession $s foo,bar -AllowClobber - - $x = foo - It "Serialization works for top-level properties" { - ($x.text) | Should Be "root" - } - It "Serialization settings works for deep properties" { - ($x.Son.Grandson.text) | Should Be "Grandson" - } - $y = foo | bar - It "Serialization settings are preserved even if types.ps1xml is missing on the client" { - $y | Should Be "Grandson" - } + $formatFile = CreateFormatFile + $typeFile = CreateTypeFile } - finally - { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } + + AfterAll { + if ($skipTest) { return } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } if ($formatFile -ne $null) { Remove-Item $formatFile -Force -ErrorAction SilentlyContinue } if ($typeFile -ne $null) { Remove-Item $typeFile -Force -ErrorAction SilentlyContinue } - if ($m -ne $null) { Remove-Module $m -Force -ErrorAction SilentlyContinue } + } + + Context "Importing format file works" { + BeforeAll { + if ($skipTest) { return } + + $formattingScript = { new-object System.Management.Automation.Host.Size | %{ $_.Width = 123; $_.Height = 456; $_ } | Out-String } + $originalLocalFormatting = & $formattingScript + + # Original local and remote formatting should be equal (sanity check) + $originalRemoteFormatting = Invoke-Command $session $formattingScript + $originalLocalFormatting | Should Be $originalRemoteFormatting + + Invoke-Command $session { param($file) Update-FormatData $file } -ArgumentList $formatFile + + # Original remote and modified remote formatting should not be equal (sanity check) + $modifiedRemoteFormatting = Invoke-Command $session $formattingScript + $originalRemoteFormatting | Should Not Be $modifiedRemoteFormatting + + $module = Import-PSSession -Session $session -CommandName @() -FormatTypeName * -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "modified remote and imported local should be equal" -Skip:$skipTest { + $importedLocalFormatting = & $formattingScript + $modifiedRemoteFormatting | Should Be $importedLocalFormatting + } + + It "original local and unimported local should be equal" -Skip:$skipTest { + Remove-Module $module -Force + $unimportedLocalFormatting = & $formattingScript + $originalLocalFormatting | Should Be $unimportedLocalFormatting + } + } + + It "Updating type table in a middle of a command has effect on serializer" -Skip:$skipTest { + $results = Invoke-Command $session -ArgumentList $typeFile -ScriptBlock { + param($file) + + New-Object System.Management.Automation.Host.Coordinates + Update-TypeData $file + New-Object System.Management.Automation.Host.Coordinates + } + + # Should get 2 deserialized S.M.A.H.Coordinates objects + $results.Count | Should Be 2 + # First object shouldn't have the additional ETS note property + $results[0].MyTestLabel -eq $null | Should Be $true + # Second object should have the additional ETS note property + $results[1].MyTestLabel | Should Be 123 + } + + Context "Implicit remoting works even when types.ps1xml is missing on the client" { + BeforeAll { + if ($skipTest) { return } + + $typeDefinition = @" + namespace MyTest + { + public class Root + { + public Root(string s) { text = s; } + public Son Son = new Son(); + public string text; + } + + public class Son + { + public Grandson Grandson = new Grandson(); + } + + public class Grandson + { + public string text = "Grandson"; + } + } +"@ + Invoke-Command -Session $session -Script { Add-Type -TypeDefinition $args[0] } -ArgumentList $typeDefinition + Invoke-Command -Session $session -Script { function foo { New-Object MyTest.Root "root" } } + Invoke-Command -Session $session -Script { function bar { param([Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]$Son) $Son.Grandson.text } } + + $module = import-pssession $session foo,bar -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Serialization works for top-level properties" -Skip:$skipTest { + $x = foo + $x.text | Should Be "root" + } + + It "Serialization settings works for deep properties" -Skip:$skipTest { + $x = foo + $x.Son.Grandson.text | Should Be "Grandson" + } + + It "Serialization settings are preserved even if types.ps1xml is missing on the client" -Skip:$skipTest { + $y = foo | bar + $y | Should Be "Grandson" + } } } +Describe "Import-PSSession functional tests" -tags "Feature" { + + BeforeAll { + # Skip test for non-windows machines for now + $skipTest = !$IsWindows -Describe "Import-PSSession functional tests" -tags 'Innerloop', 'P1' { + if ($skipTest) { return } + $session = New-RemoteSession - try - { - $s = New-PSSession + # Define a remote function + Invoke-Command -Session $session { function MyFunction { param($x) "x = '$x'; args = '$args'" } } - # define a remote function - Invoke-Command -Session $s { function MyFunction { param($x) "x = '$x'; args = '$args'" } } - - # define a remote proxy script cmdlet + # Define a remote proxy script cmdlet $remoteCommandType = $ExecutionContext.InvokeCommand.GetCommand('Get-Variable', [System.Management.Automation.CommandTypes]::Cmdlet) $remoteProxyBody = [System.Management.Automation.ProxyCommand]::Create($remoteCommandType) $remoteProxyDeclaration = "function Get-VariableProxy { $remoteProxyBody }" - Invoke-Command -Session $s { param($x) Invoke-Expression $x } -Arg $remoteProxyDeclaration + Invoke-Command -Session $session { param($x) Invoke-Expression $x } -Arg $remoteProxyDeclaration $remoteAliasDeclaration = "set-alias gvalias Get-Variable" - Invoke-Command -Session $s { param($x) Invoke-Expression $x } -Arg $remoteAliasDeclaration - del alias:gvalias -force -ea silentlycontinue + Invoke-Command -Session $session { param($x) Invoke-Expression $x } -Arg $remoteAliasDeclaration + Remove-Item alias:gvalias -Force -ErrorAction silentlycontinue - # import a remote function, script cmdlet, cmdlet, native application, alias - $module = Import-PSSession -Session $s -Name MyFunction,Get-VariableProxy,Get-Variable,gvalias,cmd -AllowClobber -Type All - It "Import-PSSession should return a PSModuleInfo object" { - $module | Should Not Be NullOrEmpty - } - It "Import-PSSession should return a PSModuleInfo object" { - ($module -is [System.Management.Automation.PSModuleInfo]) | Should Not Be NullOrEmpty - } - It "Helper functions should not be imported" { - ((Get-Item function:*PSImplicitRemoting* -ErrorAction SilentlyContinue) -eq $null) | Should Be $true - } - - # test calling implicit remoting proxies - It "NoName-ef2e1dbb-6278-4c1f-99b8-5edd68aa1679" { - (MyFunction 1 2 3) | Should Be "x = '1'; args = '2 3'" - } - - It "proxy should return remote pid" { - (Get-VariableProxy -Name:pid).Value | Should Not Be $pid - } - It "proxy should return remote pid" { - (Get-Variable -Name:pid).Value | Should Not Be $pid - } - It "proxy should return remote pid" { - $(& (Get-Command gvalias -Type alias) -Name:pid).Value | Should Not Be $pid - } - - Invoke-Command -Session $s { $env:TestImplicitRemotingVariable = 123 } - It "NoName-c8aeb5c8-2388-4d64-98c1-a9c6c218d404" { - (cmd.exe /c "echo TestImplicitRemotingVariable=%TestImplicitRemotingVariable%") | Should Be "TestImplicitRemotingVariable=123" - } - - # test what happens after the runspace is closed - Remove-PSSession $s - $s = $null - - # The loop below works around the fact that PSEventManager uses threadpool worker to queue event handler actions to process later - # Usage of threadpool means that it is impossible to predict when the event handler will run (this is Windows 8 Bugs: #882977) - $i = 0 - while ( ($i -lt 20) -and ($null -ne (Get-Module | ?{ $_.Path -eq $module.Path })) ) - { - $i++ - Start-Sleep -Milliseconds 50 - } - Write-Host "Workaround for bug 882977 used $i iterations" - It "Temporary module should be automatically removed after runspace is closed" { - ((Get-Module | ?{ $_.Path -eq $module.Path }) -eq $null) | Should Be $true - } - - It "Temporary psm1 file should be automatically removed after runspace is closed" { - ((Get-Item $module.Path -ErrorAction SilentlyContinue) -eq $null) | Should Be $true - } - - # Check that the implicit remoting event has been removed. - $implicitEventCount = 0 - foreach ($item in $ExecutionContext.Events.Subscribers) - { - if ($item.SourceIdentifier -match "Implicit remoting event") { $implicitEventCount++ } - } - It "Event should be unregistered when the runspace is closed" { - 0 | Should Be ($implicitEventCount) - } - - It "Private functions from the implicit remoting module shouldn't get imported into global scope" { - 0 | Should Be @(dir function:*Implicit* -ErrorAction SilentlyContinue).Count - } + # Import a remote function, script cmdlet, cmdlet, native application, alias + $module = Import-PSSession -Session $session -Name MyFunction,Get-VariableProxy,Get-Variable,gvalias,cmd -AllowClobber -Type All } - finally - { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } - if ($module -ne $null) { Remove-Module $module -ErrorAction SilentlyContinue } - } -} - -Describe "Implicit remoting parameter binding" -tags 'Innerloop', 'P1' { - - try - { - $s = New-PSSession - - ############################################################################## - # TEST: work-around for dynamic parameters via -ArgumentList parameter - - $dynamicParametersTest = @' - function Get-DynamicParameters - { - [CmdletBinding(DefaultParameterSetName='DefaultParameterSet')] - - param( - [Parameter(ParameterSetName = "DefaultParameterSet")] - $staticParameter - ) - - dynamicParam - { - $attributes = new-object System.Management.Automation.ParameterAttribute - $attributes.ParameterSetName = 'DefaultParameterSet' - $attributes.Mandatory = $false - - $attributeCollection = new-object -Type System.Collections.ObjectModel.Collection``1[System.Attribute] - $attributeCollection.Add($attributes) - - $dynParam1 = new-object System.Management.Automation.RuntimeDefinedParameter("dynamicParameter", [int], $attributeCollection) - - $paramDictionary = new-object System.Management.Automation.RuntimeDefinedParameterDictionary - $paramDictionary.Add("dynamicParameter", $dynParam1) - - return $paramDictionary - } - - begin - { - "static=$staticParameter; dynamic=$($paramDictionary.dynamicParameter.Value)" - } - process {} - end {} - } -'@ - - # unfortunately -ArgumentList works only for cmdlets, not for script cmdlets - Invoke-Command -Session $s { param($x) Invoke-Expression $x } -Arg $dynamicParametersTest - - ############################################################################## - # TEST: binding of ValueFromPipeline type of parameters - - $module = Import-PSSession -Session:$s -Name:Get-Random -AllowClobber - $x = 1..20 | Get-Random -Count 5 - It "Binding of ValueFromPipeline should work" { - $x.Count | Should Be 5 - } - Remove-Module $module - - ############################################################################## - # TEST: pipeline-based parameter binding works even when client has no type constraints (Windows 7: #391157) - - icm $s { - function foo { - [cmdletbinding(defaultparametersetname="string")] - param( - [string] - [parameter(ParameterSetName="string", ValueFromPipeline = $true)] - $string, - - [ipaddress] - [parameter(ParameterSetName="ipaddress", ValueFromPipeline = $true)] - $ipaddress - ) - - "Bound parameter: $($myInvocation.BoundParameters.Keys | sort)" - } - } - - It "Sanity check (no remoting). Pipeline binding works even if it relies on type constraints" { - $(icm $s {"s" | foo}) | Should Be "Bound parameter: string" - } - It "Sanity check (no remoting). Pipeline binding works even if it relies on type constraints" { - $(icm $s {[ipaddress]::parse("127.0.0.1") | foo}) | Should Be "Bound parameter: ipaddress" - } - - $module = Import-PSSession $s foo -AllowClobber - It "Pipeline binding works even if it relies on type constraints" { - $("s" | foo) | Should Be "Bound parameter: string" - } - It "Pipeline binding works even if it relies on type constraints" { - $([ipaddress]::parse("127.0.0.1") | foo) | Should Be "Bound parameter: ipaddress" - } - Remove-Module $module - - ############################################################################## - # TEST: pipeline-based parameter binding works even when client has no type constraints and parameterset is ambiguous (Windows 7: #430379) - - icm $s { - function foo { - param( - [string] - [parameter(ParameterSetName="string", ValueFromPipeline = $true)] - $string, - - [ipaddress] - [parameter(ParameterSetName="ipaddress", ValueFromPipeline = $true)] - $ipaddress - ) - - "Bound parameter: $($myInvocation.BoundParameters.Keys)" - } - } - - It "Sanity check (no remoting). Pipeline binding works even if it relies on type constraints and parameter set is ambiguous" { - $(icm $s {"s" | foo}) | Should Be "Bound parameter: string" - } - It "Sanity check (no remoting). Pipeline binding works even if it relies on type constraints and parameter set is ambiguous" { - $(icm $s {[ipaddress]::parse("127.0.0.1") | foo}) | Should Be "Bound parameter: ipaddress" - } - - $module = Import-PSSession $s foo -AllowClobber - It "Pipeline binding works even if it relies on type constraints and parameter set is ambiguous" { - $("s" | foo) | Should Be "Bound parameter: string" - } - It "Pipeline binding works even if it relies on type constraints and parameter set is ambiguous" { - $([ipaddress]::parse("127.0.0.1") | foo) | Should Be "Bound parameter: ipaddress" - } - Remove-Module $module - - ############################################################################## - # TEST: pipeline-based parameter binding works even when one of parameters - # that can be bound by pipeline gets bound by name - - icm $s { - function foo { - param( - [DateTime] - [parameter(ValueFromPipeline = $true)] - $date, - - [ipaddress] - [parameter(ValueFromPipeline = $true)] - $ipaddress - ) - - "Bound parameter: $($myInvocation.BoundParameters.Keys | sort)" - } - } - - It "Sanity check (no remoting)" { - $( icm $s { get-date | foo } ) | Should Be "Bound parameter: date" - } - It "Sanity check (no remoting)" { - $( icm $s { [ipaddress]::parse("127.0.0.1") | foo } ) | Should Be "Bound parameter: ipaddress" - } - It "Sanity check (no remoting)" { - $( icm $s { [ipaddress]::parse("127.0.0.1") | foo -date $(get-date) } ) | Should Be "Bound parameter: date ipaddress" - } - It "Sanity check (no remoting)" { - $( icm $s { get-date | foo -ipaddress ([ipaddress]::parse("127.0.0.1")) } ) | Should Be "Bound parameter: date ipaddress" - } - - $module = Import-PSSession $s foo -AllowClobber - It "Pipeline binding works even when also binding by name" { - $( get-date | foo ) | Should Be "Bound parameter: date" - } - It "Pipeline binding works even when also binding by name" { - $( [ipaddress]::parse("127.0.0.1") | foo ) | Should Be "Bound parameter: ipaddress" - } - It "Pipeline binding works even when also binding by name" { - $( [ipaddress]::parse("127.0.0.1") | foo -date $(get-date) ) | Should Be "Bound parameter: date ipaddress" - } - It "Pipeline binding works even when also binding by name" { - $( get-date | foo -ipaddress ([ipaddress]::parse("127.0.0.1")) ) | Should Be "Bound parameter: date ipaddress" - } - Remove-Module $module - - ############################################################################## - # TEST: value from pipeline by property name - multiple parameters - - icm $s { - function foo { - param( - [System.TimeSpan] - [parameter(ValueFromPipelineByPropertyName = $true)] - $TotalProcessorTime, - - [System.Diagnostics.ProcessPriorityClass] - [parameter(ValueFromPipelineByPropertyName = $true)] - $PriorityClass - ) - - "Bound parameter: $($myInvocation.BoundParameters.Keys | sort)" - } - } - - It "Sanity check (no remoting)." { - $(icm $s { gps -pid $pid | foo }) | Should Be "Bound parameter: PriorityClass TotalProcessorTime" - } - It "Sanity check (no remoting)." { - $(icm $s { gps -pid $pid | foo -Total 5 }) | Should Be "Bound parameter: PriorityClass TotalProcessorTime" - } - It "Sanity check (no remoting)." { - $(icm $s { gps -pid $pid | foo -Priority normal }) | Should Be "Bound parameter: PriorityClass TotalProcessorTime" - } - - $module = Import-PSSession $s foo -AllowClobber - It "Pipeline binding works by property name" { - $( gps -id $pid | foo ) | Should Be "Bound parameter: PriorityClass TotalProcessorTime" - } - It "Pipeline binding works by property name" { - $( gps -id $pid | foo -Total 5 ) | Should Be "Bound parameter: PriorityClass TotalProcessorTime" - } - It "Pipeline binding works by property name" { - $( gps -id $pid | foo -Priority normal ) | Should Be "Bound parameter: PriorityClass TotalProcessorTime" - } - Remove-Module $module - - ############################################################################## - # TEST: 2 parameters on the same position - # - - icm $s { - function foo { - param( - [string] - [parameter(Position = 0, parametersetname = 'set1', mandatory = $true)] - $string, - - [ipaddress] - [parameter(Position = 0, parametersetname = 'set2', mandatory = $true)] - $ipaddress - ) - - "Bound parameter: $($myInvocation.BoundParameters.Keys | sort)" - } - } - - It "Sanity check (no remoting)." { - $(icm $s { foo ([ipaddress]::parse("127.0.0.1")) }) | Should Be "Bound parameter: ipaddress" - } - It "Sanity check (no remoting)." { - $(icm $s { foo "blah" }) | Should Be "Bound parameter: string" - } - - $module = Import-PSSession $s foo -AllowClobber - It "Positional binding works" { - $( foo "blah" ) | Should Be "Bound parameter: string" - } - It "Positional binding works" { - $( foo ([ipaddress]::parse("127.0.0.1")) ) | Should Be "Bound parameter: ipaddress" - } - Remove-Module $module - - ############################################################################## - # TEST: positional binding and array argument value - # - - icm $s { - function foo { - param( - [object] - [parameter(Position = 0, mandatory = $true)] - $p1, - - [object] - [parameter(Position = 1)] - $p2 - ) - - "$p1 : $p2" - } - } - - It "Sanity check (no remoting)." { - $(icm $s { foo 1,2,3 }) | Should Be "1 2 3 : " - } - It "Sanity check (no remoting)." { - $(icm $s { foo 1,2,3 4 }) | Should Be "1 2 3 : 4" - } - It "Sanity check (no remoting)." { - $(icm $s { foo -p2 4 1,2,3 }) | Should Be "1 2 3 : 4" - } - It "Sanity check (no remoting)." { - $(icm $s { foo 1 4 }) | Should Be "1 : 4" - } - It "Sanity check (no remoting)." { - $(icm $s { foo -p2 4 1 }) | Should Be "1 : 4" - } - - $module = Import-PSSession $s foo -AllowClobber - It "Positional binding works when binding an array value" { - $( foo 1,2,3 ) | Should Be "1 2 3 : " - } - It "Positional binding works when binding an array value" { - $( foo 1,2,3 4 ) | Should Be "1 2 3 : 4" - } - It "Positional binding works when binding an array value" { - $( foo -p2 4 1,2,3 ) | Should Be "1 2 3 : 4" - } - It "Positional binding works when binding an array value" { - $( foo 1 4 ) | Should Be "1 : 4" - } - It "Positional binding works when binding an array value" { - $( foo -p2 4 1 ) | Should Be "1 : 4" - } - Remove-Module $module - - - ############################################################################## - # TEST: value from remaining arguments - # - - icm $s { - function foo { - param( - [string] - [parameter(Position = 0)] - $firstArg, - - [string[]] - [parameter(ValueFromRemainingArguments = $true)] - $remainingArgs - ) - - "$firstArg : $remainingArgs" - } - } - - It "Sanity check (no remoting)." { - $(icm $s { foo }) | Should Be " : " - } - It "Sanity check (no remoting)." { - $(icm $s { foo 1 }) | Should Be "1 : " - } - It "Sanity check (no remoting)." { - $(icm $s { foo -first 1 }) | Should Be "1 : " - } - It "Sanity check (no remoting)." { - $(icm $s { foo 1 2 3 }) | Should Be "1 : 2 3" - } - It "Sanity check (no remoting)." { - $(icm $s { foo -first 1 2 3 }) | Should Be "1 : 2 3" - } - It "Sanity check (no remoting)." { - $(icm $s { foo 2 3 -first 1 4 5 }) | Should Be "1 : 2 3 4 5" - } - It "Sanity check (no remoting)." { - $(icm $s { foo -remainingArgs 2,3 1 }) | Should Be "1 : 2 3" - } - - $module = Import-PSSession $s foo -AllowClobber - It "Value from remaining arguments works" { - $( foo ) | Should Be " : " - } - It "Value from remaining arguments works" { - $( foo 1 ) | Should Be "1 : " - } - It "Value from remaining arguments works" { - $( foo -first 1 ) | Should Be "1 : " - } - It "Value from remaining arguments works" { - $( foo 1 2 3 ) | Should Be "1 : 2 3" - } - It "Value from remaining arguments works" { - $( foo -first 1 2 3 ) | Should Be "1 : 2 3" - } - It "Value from remaining arguments works" { - $( foo 2 3 -first 1 4 5 ) | Should Be "1 : 2 3 4 5" - } - It "Value from remaining arguments works" { - $( foo -remainingArgs 2,3 1 ) | Should Be "1 : 2 3" - } - - Remove-Module $module - - ############################################################################## - # TEST: non cmdlet-based binding - # - - icm $s { - function foo { - param( - $firstArg, - $secondArg - ) - - "$firstArg : $secondArg : $args" - } - } - - It "Sanity check (no remoting)." { - $(icm $s { foo }) | Should Be " : : " - } - It "Sanity check (no remoting)." { - $(icm $s { foo 1 }) | Should Be "1 : : " - } - It "Sanity check (no remoting)." { - $(icm $s { foo -first 1 }) | Should Be "1 : : " - } - It "Sanity check (no remoting)." { - $(icm $s { foo 1 2 }) | Should Be "1 : 2 : " - } - It "Sanity check (no remoting)." { - $(icm $s { foo 1 -second 2 }) | Should Be "1 : 2 : " - } - It "Sanity check (no remoting)." { - $(icm $s { foo -first 1 -second 2 }) | Should Be "1 : 2 : " - } - It "Sanity check (no remoting)." { - $(icm $s { foo 1 2 3 4 }) | Should Be "1 : 2 : 3 4" - } - It "Sanity check (no remoting)." { - $(icm $s { foo -first 1 2 3 4 }) | Should Be "1 : 2 : 3 4" - } - It "Sanity check (no remoting)." { - $(icm $s { foo 1 -second 2 3 4 }) | Should Be "1 : 2 : 3 4" - } - It "Sanity check (no remoting)." { - $(icm $s { foo 1 3 -second 2 4 }) | Should Be "1 : 2 : 3 4" - } - It "Sanity check (no remoting)." { - $(icm $s { foo -first 1 -second 2 3 4 }) | Should Be "1 : 2 : 3 4" - } - - $module = Import-PSSession $s foo -AllowClobber - It "Non cmdlet-based binding works." { - $( foo ) | Should Be " : : " - } - - It "Non cmdlet-based binding works." { - $( foo 1 ) | Should Be "1 : : " - } - It "Non cmdlet-based binding works." { - $( foo -first 1 ) | Should Be "1 : : " - } - It "Non cmdlet-based binding works." { - $( foo 1 2 ) | Should Be "1 : 2 : " - } - It "Non cmdlet-based binding works." { - $( foo 1 -second 2 ) | Should Be "1 : 2 : " - } - It "Non cmdlet-based binding works." { - $( foo -first 1 -second 2 ) | Should Be "1 : 2 : " - } - It "Non cmdlet-based binding works." { - $( foo 1 2 3 4 ) | Should Be "1 : 2 : 3 4" - } - It "Non cmdlet-based binding works." { - $( foo -first 1 2 3 4 ) | Should Be "1 : 2 : 3 4" - } - It "Non cmdlet-based binding works." { - $( foo 1 -second 2 3 4 ) | Should Be "1 : 2 : 3 4" - } - It "Non cmdlet-based binding works." { - $( foo 1 3 -second 2 4 ) | Should Be "1 : 2 : 3 4" - } - It "Non cmdlet-based binding works." { - $( foo -first 1 -second 2 3 4 ) | Should Be "1 : 2 : 3 4" - } - Remove-Module $module - - ############################################################################## - # TEST: default parameter initialization should be executed on the server - - Invoke-Command -Session $s { param($x) IEx $x } -Args 'function MyInitializerFunction { param($x = $($pid)) $x }' - $module = Import-PSSession -Session:$s -Name:MyInitializerFunction -AllowClobber - $localPid = $pid - $remotePid = icm $s { $pid } - It "Sanity check - remotePid != localPid" { - $localPid | Should Not Be $remotePid - } - It "Initializer run on the remote server" { - (MyInitializerFunction) | Should Be $remotePid - } - It "Initializer not run when value provided" { - (MyInitializerFunction 123) | Should Be 123 - } - Remove-Module $module - - ############################################################################## - # TEST: client-side parameters - cmdlet case - - $module = Import-PSSession -Session:$s -Name:Get-Variable -Type:cmdlet -AllowClobber - It "Importing by name/type should work" { - (Get-Variable -Name:pid).Value | Should Not Be $pid - } - $remotePid = (Get-Variable -Name:pid).Value - - $job = Get-Variable -Name:pid -AsJob - It "-AsJob should return something" { - $job | Should Not Be NullOrEmpty - } - It "-AsJob returns the right type of object" { - ($job -is [System.Management.Automation.Job]) | Should Be $true - } - It "Job completes within reasonable time" { - ($job.Finished.WaitOne([TimeSpan]::FromSeconds(10), $false)) | Should Be $true - } - It "AsJob: $job.JobStateInfo.State" { - $job.JobStateInfo.State | Should Be 'Completed' - } - $childJob = $job.ChildJobs[0] - It "AsJob: $childJob.Output.Count" { - $childJob.Output.Count | Should Be 1 - } - It "AsJob: $childJob.Output[0].Value" { - $childJob.Output[0].Value | Should Be $remotePid - } - Remove-Job $job - - $result1 = Get-Variable -Name:pid -OutVariable global:result2 - It "OutVariable: $result1.Value" { - $result1.Value | Should Be $remotePid - } - It "OutVariable: $result2[0].Value" { - $global:result2[0].Value | Should Be $remotePid - } - - Remove-Module $module - - ############################################################################## - # TEST: client-side parameters - Windows 7 bug #759434 - - $module = Import-PSSession -Session:$s -Name:Write-Warning -Type:cmdlet -Prefix Remote -AllowClobber - - $jobWithWarnings = write-remotewarning foo -warningaction continue -asjob - $null = Wait-Job $jobWithWarnings - It "Warnings present if -WarningAction Continue" { - $jobWithWarnings.ChildJobs[0].Warning.Count | Should Be 1 - } - Remove-Job $jobWithWarnings - - $jobWithoutWarnings = write-remotewarning foo -warningaction silentlycontinue -asjob - $null = Wait-Job $jobWithoutWarnings - It "No warnings if -WarningAction SilentlyContinue" { - 0 | Should Be ($jobWithoutWarnings.ChildJobs[0].Warning.Count) - } - Remove-Job $jobWithoutWarnings - - Remove-Module $module - - ############################################################################## - # TEST: client-side parameters - non-cmdlet case - - icm $s { function foo { param($OutVariable) "OutVariable = $OutVariable" } } - It "Sanity check: OutVariable is not intercepted for non-cmdlet-bound functions" { - $(icm $s { foo -OutVariable x }) | Should Be "OutVariable = x" - } - $module = Import-PSSession -Session:$s -Name:foo -Type:function -AllowClobber - It "Implicit remoting: OutVariable is not intercepted for non-cmdlet-bound functions" { - $( foo -OutVariable x ) | Should Be "OutVariable = x" - } - Remove-Module $module - - ############################################################################## - # TEST: switch and positional parameters - - $module = Import-PSSession -Session $s -Name Get-Variable -Type cmdlet -Prefix Remote -AllowClobber - $remotePid = ICm $s { $pid } - It "Sanity check: remote pid != local pid" { - $remotePid | Should Not Be $pid - } - - # switch - $proxiedPid = Get-RemoteVariable -Name pid -ValueOnly - It "Switch parameters work fine" { - $remotePid | Should Be $proxiedPid - } - - # positional - $proxiedPid = Get-RemoteVariable pid - It "Positional parameters work fine" { - $remotePid | Should Be ($proxiedPid.Value) - } - } - finally - { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } + AfterAll { + if ($skipTest) { return } if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + } + + It "Import-PSSession should return a PSModuleInfo object" -Skip:$skipTest { + $module | Should Not Be NullOrEmpty + } + + It "Import-PSSession should return a PSModuleInfo object" -Skip:$skipTest { + ($module -as [System.Management.Automation.PSModuleInfo]) | Should Not Be NullOrEmpty + } + + It "Helper functions should not be imported" -Skip:$skipTest { + ((Get-Item function:*PSImplicitRemoting* -ErrorAction SilentlyContinue) -eq $null) | Should Be $true + } + + It "Calls implicit remoting proxies 'MyFunction'" -Skip:$skipTest { + (MyFunction 1 2 3) | Should Be "x = '1'; args = '2 3'" + } + + It "proxy should return remote pid" -Skip:$skipTest { + (Get-VariableProxy -Name:pid).Value | Should Not Be $pid + } + + It "proxy should return remote pid" -Skip:$skipTest { + (Get-Variable -Name:pid).Value | Should Not Be $pid + } + + It "proxy should return remote pid" -Skip:$skipTest { + $(& (Get-Command gvalias -Type alias) -Name:pid).Value | Should Not Be $pid + } + + It "NoName-c8aeb5c8-2388-4d64-98c1-a9c6c218d404" -Skip:$skipTest { + Invoke-Command -Session $session { $env:TestImplicitRemotingVariable = 123 } + (cmd.exe /c "echo TestImplicitRemotingVariable=%TestImplicitRemotingVariable%") | Should Be "TestImplicitRemotingVariable=123" + } + + Context "Test what happens after the runspace is closed" { + BeforeAll { + if ($skipTest) { return } + + Remove-PSSession $session + + # The loop below works around the fact that PSEventManager uses threadpool worker to queue event handler actions to process later. + # Usage of threadpool means that it is impossible to predict when the event handler will run (this is Windows 8 Bugs: #882977). + $i = 0 + while ( ($i -lt 20) -and ($null -ne (Get-Module | ? { $_.Path -eq $module.Path })) ) + { + $i++ + Start-Sleep -Milliseconds 50 + } + } + + It "Temporary module should be automatically removed after runspace is closed" -Skip:$skipTest { + ((Get-Module | ? { $_.Path -eq $module.Path }) -eq $null) | Should Be $true + } + + It "Temporary psm1 file should be automatically removed after runspace is closed" -Skip:$skipTest { + ((Get-Item $module.Path -ErrorAction SilentlyContinue) -eq $null) | Should Be $true + } + + It "Event should be unregistered when the runspace is closed" -Skip:$skipTest { + # Check that the implicit remoting event has been removed. + $implicitEventCount = 0 + foreach ($item in $ExecutionContext.Events.Subscribers) + { + if ($item.SourceIdentifier -match "Implicit remoting event") { $implicitEventCount++ } + } + $implicitEventCount | Should Be 0 + } + + It "Private functions from the implicit remoting module shouldn't get imported into global scope" -Skip:$skipTest { + @(Get-ChildItem function:*Implicit* -ErrorAction SilentlyContinue).Count | Should Be 0 + } } } +Describe "Implicit remoting parameter binding" -tags "Feature" { -Describe "Implicit remoting on restricted ISS" -tags 'Innerloop', 'P1' { + BeforeAll { + # Skip test for non-windows machines for now + $skipTest = !$IsWindows - if (${env:PROCESSOR_ARCHITECTURE} -eq 'ARM') - { - Write-Warning "Skipping the test on ARM" - return + if ($skipTest) { return } + $session = New-RemoteSession } - ############################################################################## - # SETUP: create a remote end-point + AfterAll { + if ($skipTest) { return } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + } + + It "Binding of ValueFromPipeline should work" -Skip:$skipTest { + try { + $module = Import-PSSession -Session $session -Name Get-Random -AllowClobber + $x = 1..20 | Get-Random -Count 5 + $x.Count | Should Be 5 + } finally { + Remove-Module $module -Force + } + } + + Context "Pipeline-based parameter binding works even when client has no type constraints (Windows 7: #391157)" { + BeforeAll { + if ($skipTest) { return } + + Invoke-Command -Session $session -ScriptBlock { + function foo { + [cmdletbinding(defaultparametersetname="string")] + param( + [string] + [parameter(ParameterSetName="string", ValueFromPipeline = $true)] + $string, + + [ipaddress] + [parameter(ParameterSetName="ipaddress", ValueFromPipeline = $true)] + $ipaddress + ) + + "Bound parameter: $($myInvocation.BoundParameters.Keys | sort)" + } + } + + # Sanity checks. + Invoke-Command $session {"s" | foo} | Should Be "Bound parameter: string" + Invoke-Command $session {[ipaddress]::parse("127.0.0.1") | foo} | Should Be "Bound parameter: ipaddress" + + $module = Import-PSSession $session foo -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Pipeline binding works even if it relies on type constraints" -Skip:$skipTest { + ("s" | foo) | Should Be "Bound parameter: string" + } + + It "Pipeline binding works even if it relies on type constraints" -Skip:$skipTest { + ([ipaddress]::parse("127.0.0.1") | foo) | Should Be "Bound parameter: ipaddress" + } + } + + Context "Pipeline-based parameter binding works even when client has no type constraints and parameterset is ambiguous (Windows 7: #430379)" { + BeforeAll { + if ($skipTest) { return } + + Invoke-Command -Session $session -ScriptBlock { + function foo { + param( + [string] + [parameter(ParameterSetName="string", ValueFromPipeline = $true)] + $string, + + [ipaddress] + [parameter(ParameterSetName="ipaddress", ValueFromPipeline = $true)] + $ipaddress + ) + + "Bound parameter: $($myInvocation.BoundParameters.Keys)" + } + } + + # Sanity checks. + Invoke-Command $session {"s" | foo} | Should Be "Bound parameter: string" + Invoke-Command $session {[ipaddress]::parse("127.0.0.1") | foo} | Should Be "Bound parameter: ipaddress" + + $module = Import-PSSession $session foo -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Pipeline binding works even if it relies on type constraints and parameter set is ambiguous" -Skip:$skipTest { + ("s" | foo) | Should Be "Bound parameter: string" + } + + It "Pipeline binding works even if it relies on type constraints and parameter set is ambiguous" -Skip:$skipTest { + ([ipaddress]::parse("127.0.0.1") | foo) | Should Be "Bound parameter: ipaddress" + } + } + + Context "pipeline-based parameter binding works even when one of parameters that can be bound by pipeline gets bound by name" { + BeforeAll { + if ($skipTest) { return } + + Invoke-Command -Session $session -ScriptBlock { + function foo { + param( + [DateTime] + [parameter(ValueFromPipeline = $true)] + $date, + + [ipaddress] + [parameter(ValueFromPipeline = $true)] + $ipaddress + ) + + "Bound parameter: $($myInvocation.BoundParameters.Keys | sort)" + } + } + + # Sanity checks. + Invoke-Command $session {Get-Date | foo} | Should Be "Bound parameter: date" + Invoke-Command $session {[ipaddress]::parse("127.0.0.1") | foo} | Should Be "Bound parameter: ipaddress" + Invoke-Command $session {[ipaddress]::parse("127.0.0.1") | foo -date (get-date)} | Should Be "Bound parameter: date ipaddress" + Invoke-Command $session {Get-Date | foo -ipaddress ([ipaddress]::parse("127.0.0.1"))} | Should Be "Bound parameter: date ipaddress" + + $module = Import-PSSession $session foo -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Pipeline binding works even when also binding by name" -Skip:$skipTest { + (Get-Date | foo) | Should Be "Bound parameter: date" + } + + It "Pipeline binding works even when also binding by name" -Skip:$skipTest { + ([ipaddress]::parse("127.0.0.1") | foo) | Should Be "Bound parameter: ipaddress" + } + + It "Pipeline binding works even when also binding by name" -Skip:$skipTest { + ([ipaddress]::parse("127.0.0.1") | foo -date $(Get-Date)) | Should Be "Bound parameter: date ipaddress" + } + + It "Pipeline binding works even when also binding by name" -Skip:$skipTest { + (Get-Date | foo -ipaddress ([ipaddress]::parse("127.0.0.1"))) | Should Be "Bound parameter: date ipaddress" + } + } + + Context "value from pipeline by property name - multiple parameters" { + BeforeAll { + if ($skipTest) { return } + + Invoke-Command -Session $session -ScriptBlock { + function foo { + param( + [System.TimeSpan] + [parameter(ValueFromPipelineByPropertyName = $true)] + $TotalProcessorTime, + + [System.Diagnostics.ProcessPriorityClass] + [parameter(ValueFromPipelineByPropertyName = $true)] + $PriorityClass + ) + + "Bound parameter: $($myInvocation.BoundParameters.Keys | sort)" + } + } + + # Sanity checks. + Invoke-Command $session {gps -pid $pid | foo} | Should Be "Bound parameter: PriorityClass TotalProcessorTime" + Invoke-Command $session {gps -pid $pid | foo -Total 5} | Should Be "Bound parameter: PriorityClass TotalProcessorTime" + Invoke-Command $session {gps -pid $pid | foo -Priority normal} | Should Be "Bound parameter: PriorityClass TotalProcessorTime" + + $module = Import-PSSession $session foo -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Pipeline binding works by property name" -Skip:$skipTest { + (gps -id $pid | foo) | Should Be "Bound parameter: PriorityClass TotalProcessorTime" + } + + It "Pipeline binding works by property name" -Skip:$skipTest { + (gps -id $pid | foo -Total 5) | Should Be "Bound parameter: PriorityClass TotalProcessorTime" + } + + It "Pipeline binding works by property name" -Skip:$skipTest { + (gps -id $pid | foo -Priority normal) | Should Be "Bound parameter: PriorityClass TotalProcessorTime" + } + } + + Context "2 parameters on the same position" { + BeforeAll { + if ($skipTest) { return } + + Invoke-Command -Session $session -ScriptBlock { + function foo { + param( + [string] + [parameter(Position = 0, parametersetname = 'set1', mandatory = $true)] + $string, + + [ipaddress] + [parameter(Position = 0, parametersetname = 'set2', mandatory = $true)] + $ipaddress + ) + + "Bound parameter: $($myInvocation.BoundParameters.Keys | sort)" + } + } + + # Sanity checks. + Invoke-Command $session {foo ([ipaddress]::parse("127.0.0.1"))} | Should Be "Bound parameter: ipaddress" + Invoke-Command $session {foo "blah"} | Should Be "Bound parameter: string" + Invoke-Command $session {gps -pid $pid | foo -Priority normal} | Should Be "Bound parameter: PriorityClass TotalProcessorTime" + + $module = Import-PSSession $session foo -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Positional binding works" -Skip:$skipTest { + foo "blah" | Should Be "Bound parameter: string" + } + + It "Positional binding works" -Skip:$skipTest { + foo ([ipaddress]::parse("127.0.0.1")) | Should Be "Bound parameter: ipaddress" + } + } + + Context "positional binding and array argument value" { + BeforeAll { + if ($skipTest) { return } + + Invoke-Command -Session $session -ScriptBlock { + function foo { + param( + [object] + [parameter(Position = 0, mandatory = $true)] + $p1, + + [object] + [parameter(Position = 1)] + $p2 + ) + + "$p1 : $p2" + } + } + + # Sanity checks. + Invoke-Command $session {foo 1,2,3} | Should Be "1 2 3 : " + Invoke-Command $session {foo 1,2,3 4} | Should Be "1 2 3 : 4" + Invoke-Command $session {foo -p2 4 1,2,3} | Should Be "1 2 3 : 4" + Invoke-Command $session {foo 1 4} | Should Be "1 : 4" + Invoke-Command $session {foo -p2 4 1} | Should Be "1 : 4" + + $module = Import-PSSession $session foo -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Positional binding works when binding an array value" -Skip:$skipTest { + foo 1,2,3 | Should Be "1 2 3 : " + } + + It "Positional binding works when binding an array value" -Skip:$skipTest { + foo 1,2,3 4 | Should Be "1 2 3 : 4" + } + + It "Positional binding works when binding an array value" -Skip:$skipTest { + foo -p2 4 1,2,3 | Should Be "1 2 3 : 4" + } + + It "Positional binding works when binding an array value" -Skip:$skipTest { + foo 1 4 | Should Be "1 : 4" + } + + It "Positional binding works when binding an array value" -Skip:$skipTest { + foo -p2 4 1 | Should Be "1 : 4" + } + } + + Context "value from remaining arguments" { + BeforeAll { + if ($skipTest) { return } + + Invoke-Command -Session $session -ScriptBlock { + function foo { + param( + [string] + [parameter(Position = 0)] + $firstArg, + + [string[]] + [parameter(ValueFromRemainingArguments = $true)] + $remainingArgs + ) + + "$firstArg : $remainingArgs" + } + } + + # Sanity checks. + Invoke-Command $session {foo} | Should Be " : " + Invoke-Command $session {foo 1} | Should Be "1 : " + Invoke-Command $session {foo -first 1} | Should Be "1 : " + Invoke-Command $session {foo 1 2 3} | Should Be "1 : 2 3" + Invoke-Command $session {foo -first 1 2 3} | Should Be "1 : 2 3" + Invoke-Command $session {foo 2 3 -first 1 4 5} | Should Be "1 : 2 3 4 5" + Invoke-Command $session {foo -remainingArgs 2,3 1} | Should Be "1 : 2 3" + + $module = Import-PSSession $session foo -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Value from remaining arguments works" -Skip:$skipTest { + $( foo ) | Should Be " : " + } + + It "Value from remaining arguments works" -Skip:$skipTest { + $( foo 1 ) | Should Be "1 : " + } + + It "Value from remaining arguments works" -Skip:$skipTest { + $( foo -first 1 ) | Should Be "1 : " + } + + It "Value from remaining arguments works" -Skip:$skipTest { + $( foo 1 2 3 ) | Should Be "1 : 2 3" + } + + It "Value from remaining arguments works" -Skip:$skipTest { + $( foo -first 1 2 3 ) | Should Be "1 : 2 3" + } + + It "Value from remaining arguments works" -Skip:$skipTest { + $( foo 2 3 -first 1 4 5 ) | Should Be "1 : 2 3 4 5" + } + + It "Value from remaining arguments works" -Skip:$skipTest { + $( foo -remainingArgs 2,3 1 ) | Should Be "1 : 2 3" + } + } + + Context "non cmdlet-based binding" { + BeforeAll { + if ($skipTest) { return } + + Invoke-Command -Session $session -ScriptBlock { + function foo { + param( + $firstArg, + $secondArg + ) + + "$firstArg : $secondArg : $args" + } + } + + # Sanity checks. + Invoke-Command $session { foo } | Should Be " : : " + Invoke-Command $session { foo 1 } | Should Be "1 : : " + Invoke-Command $session { foo -first 1 } | Should Be "1 : : " + Invoke-Command $session { foo 1 2 } | Should Be "1 : 2 : " + Invoke-Command $session { foo 1 -second 2 } | Should Be "1 : 2 : " + Invoke-Command $session { foo -first 1 -second 2 } | Should Be "1 : 2 : " + Invoke-Command $session { foo 1 2 3 4 } | Should Be "1 : 2 : 3 4" + Invoke-Command $session { foo -first 1 2 3 4 } | Should Be "1 : 2 : 3 4" + Invoke-Command $session { foo 1 -second 2 3 4 } | Should Be "1 : 2 : 3 4" + Invoke-Command $session { foo 1 3 -second 2 4 } | Should Be "1 : 2 : 3 4" + Invoke-Command $session { foo -first 1 -second 2 3 4 } | Should Be "1 : 2 : 3 4" + + $module = Import-PSSession $session foo -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo | Should Be " : : " + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo 1 | Should Be "1 : : " + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo -first 1 | Should Be "1 : : " + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo 1 2 | Should Be "1 : 2 : " + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo 1 -second 2 | Should Be "1 : 2 : " + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo -first 1 -second 2 | Should Be "1 : 2 : " + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo 1 2 3 4 | Should Be "1 : 2 : 3 4" + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo -first 1 2 3 4 | Should Be "1 : 2 : 3 4" + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo 1 -second 2 3 4 | Should Be "1 : 2 : 3 4" + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo 1 3 -second 2 4 | Should Be "1 : 2 : 3 4" + } + + It "Non cmdlet-based binding works." -Skip:$skipTest { + foo -first 1 -second 2 3 4 | Should Be "1 : 2 : 3 4" + } + } + + Context "default parameter initialization should be executed on the server" { + BeforeAll { + if ($skipTest) { return } + + Invoke-Command -Session $session -ScriptBlock { + function MyInitializerFunction { param($x = $PID) $x } + } + + $localPid = $PID + $remotePid = Invoke-Command $session { $PID } + + # Sanity check + $localPid | Should Not Be $remotePid + + $module = Import-PSSession -Session $session -Name MyInitializerFunction -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Initializer run on the remote server" -Skip:$skipTest { + (MyInitializerFunction) | Should Be $remotePid + } + + It "Initializer not run when value provided" -Skip:$skipTest { + (MyInitializerFunction 123) | Should Be 123 + } + } + + Context "client-side parameters - cmdlet case" { + BeforeAll { + if ($skipTest) { return } + $remotePid = Invoke-Command $session { $PID } + $module = Import-PSSession -Session $session -Name Get-Variable -Type cmdlet -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Importing by name/type should work" -Skip:$skipTest { + (Get-Variable -Name PID).Value | Should Not Be $PID + } + + It "Test -AsJob parameter" -Skip:$skipTest { + try { + $job = Get-Variable -Name PID -AsJob + + $job | Should Not Be NullOrEmpty + ($job -is [System.Management.Automation.Job]) | Should Be $true + ($job.Finished.WaitOne([TimeSpan]::FromSeconds(10), $false)) | Should Be $true + $job.JobStateInfo.State | Should Be 'Completed' + + $childJob = $job.ChildJobs[0] + $childJob.Output.Count | Should Be 1 + $childJob.Output[0].Value | Should Be $remotePid + } finally { + Remove-Job $job -Force + } + } + + It "Test OutVariable" -Skip:$skipTest { + $result1 = Get-Variable -Name PID -OutVariable global:result2 + $result1.Value | Should Be $remotePid + $global:result2[0].Value | Should Be $remotePid + } + } + + Context "client-side parameters - Windows 7 bug #759434" { + BeforeAll { + if ($skipTest) { return } + $module = Import-PSSession -Session $session -Name Write-Warning -Type cmdlet -Prefix Remote -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Test warnings present with '-WarningAction Continue'" -Skip:$skipTest { + try { + $jobWithWarnings = write-remotewarning foo -WarningAction continue -Asjob + $null = Wait-Job $jobWithWarnings + + $jobWithWarnings.ChildJobs[0].Warning.Count | Should Be 1 + } finally { + Remove-Job $jobWithWarnings -Force + } + } + + It "Test no warnings with '-WarningAction SilentlyContinue'" -Skip:$skipTest { + try { + $jobWithoutWarnings = write-remotewarning foo -WarningAction silentlycontinue -Asjob + $null = Wait-Job $jobWithoutWarnings + + $jobWithoutWarnings.ChildJobs[0].Warning.Count | Should Be 0 + } finally { + Remove-Job $jobWithoutWarnings -Force + } + } + } + + Context "client-side parameters - non-cmdlet case" { + BeforeAll { + if ($skipTest) { return } + + Invoke-Command $session { function foo { param($OutVariable) "OutVariable = $OutVariable" } } + + # Sanity check + Invoke-Command $session { foo -OutVariable x } | Should Be "OutVariable = x" + + $module = Import-PSSession -Session $session -Name foo -Type function -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Implicit remoting: OutVariable is not intercepted for non-cmdlet-bound functions" -Skip:$skipTest { + foo -OutVariable x | Should Be "OutVariable = x" + } + } + + Context "switch and positional parameters" { + BeforeAll { + if ($skipTest) { return } + $remotePid = Invoke-Command $session { $PID } + $module = Import-PSSession -Session $session -Name Get-Variable -Type cmdlet -Prefix Remote -AllowClobber + } + + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Switch parameters work fine" -Skip:$skipTest { + $proxiedPid = Get-RemoteVariable -Name pid -ValueOnly + $remotePid | Should Be $proxiedPid + } + + It "Positional parameters work fine" -Skip:$skipTest { + $proxiedPid = Get-RemoteVariable pid + $remotePid | Should Be ($proxiedPid.Value) + } + } +} + +Describe "Implicit remoting on restricted ISS" -tags "Feature" { + + BeforeAll { + # Skip test for non-windows machines for now + # Skip the test on ARM + $skipTest = !$IsWindows -or $env:PROCESSOR_ARCHITECTURE -eq 'ARM' + + if ($skipTest) { return } - try - { $sessionConfigurationDll = [IO.Path]::Combine([IO.Path]::GetTempPath(), "ImplicitRemotingRestrictedConfiguration$(Get-Random).dll") - Add-Type -OutputAssembly $sessionConfigurationDll -TypeDefinition @" using System; @@ -1400,38 +1458,38 @@ Describe "Implicit remoting on restricted ISS" -tags 'Innerloop', 'P1' { namespace MySessionConfiguration { - public class MySessionConfiguration : PSSessionConfiguration - { - public override InitialSessionState GetInitialSessionState(PSSenderInfo senderInfo) - { - //System.Diagnostics.Debugger.Launch(); - //System.Diagnostics.Debugger.Break(); + public class MySessionConfiguration : PSSessionConfiguration + { + public override InitialSessionState GetInitialSessionState(PSSenderInfo senderInfo) + { + //System.Diagnostics.Debugger.Launch(); + //System.Diagnostics.Debugger.Break(); - InitialSessionState iss = InitialSessionState.CreateRestricted(System.Management.Automation.SessionCapabilities.RemoteServer); + InitialSessionState iss = InitialSessionState.CreateRestricted(System.Management.Automation.SessionCapabilities.RemoteServer); - // add Out-String for testing stuff - iss.Commands["Out-String"][0].Visibility = SessionStateEntryVisibility.Public; + // add Out-String for testing stuff + iss.Commands["Out-String"][0].Visibility = SessionStateEntryVisibility.Public; - // remove all commands that are not public - List commandsToRemove = new List(); - foreach (SessionStateCommandEntry entry in iss.Commands) - { - List sameNameEntries = new List(iss.Commands[entry.Name]); - if (!sameNameEntries.Exists(delegate(SessionStateCommandEntry e) { return e.Visibility == SessionStateEntryVisibility.Public; })) - { - commandsToRemove.Add(entry.Name); - } - } - foreach (string commandToRemove in commandsToRemove) - { - iss.Commands.Remove(commandToRemove, null /* all types */); - } + // remove all commands that are not public + List commandsToRemove = new List(); + foreach (SessionStateCommandEntry entry in iss.Commands) + { + List sameNameEntries = new List(iss.Commands[entry.Name]); + if (!sameNameEntries.Exists(delegate(SessionStateCommandEntry e) { return e.Visibility == SessionStateEntryVisibility.Public; })) + { + commandsToRemove.Add(entry.Name); + } + } - return iss; - } - } - } + foreach (string commandToRemove in commandsToRemove) + { + iss.Commands.Remove(commandToRemove, null /* all types */); + } + return iss; + } + } + } "@ Get-PSSessionConfiguration ImplicitRemotingRestrictedConfiguration* | Unregister-PSSessionConfiguration -Force @@ -1443,559 +1501,576 @@ Describe "Implicit remoting on restricted ISS" -tags 'Innerloop', 'P1' { -ConfigurationTypeName "MySessionConfiguration.MySessionConfiguration" ` -Force ` - $s = New-PSSession -Cn "localhost" -ConfigurationName $myConfiguration.Name - It "Verifies that created PSSession is not null" { - $s | Should Not Be $null - } - - ############################################################################## - # TEST: restrictions work - # - - It "Get-Variable is private" { - (@(ICm $s { Get-Command -Name Get-Variabl* }).Count) | Should Be 0 - } - It "Only 9 commands are public" { - (@(ICm $s { Get-Command }).Count) | Should Be 9 - } - - ############################################################################## - # TEST: basic functionality of Import-PSSession works (against a directly exposed cmdlet and against a proxy function) - - $m = Import-PSSession $s Out-Strin*,Measure-Object -Type Cmdlet,Function -ArgumentList 123 -AllowClobber - - It "Import-PSSession works against the ISS-restricted runspace (Out-String)" { - (@(Get-Command Out-String -Type Function).Count) | Should Be 1 - } - It "Import-PSSession works against the ISS-restricted runspace (Measure-Object)" { - (@(Get-Command Measure-Object -Type Function).Count) | Should Be 1 - } - - $remoteResult = Out-String -input ("blah " * 10) -Width 10 - $localResult = Microsoft.PowerShell.Utility\Out-String -input ("blah " * 10) -Width 10 - It "Invoking an implicit remoting proxy works against the ISS-restricted runspace (Out-String)" { - $localResult | Should Be $remoteResult - } - - $remoteResult = 1..10 | Measure-Object - $localResult = 1..10 | Microsoft.PowerShell.Utility\Measure-Object - It "Invoking an implicit remoting proxy works against the ISS-restricted runspace (Measure-Object)" { - ($localResult.Count) | Should Be ($remoteResult.Count) - } + $session = New-RemoteSession -ConfigurationName $myConfiguration.Name + $session | Should Not Be $null } - finally - { - if ($m -ne $null) { Remove-Module $m -Force -ErrorAction SilentlyContinue } - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } + + AfterAll { + if ($skipTest) { return } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } if ($myConfiguration -ne $null) { Unregister-PSSessionConfiguration -Name ($myConfiguration.Name) -Force -ErrorAction SilentlyContinue } if ($sessionConfigurationDll -ne $null) { Remove-Item $sessionConfigurationDll -Force -ErrorAction SilentlyContinue } } + + Context "restrictions works" { + It "Get-Variable is private" -Skip:$skipTest { + @(Invoke-Command $session { Get-Command -Name Get-Variabl* }).Count | Should Be 0 + } + It "Only 9 commands are public" -Skip:$skipTest { + @(Invoke-Command $session { Get-Command }).Count | Should Be 9 + } + } + + Context "basic functionality of Import-PSSession works (against a directly exposed cmdlet and against a proxy function)" { + BeforeAll { + if ($skipTest) { return } + $module = Import-PSSession $session Out-Strin*,Measure-Object -Type Cmdlet,Function -ArgumentList 123 -AllowClobber + } + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "Import-PSSession works against the ISS-restricted runspace (Out-String)" -Skip:$skipTest { + @(Get-Command Out-String -Type Function).Count | Should Be 1 + } + + It "Import-PSSession works against the ISS-restricted runspace (Measure-Object)" -Skip:$skipTest { + @(Get-Command Measure-Object -Type Function).Count | Should Be 1 + } + + It "Invoking an implicit remoting proxy works against the ISS-restricted runspace (Out-String)" -Skip:$skipTest { + $remoteResult = Out-String -input ("blah " * 10) -Width 10 + $localResult = Microsoft.PowerShell.Utility\Out-String -input ("blah " * 10) -Width 10 + + $localResult | Should Be $remoteResult + } + + It "Invoking an implicit remoting proxy works against the ISS-restricted runspace (Measure-Object)" -Skip:$skipTest { + $remoteResult = 1..10 | Measure-Object + $localResult = 1..10 | Microsoft.PowerShell.Utility\Measure-Object + ($localResult.Count) | Should Be ($remoteResult.Count) + } + } } +Describe "Implicit remoting tests" -tags "Feature" { + + BeforeAll { + # Skip test for non-windows machines for now + $skipTest = !$IsWindows -Describe "Implicit remoting tests" -tags 'Innerloop', 'P1' { + if ($skipTest) { return } - try - { - $s = New-PSSession + $session = New-RemoteSession + } - ############################################################################## - # TEST: Get-Command $m and $m.Name work (Windows 7: #334112) + AfterAll { + if ($skipTest) { return } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + } - $m = Import-PSSession $s Get-Variable -Prefix My -AllowClobber + Context "Get-Command and work (Windows 7: #334112)" { + BeforeAll { + if ($skipTest) { return } + $module = Import-PSSession $session Get-Variable -Prefix My -AllowClobber + } + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + It "PSModuleInfo.Name shouldn't contain a psd1 extension" -Skip:$skipTest { + ($module.Name -notlike '*.psd1') | Should Be $true + } + + It "PSModuleInfo.Name shouldn't contain a psm1 extension" -Skip:$skipTest { + ($module.Name -notlike '*.psm1') | Should Be $true + } + + It "PSModuleInfo.Name shouldn't contain a path" -Skip:$skipTest { + ($module.Name -notlike "${env:TMP}*") | Should Be $true + } - It "PSModuleInfo.Name shouldn't contain a psd1 extension" { - ($m.Name -notlike '*.psd1') | Should Be $true - } - It "PSModuleInfo.Name shouldn't contain a psm1 extension" { - ($m.Name -notlike '*.psm1') | Should Be $true - } - It "PSModuleInfo.Name shouldn't contain a path" { - ($m.Name -notlike "${env:TMP}*") | Should Be $true - } + It "Get-Command returns only 1 public command from implicit remoting module (1)" -Skip:$skipTest { + $c = @(Get-Command -Module $module) + $c.Count | Should Be 1 + $c[0].Name | Should Be "Get-MyVariable" + } - $c = @(Get-Command -Module $m) - It "Get-Command returns only 1 public command from implicit remoting module (1)" { - $c.Count | Should Be 1 - } - It "Get-Command returns the right public command from implicit remoting module (1)" { - $c[0].Name | Should Be "Get-MyVariable" - } + It "Get-Command returns only 1 public command from implicit remoting module (2)" -Skip:$skipTest { + $c = @(Get-Command -Module $module.Name) + $c.Count | Should Be 1 + $c[0].Name | Should Be "Get-MyVariable" + } + } - $c = @(Get-Command -Module $m.Name) - It "Get-Command returns only 1 public command from implicit remoting module (2)" { - $c.Count | Should Be 1 - } - It "Get-Command returns the right public command from implicit remoting module (2)" { - $c[0].Name | Should Be "Get-MyVariable" - } + Context "progress bar should be 1) present and 2) completed also" { + BeforeAll { + if ($skipTest) { return } - Remove-Module $m + $file = [IO.Path]::Combine([IO.Path]::GetTempPath(), [Guid]::NewGuid().ToString()) + $powerShell = [PowerShell]::Create().AddCommand("Export-PSSession").AddParameter("Session", $session).AddParameter("ModuleName", $file).AddParameter("CommandName", "Get-Process").AddParameter("AllowClobber") + $powerShell.Invoke() | Out-Null + } + AfterAll { + if ($skipTest) { return } + $powerShell.Dispose() + if ($file -ne $null) { Remove-Item $file -Recurse -Force -ErrorAction SilentlyContinue } + } - ############################################################################## - # TEST: progress bar should be 1) present and 2) completed also + It "'Completed' progress record should be present" -Skip:$skipTest { + ($powerShell.Streams.Progress | select -last 1).RecordType.ToString() | Should Be "Completed" + } + } - function Get-TempModuleFile - { - [IO.Path]::Combine([IO.Path]::GetTempPath(), [Guid]::NewGuid().ToString()) - } + Context "display of property-less objects (not sure if this test belongs here) (Windows 7: #248499)" { + BeforeAll { + if ($skipTest) { return } + $x = new-object random + $expected = $x.ToString() + } - $file = Get-TempModuleFile - - $powerShell = [PowerShell]::Create().AddCommand("Export-PSSession").AddParameter("Session", $s).AddParameter("ModuleName", $file).AddParameter("CommandName", "Get-Process").AddParameter("AllowClobber") - $powerShell.Invoke() | Out-Null - It "'Completed' progress record should be present" { - ($powerShell.Streams.Progress | select -last 1).RecordType.ToString() | Should Be "Completed" - } - $powerShell.Dispose() - - ############################################################################## - # TEST: display of property-less objects (not sure if this test belongs here) (Windows 7: #248499) - - $x = new-object random - $expected = $x.ToString() - # Since New-PSSession now only loads Microsoft.PowerShell.Core and for the session in the test, Autoloading is disabled, engine cannot find New-Object as it is part of Microsoft.PowerShell.Utility module. + # Since New-PSSession now only loads Microsoft.PowerShell.Core and for the session in the test, Autoloading is disabled, engine cannot find New-Object as it is part of Microsoft.PowerShell.Utility module. # The fix is to import this module before running the command. - It "Display of local property-less objects" { - $expected | Should Be ($($x | out-string).Trim()) - } - It "Display of remote property-less objects" { - $expected | Should Be ($(ICm $s { Import-Module Microsoft.PowerShell.Utility; new-object random } | out-string).Trim()) - } - - ############################################################################## - # TEST: piping between remoting proxies should work - - $module = Import-PSSession -Session:$s -Name:Write-Output -AllowClobber - $result = Write-Output 123 | Write-Output - It "piping between remoting proxies should work" { - $result | Should Be 123 - } - Remove-Module $module - - ############################################################################## - # TEST: BUG: Windows 7: #269467: Security: Server can inject code that will be executed on a client during implicit remoting call. - - icm $s { function attack(${foo="$(calc)"}){echo "It is done."}} - $m = Import-PSSession -Session $s -CommandName attack -EA SilentlyContinue -ErrorVariable expectedError -AllowClobber - It "Strange parameter names should trigger an error" { - $expectedError | Should Not Be NullOrEmpty - } - Remove-Module $m - - ############################################################################## - # TEST: Non-terminating error from a remote command - - icm $s { $oldGetCommand = ${function:Get-Command} } - icm $s { function get-command { write-error blah } } - - $module = Import-PSSession -Session:$s -EA SilentlyContinue -ErrorVariable expectedError -AllowClobber - It "Non-terminating error from remote end got duplicated locally" { - $expectedError | Should Not Be NullOrEmpty - } - $msg = [string]($expectedError[0]) - It "Error message got duplicated correctly" { - ($msg.Contains("blah")) | Should Be $true - } - - Remove-Module $module - icm $s { ${function:Get-Command} = $oldGetCommand } - - ############################################################################## - # TEST: Get-Command returns something that wasn't asked for - - icm $s { $oldGetCommand = ${function:Get-Command} } - icm $s { function notRequested { "notRequested" }; function get-command { Microsoft.PowerShell.Core\Get-Command Get-Variable,notRequested } } - - $module = Import-PSSession -Session:$s Get-Variable -AllowClobber -EA SilentlyContinue -ErrorVariable expectedError - It "We get an error if remote server returns something that wasn't asked for" { - $expectedError | Should Not Be NullOrEmpty - } - $msg = [string]($expectedError[0]) - It "Error message contains reference to the command that wasn't asked for" { - ($msg.Contains("notRequested")) | Should Be $true - } - - Remove-Module $module - icm $s { ${function:Get-Command} = $oldGetCommand } - - ############################################################################## - # TEST: Get-Command returns something that is not CommandInfo - - icm $s { $oldGetCommand = ${function:Get-Command} } - icm $s { function get-command { Microsoft.PowerShell.Utility\Get-Variable } } - - $expectedError = $null - try - { - $module = Import-PSSession -Session:$s -AllowClobber - } - catch - { - $expectedError = $_ - } - It "Got terminating error for malformed data" { - ($expectedError) | Should Not Be $null - } - $msg = [string]($expectedError) - It "Error message contains reference to Get-Command" { - ($msg.Contains("Get-Command")) | Should Be $true - } - - Remove-Module $module - icm $s { ${function:Get-Command} = $oldGetCommand } - - ############################################################################## - # TEST: order of remote commands (alias > function > cmdlet > external script) - - $tempdir = join-path $env:TEMP ([IO.Path]::GetRandomFileName()) - $null = mkdir $tempdir - $oldpath = $env:PATH - try - { - 'param([Parameter(Mandatory=$true)]$scriptParam) "external script / $scriptParam"' >$tempdir\myOrder.ps1 - icm $s { param($x) $env:PATH = $env:PATH + ";" + $x } -Args $tempDir - icm $s { function myOrder { param([Parameter(Mandatory=$true)]$functionParam) "function / $functionParam" } } - icm $s { function helper { param([Parameter(Mandatory=$true)]$aliasParam) "alias / $aliasParam" }; set-alias myOrder helper } - - $expectedResult = icm $s { myOrder -aliasParam 123 } - - $m = Import-PSSession $s myOrder -CommandType All -AllowClobber - $actualResult = myOrder -aliasParam 123 - It "Command resolution myOrder should be respected by implicit remoting" { - $expectedResult | Should Be $actualResult - } - Remove-Module $m - } - finally - { - $env:PATH = $oldpath - del $tempDir -Force -Recurse -EA SilentlyContinue - } - - - ############################################################################## - # TEST: -Prefix parameter - - $module = Import-PSSession -Session:$s -Name:Get-Variable -Type:cmdlet -Prefix My -AllowClobber - It "proxy should return remote pid" { - (Get-MyVariable -Name:pid).Value | Should Not Be $pid - } - Remove-Module $module - It "Prefixed commands are removed correctly" { - ((Get-Item function:Get-MyVariable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true - } - - ############################################################################## - # TEST: BadVerbs of functions should trigger a warning - - icm $s { function BadVerb-Variable { param($name) Get-Variable $name } } - - $ps = [powershell]::Create().AddCommand("Import-PSSession", $true).AddParameter("Session", $s).AddParameter("CommandName", "BadVerb-Variable") - $ps.Invoke() | out-null - - It "No errors from importing a function with a bad verb" { - $ps.Streams.Error.Count | Should Be 0 - } - It "Warnings should be emitted when importing a function with a bad verb" { - $ps.Streams.Warning.Count | Should Not Be 0 - } - - $m = Import-PSSession $s BadVerb-Variable -WarningAction SilentlyContinue -AllowClobber - - $remotePid = icm $s { $pid } - $badVerbVariablePid = (BadVerb-Variable -Name:pid).Value - $getVariablePid = icm $s { (Get-Variable -Name:pid).Value } - It "Importing function with bad verb should work" { - $badVerbVariablePid | Should Be $remotePid - } - It "Importing function with bad verb should work" { - $getVariablePid | Should Be $remotePid - } - - It "Get-Variable function should be not exported when importing a BadVerb-Variable function" { - ((Get-Item function:Get-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true - } - It "BadVerb-Variable should be a function, not an alias (1)" { - ((Get-Item function:BadVerb-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Not Be $true - } - It "BadVerb-Variable should be a function, not an alias (2)" { - ((Get-Item alias:BadVerb-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true - } - - Remove-Module $m - icm $s { del function:BadVerb-Variable } - - ############################################################################## - # TEST: BadVerbs of functions shouldn't trigger a warning when -DisableNameChecking is used - - icm $s { function BadVerb-Variable { param($name) Get-Variable $name } } - - $ps = [powershell]::Create().AddCommand("Import-PSSession", $true).AddParameter("Session", $s).AddParameter("CommandName", "BadVerb-Variable").AddParameter("DisableNameChecking", $true) - $ps.Invoke() | out-null - - It "No errors from importing a function with a bad verb + -DisableNameChecking" { - $ps.Streams.Error.Count | Should Be 0 - } - It "No warnings from importing a function with a bad verb + -DisableNameChecking" { - $ps.Streams.Warning.Count | Should Be 0 - } - - $m = Import-PSSession $s BadVerb-Variable -DisableNameChecking -AllowClobber - - $remotePid = icm $s { $pid } - $badVerbVariablePid = (BadVerb-Variable -Name:pid).Value - $getVariablePid = icm $s { (Get-Variable -Name:pid).Value } - It "Importing function with bad verb should work" { - $badVerbVariablePid | Should Be $remotePid - } - It "Importing function with bad verb should work" { - $getVariablePid | Should Be $remotePid - } - - It "Get-Variable function should be not exported when importing a BadVerb-Variable function" { - ((Get-Item function:Get-Variable -ErrorAction SilentlyContinue) -eq $Null ) | Should Be $true - } - It "BadVerb-Variable should be a function, not an alias (1)" { - ((Get-Item function:BadVerb-Variable -ErrorAction SilentlyContinue) -ne $null) | Should Be $true - } - It "BadVerb-Variable should be a function, not an alias (2)" { - ((Get-Item alias:BadVerb-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true - } - - Remove-Module $m - icm $s { del function:BadVerb-Variable } - - ############################################################################## - # TEST: BadVerbs of aliases shouldn't trigger a warning - # (+ can import an alias without saying -CommandType Alias) - - icm $s { set-alias BadVerb-Variable Get-Variable } - - $ps = [powershell]::Create().AddCommand("Import-PSSession", $true).AddParameter("Session", $s).AddParameter("CommandName", "BadVerb-Variable") - $ps.Invoke() | out-null - - It "No errors from importing an alias with a bad verb" { - $ps.Streams.Error.Count | Should Be 0 - } - It "No warnings from importing an alias with a bad verb" { - $ps.Streams.Warning.Count | Should Be 0 - } - - $m = Import-PSSession $s BadVerb-Variable -AllowClobber - - $remotePid = icm $s { $pid } - $badVerbVariablePid = (BadVerb-Variable -Name:pid).Value - $getVariablePid = icm $s { (Get-Variable -Name:pid).Value } - It "Importing alias with bad verb should work" { - $badVerbVariablePid | Should Be $remotePid - } - It "Importing alias with bad verb should work" { - $getVariablePid | Should Be $remotePid - } - - Remove-Module $m - icm $s { del alias:BadVerb-Variable } - - ############################################################################## - # TEST: removing a module should clean-up event handlers (Windows 7: #268819) - - $oldNumberOfHandlers = $executionContext.GetType().GetProperty("Events").GetValue($executionContext, $null).Subscribers.Count - $module = Import-PSSession -Session:$s -Name:Get-Random -AllowClobber - Remove-Module $module - $newNumberOfHandlers = $executionContext.GetType().GetProperty("Events").GetValue($executionContext, $null).Subscribers.Count - It "Event should be unregistered when the module is removed" { - $oldNumberOfHandlers | Should Be $newNumberOfHandlers - } - - It "Private functions from the implicit remoting module shouldn't get imported into global scope" { - 0 | Should Be @(dir function:*Implicit* -ErrorAction SilentlyContinue).Count - } + It "Display of local property-less objects" -Skip:$skipTest { + ($x | Out-String).Trim() | Should Be $expected + } + It "Display of remote property-less objects" -Skip:$skipTest { + (Invoke-Command $session { Import-Module Microsoft.PowerShell.Utility; New-Object random } | out-string).Trim() | Should Be $expected + } } - finally - { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } - if ($m -ne $null) { Remove-Module $m -Force -ErrorAction SilentlyContinue } - if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } - if ($file -ne $null) { Remove-item $file -Force -Recurse -ErrorAction SilentlyContinue } + + It "piping between remoting proxies should work" -Skip:$skipTest { + try { + $module = Import-PSSession -Session $session -Name Write-Output -AllowClobber + $result = Write-Output 123 | Write-Output + $result | Should Be 123 + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + } + + It "Strange parameter names should trigger an error" -Skip:$skipTest { + try { + Invoke-Command $session { function attack(${foo="$(calc)"}){echo "It is done."}} + $module = Import-PSSession -Session $session -CommandName attack -ErrorAction SilentlyContinue -ErrorVariable expectedError -AllowClobber + $expectedError | Should Not Be NullOrEmpty + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + } + + It "Non-terminating error from remote end got duplicated locally" -Skip:$skipTest { + try { + Invoke-Command $session { $oldGetCommand = ${function:Get-Command} } + Invoke-Command $session { function Get-Command { write-error blah } } + $module = Import-PSSession -Session $session -ErrorAction SilentlyContinue -ErrorVariable expectedError -AllowClobber + + $expectedError | Should Not Be NullOrEmpty + + $msg = [string]($expectedError[0]) + $msg.Contains("blah") | Should Be $true + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + Invoke-Command $session { ${function:Get-Command} = $oldGetCommand } + } + } + + It "Should get an error if remote server returns something that wasn't asked for" -Skip:$skipTest { + try { + Invoke-Command $session { $oldGetCommand = ${function:Get-Command} } + Invoke-Command $session { function notRequested { "notRequested" }; function Get-Command { Microsoft.PowerShell.Core\Get-Command Get-Variable,notRequested } } + $module = Import-PSSession -Session $session Get-Variable -AllowClobber -ErrorAction SilentlyContinue -ErrorVariable expectedError + + $expectedError | Should Not Be NullOrEmpty + + $msg = [string]($expectedError[0]) + $msg.Contains("notRequested") | Should Be $true + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + Invoke-Command $session { ${function:Get-Command} = $oldGetCommand } + } + } + + It "Get-Command returns something that is not CommandInfo" -Skip:$skipTest { + try { + Invoke-Command $session { $oldGetCommand = ${function:Get-Command} } + Invoke-Command $session { function Get-Command { Microsoft.PowerShell.Utility\Get-Variable } } + + $module = Import-PSSession -Session $session -AllowClobber + throw "Import-PSSession should throw" + } catch { + $msg = [string]($_) + $msg.Contains("Get-Command") | Should Be $true + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + Invoke-Command $session { ${function:Get-Command} = $oldGetCommand } + } + } + + # Test order of remote commands (alias > function > cmdlet > external script) + It "Command resolution for 'myOrder' should be respected by implicit remoting" -Skip:$skipTest { + try + { + $tempdir = Join-Path $env:TEMP ([IO.Path]::GetRandomFileName()) + $null = New-Item $tempdir -ItemType Directory -Force + $oldPath = Invoke-Command $session { $env:PATH } + + 'param([Parameter(Mandatory=$true)]$scriptParam) "external script / $scriptParam"' > $tempdir\myOrder.ps1 + Invoke-Command $session { param($x) $env:PATH = $env:PATH + [IO.Path]::PathSeparator + $x } -ArgumentList $tempDir + Invoke-Command $session { function myOrder { param([Parameter(Mandatory=$true)]$functionParam) "function / $functionParam" } } + Invoke-Command $session { function helper { param([Parameter(Mandatory=$true)]$aliasParam) "alias / $aliasParam" }; Set-Alias myOrder helper } + + $expectedResult = Invoke-Command $session { myOrder -aliasParam 123 } + + $module = Import-PSSession $session myOrder -CommandType All -AllowClobber + $actualResult = myOrder -aliasParam 123 + + $expectedResult | Should Be $actualResult + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + Invoke-Command $session { param($x) $env:PATH = $x; Remove-Item Alias:\myOrder, Function:\myOrder, Function:\helper -Force -ErrorAction SilentlyContinue } -ArgumentList $oldPath + Remove-Item $tempDir -Force -Recurse -ErrorAction SilentlyContinue + } + } + + It "Test -Prefix parameter" -Skip:$skipTest { + try { + $module = Import-PSSession -Session $session -Name Get-Variable -Type cmdlet -Prefix My -AllowClobber + (Get-MyVariable -Name pid).Value | Should Not Be $PID + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + + ((Get-Item function:Get-MyVariable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true + } + + Context "BadVerbs of functions should trigger a warning" { + BeforeAll { + if ($skipTest) { return } + Invoke-Command $session { function BadVerb-Variable { param($name) Get-Variable $name } } + } + AfterAll { + if ($skipTest) { return } + Invoke-Command $session { Remove-Item Function:\BadVerb-Variable } + } + + It "Bad verb causes no error but warning" -Skip:$skipTest { + try { + $ps = [powershell]::Create().AddCommand("Import-PSSession", $true).AddParameter("Session", $session).AddParameter("CommandName", "BadVerb-Variable") + $module = $ps.Invoke() | Select-Object -First 1 + + $ps.Streams.Error.Count | Should Be 0 + $ps.Streams.Warning.Count | Should Not Be 0 + } finally { + if ($module -ne $null) { + $ps.Commands.Clear() + $ps.AddCommand("Remove-Module").AddParameter("ModuleInfo", $module).AddParameter("Force", $true) > $null + $ps.Invoke() > $null + } + $ps.Dispose() + } + } + + It "Imported function with bad verb should work" -Skip:$skipTest { + try { + $module = Import-PSSession $session BadVerb-Variable -WarningAction SilentlyContinue -AllowClobber + + $remotePid = Invoke-Command $session { $PID } + $getVariablePid = Invoke-Command $session { (Get-Variable -Name PID).Value } + $getVariablePid | Should Be $remotePid + + ## Get-Variable function should not be exported when importing a BadVerb-Variable function + ((Get-Item Function:\Get-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true + + ## BadVerb-Variable should be a function, not an alias (1) + ((Get-Item Function:\BadVerb-Variable -ErrorAction SilentlyContinue) -ne $null) | Should Be $true + + ## BadVerb-Variable should be a function, not an alias (2) + ((Get-Item Alias:\BadVerb-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true + + (BadVerb-Variable -Name pid).Value | Should Be $remotePid + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + } + + It "Test warning is supressed by '-DisableNameChecking'" -Skip:$skipTest { + try { + $ps = [powershell]::Create().AddCommand("Import-PSSession", $true).AddParameter("Session", $session).AddParameter("CommandName", "BadVerb-Variable").AddParameter("DisableNameChecking", $true) + $module = $ps.Invoke() | Select-Object -First 1 + + $ps.Streams.Error.Count | Should Be 0 + $ps.Streams.Warning.Count | Should Be 0 + } finally { + if ($module -ne $null) { + $ps.Commands.Clear() + $ps.AddCommand("Remove-Module").AddParameter("ModuleInfo", $module).AddParameter("Force", $true) > $null + $ps.Invoke() > $null + } + $ps.Dispose() + } + } + + It "Imported function with bad verb by 'Import-PSSession -DisableNameChecking' should work" -Skip:$skipTest { + try { + $module = Import-PSSession $session BadVerb-Variable -DisableNameChecking -AllowClobber + + $remotePid = Invoke-Command $session { $PID } + $getVariablePid = Invoke-Command $session { (Get-Variable -Name PID).Value } + $getVariablePid | Should Be $remotePid + + ## Get-Variable function should not be exported when importing a BadVerb-Variable function + ((Get-Item Function:\Get-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true + + ## BadVerb-Variable should be a function, not an alias (1) + ((Get-Item Function:\BadVerb-Variable -ErrorAction SilentlyContinue) -ne $null) | Should Be $true + + ## BadVerb-Variable should be a function, not an alias (2) + ((Get-Item Alias:\BadVerb-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true + + (BadVerb-Variable -Name pid).Value | Should Be $remotePid + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + } + } + + Context "BadVerbs of alias shouldn't trigger a warning + can import an alias without saying -CommandType Alias" { + BeforeAll { + if ($skipTest) { return } + Invoke-Command $session { Set-Alias BadVerb-Variable Get-Variable } + } + AfterAll { + if ($skipTest) { return } + Invoke-Command $session { Remove-Item Alias:\BadVerb-Variable } + } + + It "Bad verb alias causes no error or warning" -Skip:$skipTest { + try { + $ps = [powershell]::Create().AddCommand("Import-PSSession", $true).AddParameter("Session", $session).AddParameter("CommandName", "BadVerb-Variable") + $module = $ps.Invoke() | Select-Object -First 1 + + $ps.Streams.Error.Count | Should Be 0 + $ps.Streams.Warning.Count | Should Be 0 + } finally { + if ($module -ne $null) { + $ps.Commands.Clear() + $ps.AddCommand("Remove-Module").AddParameter("ModuleInfo", $module).AddParameter("Force", $true) > $null + $ps.Invoke() > $null + } + $ps.Dispose() + } + } + + It "Importing alias with bad verb should work" -Skip:$skipTest { + try { + $module = Import-PSSession $session BadVerb-Variable -AllowClobber + + $remotePid = Invoke-Command $session { $PID } + $getVariablePid = Invoke-Command $session { (Get-Variable -Name PID).Value } + $getVariablePid | Should Be $remotePid + + ## BadVerb-Variable should be an alias, not a function (1) + ((Get-Item Function:\BadVerb-Variable -ErrorAction SilentlyContinue) -eq $null) | Should Be $true + + ## BadVerb-Variable should be an alias, not a function (2) + ((Get-Item Alias:\BadVerb-Variable -ErrorAction SilentlyContinue) -ne $null) | Should Be $true + + (BadVerb-Variable -Name pid).Value | Should Be $remotePid + } finally { + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + } + } + } + + It "Removing a module should clean-up event handlers (Windows 7: #268819)" -Skip:$skipTest { + $oldNumberOfHandlers = $executionContext.GetType().GetProperty("Events").GetValue($executionContext, $null).Subscribers.Count + $module = Import-PSSession -Session $session -Name Get-Random -AllowClobber + + Remove-Module $module -Force + $newNumberOfHandlers = $executionContext.GetType().GetProperty("Events").GetValue($executionContext, $null).Subscribers.Count + + ## Event should be unregistered when the module is removed + $oldNumberOfHandlers | Should Be $newNumberOfHandlers + + ## Private functions from the implicit remoting module shouldn't get imported into global scope + @(dir function:*Implicit* -ErrorAction SilentlyContinue).Count | Should Be 0 } } +Describe "Export-PSSession function" -tags "Feature" { + BeforeAll { + # Skip test for non-windows machines for now + $skipTest = !$IsWindows -Describe "Export-PSSession function" -tags 'Innerloop', 'P1' { + if ($skipTest) { return } - try - { - ############################################################################## - # TEST SETUP - CREATE TEMP DIRECTORY - # - - $modulesDir = $env:TEMP -split ';' | select -first 1 - - $tempdir = join-path $modulesDir ([IO.Path]::GetRandomFileName()) - mkdir $tempdir | Out-Null - - $fileName = [io.path]::GetFileName($tempdir) - - ############################################################################## - # TEST: basic functionality of Export-PSSession - - $s = New-PSSession - Invoke-Command -Session $s {Import-Module PSDiagnostics} - Export-PSSession -Session $s -OutputModule $tempdir\Diag -CommandName Start-Trace -AllowClobber | Out-Null + $session = New-RemoteSession + + $tempdir = Join-Path $env:TEMP ([IO.Path]::GetRandomFileName()) + New-Item $tempdir -ItemType Directory > $null @" Import-Module `"$tempdir\Diag`" `$mod = Get-Module Diag Return `$mod "@ > $tempdir\TestBug450687.ps1 - - # Only the snapin Microsoft.PowerShell.Core is loaded - $iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault2() - $ps = [PowerShell]::Create($iss) - $result = $ps.AddScript(" & $tempdir\TestBug450687.ps1").Invoke() - - It "The module created by Export-PSSession is imported successfully" { - ($result -ne $null -and $result.Count -eq 1 -and $result[0].Name -eq "Diag") | Should Be $true - } - $c = $result[0].ExportedCommands["Start-Trace"] - It "The command Add-BitsFile is imported successfully" { - ($c -ne $null -and $c.CommandType -eq "Function") | Should Be $true - } } - finally - { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } - if ($tempdir -ne $null) { Remove-Item $tempDir -Force -Recurse -ErrorAction SilentlyContinue } + + AfterAll { + if ($skipTest) { return } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + if ($tempdir -ne $null) { Remove-Item $tempdir -Force -Recurse -ErrorAction SilentlyContinue } + } + + It "Test the module created by Export-PSSession" -Skip:$skipTest { + try { + Export-PSSession -Session $session -OutputModule $tempdir\Diag -CommandName New-Guid -AllowClobber > $null + + # Only the snapin Microsoft.PowerShell.Core is loaded + $iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault2() + $ps = [PowerShell]::Create($iss) + $result = $ps.AddScript(" & $tempdir\TestBug450687.ps1").Invoke() + + ## The module created by Export-PSSession is imported successfully + ($result -ne $null -and $result.Count -eq 1 -and $result[0].Name -eq "Diag") | Should Be $true + + ## The command Add-BitsFile is imported successfully + $c = $result[0].ExportedCommands["New-Guid"] + ($c -ne $null -and $c.CommandType -eq "Function") | Should Be $true + } finally { + $ps.Dispose() + } } } +Describe "Implicit remoting with disconnected session" -tags "Feature" { + BeforeAll { + # Skip test for non-windows machines for now + $skipTest = !$IsWindows -Describe "Implicit remoting with disconnected session" -tags 'Innerloop', 'P1' { + if ($skipTest) { return } - try - { - ## Create session for import. - $s = nsn -Name Session102 - $m = Import-PSSession $s Get-Variable -prefix remote -AllowClobber + $session = New-RemoteSession -Name Session102 + $remotePid = Invoke-Command $session { $PID } + $module = Import-PSSession $session Get-Variable -prefix Remote -AllowClobber + } - ## Check local and remote versions of process Id variable. - $thisPid = Get-Variable pid + AfterAll { + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + } + + It "Remote session PID should be different" -Skip:$skipTest { $sessionPid = Get-RemoteVariable pid - It "This and remote session process ids should be different." { - $thisPid.Value | Should Not Be $sessionPid.Value - } + $sessionPid.Value | Should Be $remotePid + } - ## Disconnect session and use imported command. - Disconnect-PSSession $s + It "Disconnected session should be reconnected when calling proxied command" -Skip:$skipTest { + Disconnect-PSSession $session + $dSessionPid = Get-RemoteVariable pid - It "Session process id should be same as before, with connected session." { - $dSessionPid.Value | Should Be $sessionPid.Value - } - It "Session should be reconnected." { - $s.State | Should Be 'Opened' - } + $dSessionPid.Value | Should Be $remotePid + + $session.State | Should Be 'Opened' + } + ## It requires 'New-PSSession' to work with implicit credential to allow proxied command to create new session. + ## Implicit credential doesn't work in AppVeyor builder, so mark this test '-pending'. + It "Should have a new session when the disconnected session cannot be re-connected" -Pending { ## Disconnect session and make it un-connectable. - Disconnect-PSSession $s + Disconnect-PSSession $session start powershell -arg 'Get-PSSession -cn localhost -name Session102 | Connect-PSSession' -Wait sleep 3 ## This time a new session is created because the old one is unavailable. $dSessionPid = Get-RemoteVariable pid - It "Should have a new session process id because old session is unavailable." { - $dSessionPid.Value | Should Not Be $sessionPid.Value - } - } - finally - { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } - if ($m -ne $null) { Remove-Module $m -Force -ErrorAction SilentlyContinue } + $dSessionPid.Value | Should Not Be $remotePid } } - -Describe "Select-Object with implicit remoting" -tags 'Innerloop', 'P1' { - - try - { - - $session = New-PSSession localhost - icm $session { function foo { "a","b","c" } } - $module = Import-PSSession $session foo -AllowClobber - $bar = foo | select -First 2; "here" - - It "Select -First failed with implicit remoting" { - $bar | Should Not Be NullOrEmpty - } - It "Select -First failed with implicit remoting" { - $bar.Count | Should Be 2 - } - It "Select -First failed with implicit remoting" { - $bar[0] | Should Be "a" - } - It "Select -First failed with implicit remoting" { - $bar[1] | Should Be "b" - } - } - finally - { - if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } - if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } - } -} - -Describe "Get-FormatData used in Export-PSSession should work on DL targets" -Tags 'Innerloop','P1' { - - # Only run these tests if .NET 2.0 and PS 2.0 is installed on the machine - if (! (test-path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727') -or - ! (test-path 'HKLM:\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine') - ) - { - It -SKip "PS 2.0 not installed. Skipping test." - return - } - - $configName = "DLConfigTest" - - try - { - $null = Register-PSSessionConfiguration -Name $configName -PSVersion 2.0 -Force - $s = New-PSSession -ComputerName . -ConfigurationName $configName - - $results = Export-PSSession -Session $s -OutputModule tempTest -CommandName Get-Process ` - -AllowClobber -FormatTypeName * -Force -ErrorAction Stop - - It "Verifies that Export-PSSession with PS 2.0 session and format type names succeeds" { - $results.Count | Should Not Be 0 - } - } - finally - { - Unregister-PSSessionConfiguration -Name $configName -Force -ErrorAction SilentlyContinue - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } - if ($results.Count -gt 0) - { - Remove-Item -Path $results[0].DirectoryName -Recurse -Force -ErrorAction SilentlyContinue - } - } -} - -Describe "GetCommand locally and remotely" -Tags 'Innerloop','P1' { - +Describe "Select-Object with implicit remoting" -tags "Feature" { BeforeAll { - $s = New-PSSession -cn localhost + # Skip test for non-windows machines for now + $skipTest = !$IsWindows + + if ($skipTest) { return } + + $session = New-RemoteSession + Invoke-Command $session { function foo { "a","b","c" } } + $module = Import-PSSession $session foo -AllowClobber } AfterAll { - if ($s -ne $null) { Remove-PSSession $s -ErrorAction SilentlyContinue } + if ($skipTest) { return } + if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } } - $localCommandCount = (Get-Command -Type Cmdlet).Count - $remoteCommandCount = Invoke-Command { (Get-Command -Type Cmdlet).Count } + It "Select -First should work with implicit remoting" -Skip:$skipTest { + $bar = foo | select -First 2 + $bar | Should Not Be NullOrEmpty + $bar.Count | Should Be 2 + $bar[0] | Should Be "a" + $bar[1] | Should Be "b" + } +} - It "Verifies that the number of local cmdlet command count is the same as remote cmdlet command count." { +Describe "Get-FormatData used in Export-PSSession should work on DL targets" -tags "Feature" { + BeforeAll { + # Skip tests for non-windows machines for now + # Skip tests if .NET 2.0 and PS 2.0 is installed on the machine + $skipTest = !$IsWindows -or (! (Test-Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727')) -or + (! (Test-Path 'HKLM:\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine')) + + if ($skipTest) { return } + + $configName = "DLConfigTest" + $null = Register-PSSessionConfiguration -Name $configName -PSVersion 2.0 -Force + $session = New-RemoteSession -ConfigurationName $configName + } + + AfterAll { + if ($skipTest) { return } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + Unregister-PSSessionConfiguration -Name $configName -Force -ErrorAction SilentlyContinue + } + + It "Verifies that Export-PSSession with PS 2.0 session and format type names succeeds" -Skip:$skipTest { + try { + $results = Export-PSSession -Session $session -OutputModule tempTest -CommandName Get-Process ` + -AllowClobber -FormatTypeName * -Force -ErrorAction Stop + $results.Count | Should Not Be 0 + } finally { + if ($results.Count -gt 0) { + Remove-Item -Path $results[0].DirectoryName -Recurse -Force -ErrorAction SilentlyContinue + } + } + } +} + +Describe "GetCommand locally and remotely" -tags "Feature" { + + BeforeAll { + # Run this test only on FullCLR powershell + $skipTest = !$IsWindows -or $IsCoreCLR + + if ($skipTest) { return } + $session = New-RemoteSession + } + + AfterAll { + if ($skipTest) { return } + if ($session -ne $null) { Remove-PSSession $session -ErrorAction SilentlyContinue } + } + + It "Verifies that the number of local cmdlet command count is the same as remote cmdlet command count." -Skip:$skipTest { + $localCommandCount = (Get-Command -Type Cmdlet).Count + $remoteCommandCount = Invoke-Command { (Get-Command -Type Cmdlet).Count } $localCommandCount | Should Be $remoteCommandCount } }