/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1
2 085 строк
87 KB
xtqqczze
Use `[initialsessionstate]` type accelerator (#25912)
24 окт 2025, 07:54
Не верифицирован
24 окт 2025, 07:54
82d5913
Код
Авторство
О чём код?
# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # Skip all tests on non-windows and non-PowerShellCore and non-elevated platforms. $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() $originalWarningPreference = $WarningPreference $WarningPreference = "SilentlyContinue" $skipTest = ! ($IsWindows -and $IsCoreCLR -and (Test-IsElevated)) -or (Test-IsWinWow64) $PSDefaultParameterValues["it:skip"] = $skipTest try { Describe "Implicit remoting and CIM cmdlets with AllSigned and Restricted policy" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } # # GET CERTIFICATE # $tempName = "TESTDRIVE:\signedscript_$(Get-Random).ps1" "123456" > $tempName $cert = $null foreach ($thisCertificate in (Get-ChildItem cert:\ -rec -codesigning)) { $null = Set-AuthenticodeSignature $tempName -Certificate $thisCertificate if ((Get-AuthenticodeSignature $tempName).Status -eq "Valid") { $cert = $thisCertificate break } } # Skip the tests if we couldn't find a code sign certificate # This will happen in NanoServer and IoT if ($null -eq $cert) { $skipThisTest = $true return } $skipThisTest = $false # Ensure the cert is trusted if (-not (Test-Path "cert:\currentuser\TrustedPublisher\$($cert.Thumbprint)")) { $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "TrustedPublisher" $store.Open("ReadWrite") $store.Add($cert) $store.Close() } # # Create a remote session # $session = New-RemoteSession # # Set process scope execution policy to 'AllSigned' # $oldExecutionPolicy = Get-ExecutionPolicy -Scope Process Set-ExecutionPolicy AllSigned -Scope Process } AfterAll { if ($skipTest) { return } if ($null -ne $tempName) { Remove-Item -Path $tempName -Force -ErrorAction SilentlyContinue } if ($null -ne $oldExecutionPolicy) { Set-ExecutionPolicy $oldExecutionPolicy -Scope Process } if ($null -ne $session) { 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 -or $skipThisTest) { try { $importedModule = Import-PSSession $session Get-Variable -Prefix Remote -Certificate $cert -AllowClobber $importedModule | Should -Not -BeNullOrEmpty } finally { $importedModule | Remove-Module -Force -ErrorAction SilentlyContinue } } It "Verifies security error when Certificate parameter is not used" -Skip:($skipTest -or $skipThisTest) { { $importedModule = Import-PSSession $session Get-Variable -Prefix Remote -AllowClobber } | Should -Throw -ErrorId "InvalidOperation,Microsoft.PowerShell.Commands.ImportPSSessionCommand" } } Describe "Tests Import-PSSession cmdlet works with types unavailable on the client" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $typeDefinition = @" namespace MyTest { public enum MyEnum { Value1 = 1, Value2 = 2 } } "@ # # Create a remote session # $session = New-RemoteSession 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 } } } AfterAll { if ($skipTest) { return } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } It "Verifies client-side unavailable enum is correctly handled" { 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 -BeExactly "Value2" } finally { if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } } } Describe "Cmdlet help from remote session" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $session = New-RemoteSession } AfterAll { if ($skipTest) { return } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } It "Verifies that get-help name for remote proxied commands matches the get-command name" { 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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } } } Describe "Import-PSSession Cmdlet error handling" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $session = New-RemoteSession } AfterAll { if ($skipTest) { return } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } It "Verifies that broken alias results in one error" { try { Invoke-Command $session { Set-Alias BrokenAlias NonExistantCommand } $module = Import-PSSession $session -CommandName:BrokenAlias -CommandType:All -ErrorAction SilentlyContinue -ErrorVariable expectedError -AllowClobber $expectedError | Should -Not -BeNullOrEmpty $expectedError[0].ToString().Contains("BrokenAlias") | Should -BeTrue } finally { if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } Invoke-Command $session { Remove-Item alias:BrokenAlias } } } 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 $oldErrorView = $ErrorView $ErrorView = "NormalView" } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } $ErrorView = $oldErrorView } It "Test non-terminating error" { $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 -BeTrue } It "Test terminating error" { $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 -BeTrue } } 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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } 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 } It "Verifies proxied order = icm order (for mixed error and output results)" { $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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Verifies WarningVariable" { $global:myWarningVariable = @() Write-RemoteWarning MyWarning -WarningVariable global:myWarningVariable ([string]($myWarningVariable[0])) | Should -Be 'MyWarning' } } } Describe "Tests Export-PSSession" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $sessionOption = New-PSSessionOption -ApplicationArguments @{myTest="MyValue"} $session = New-RemoteSession -SessionOption $sessionOption $file = [IO.Path]::Combine([IO.Path]::GetTempPath(), [Guid]::NewGuid().ToString()) $results = Export-PSSession -Session $session -CommandName Get-Variable -AllowClobber -ModuleName $file $oldTimestamp = $($results | Select-Object -First 1).LastWriteTime } AfterAll { if ($skipTest) { return } if ($null -ne $file) { Remove-Item $file -Force -Recurse -ErrorAction SilentlyContinue } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } It "Verifies Export-PSSession creates a file/directory" { @(Get-Item $file).Count | Should -Be 1 } It "Verifies Export-PSSession creates a psd1 file" { ($results | Where-Object { $_.Name -like "*$(Split-Path -Leaf $file).psd1" }) | Should -BeTrue } It "Verifies Export-PSSession creates a psm1 file" { ($results | Where-Object { $_.Name -like "*.psm1" }) | Should -BeTrue } It "Verifies Export-PSSession creates a ps1xml file" { ($results | Where-Object { $_.Name -like "*.ps1xml" }) | Should -BeTrue } It "Verifies that Export-PSSession fails when a module directory already exists" { $e = { Export-PSSession -Session $session -CommandName Get-Variable -AllowClobber -ModuleName $file -ErrorAction Stop } | Should -Throw -PassThru $e | Should -Not -BeNullOrEmpty # Error contains reference to the directory that already exists ([string]($e[0]) -like "*$file*") | Should -BeTrue } It "Verifies that overwriting an existing directory succeeds with -Force" { $newResults = Export-PSSession -Session $session -CommandName Get-Variable -AllowClobber -ModuleName $file -Force # Verifies that Export-PSSession returns 4 files @($newResults).Count | Should -Be 4 # Verifies that Export-PSSession creates *new* files $newResults | ForEach-Object { $_.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 } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Verifies that proxy returns remote pid" { (Get-Variable -Name pid).Value | Should -Not -Be $PID } It "Verfies Remove-Module doesn't remove user's runspace" { Remove-Module $module -Force -ErrorAction SilentlyContinue (Get-PSSession -InstanceId $session.InstanceId) | Should -Not -BeNullOrEmpty } } } Describe "Proxy module is usable when the original runspace is no longer around" -tags "Feature","RequireAdminOnWindows" { BeforeAll { 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 ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue; $session = $null } } AfterAll { if ($skipTest) { return } if ($null -ne $file) { Remove-Item $file -Force -Recurse -ErrorAction SilentlyContinue } if ($null -ne $session) { 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 the Azure DevOps 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 ($null -ne $module) { 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 -BeExactly "MyValue" } It "Verifies Remove-Module removed the runspace that was automatically created" -Pending { Remove-Module $module -Force (Get-PSSession -InstanceId $internalSession.InstanceId -ErrorAction SilentlyContinue) | Should -BeNullOrEmpty } 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 -BeExactly "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 ($null -ne $module) { 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" -Pending { (Get-Variable -Name PSCulture).Value | Should -BeExactly "fr-FR" } It "Verifies proxy returns modified culture" -Pending { (Get-Variable -Name PSUICulture).Value | Should -BeExactly "de-DE" } # removing the module should remove the implicitly/magically created runspace It "Verifies Remove-Module removes automatically created runspace" -Pending { Remove-Module $module -Force (Get-PSSession -InstanceId $internalSession.InstanceId -ErrorAction SilentlyContinue) | Should -BeNullOrEmpty } 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 -BeExactly "Closed" } } 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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } if ($null -ne $newSession) { Remove-PSSession $newSession -ErrorAction SilentlyContinue } } 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" { ($internalSession.Runspace.RunspaceStateInfo.ToString()) | Should -BeExactly "Opened" } It "Verifies Runspace stays opened after removing module from Export-PSSession that got initialized with an external runspace" { Remove-Module $module -Force ($internalSession.Runspace.RunspaceStateInfo.ToString()) | Should -BeExactly "Opened" } } } Describe "Import-PSSession with FormatAndTypes" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } # remote into same powershell instance $samesession = New-RemoteSession -ConfigurationName $endpointName $session = New-RemoteSession function CreateTempPs1xmlFile { do { $tmpFile = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName()) + ".ps1xml"; } while ([IO.File]::Exists($tmpFile)) $tmpFile } function CreateTypeFile { $tmpFile = CreateTempPs1xmlFile @" <Types> <Type> <Name>System.Management.Automation.Host.Coordinates</Name> <Members> <NoteProperty> <Name>MyTestLabel</Name> <Value>123</Value> </NoteProperty> </Members> </Type> <Type> <Name>MyTest.Root</Name> <Members> <MemberSet> <Name>PSStandardMembers</Name> <Members> <NoteProperty> <Name>SerializationDepth</Name> <Value>1</Value> </NoteProperty> </Members> </MemberSet> </Members> </Type> <Type> <Name>MyTest.Son</Name> <Members> <MemberSet> <Name>PSStandardMembers</Name> <Members> <NoteProperty> <Name>SerializationDepth</Name> <Value>1</Value> </NoteProperty> </Members> </MemberSet> </Members> </Type> <Type> <Name>MyTest.Grandson</Name> <Members> <MemberSet> <Name>PSStandardMembers</Name> <Members> <NoteProperty> <Name>SerializationDepth</Name> <Value>1</Value> </NoteProperty> </Members> </MemberSet> </Members> </Type> </Types> "@ | Set-Content $tmpFile $tmpFile } function CreateFormatFile { $tmpFile = CreateTempPs1xmlFile @" <Configuration> <ViewDefinitions> <View> <Name>MySizeView</Name> <ViewSelectedBy> <TypeName>System.Management.Automation.Host.Size</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Label>MyTestWidth</Label> </TableColumnHeader> <TableColumnHeader> <Label>MyTestHeight</Label> </TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>Width</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>Height</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> </ViewDefinitions> </Configuration> "@ | Set-Content $tmpFile $tmpFile } $formatFile = CreateFormatFile $typeFile = CreateTypeFile } AfterAll { if ($skipTest) { return } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } if ($null -ne $samesession) { Remove-PSSession $samesession -ErrorAction SilentlyContinue } if ($null -ne $formatFile) { Remove-Item $formatFile -Force -ErrorAction SilentlyContinue } if ($null -ne $typeFile) { Remove-Item $typeFile -Force -ErrorAction SilentlyContinue } } Context "Importing format file works" { BeforeAll { if ($skipTest) { return } $formattingScript = { New-Object System.Management.Automation.Host.Size | ForEach-Object { $_.Width = 123; $_.Height = 456; $_ } | Out-String } $originalLocalFormatting = & $formattingScript # Original local and remote formatting should be equal (sanity check) $originalRemoteFormatting = Invoke-Command $samesession $formattingScript $originalLocalFormatting | Should -Be $originalRemoteFormatting Invoke-Command $samesession { param($file) Update-FormatData $file } -ArgumentList $formatFile # Original remote and modified remote formatting should not be equal (sanity check) $modifiedRemoteFormatting = Invoke-Command $samesession $formattingScript $originalRemoteFormatting | Should -Not -Be $modifiedRemoteFormatting $module = Import-PSSession -Session $samesession -CommandName @() -FormatTypeName * -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "modified remote and imported local should be equal" { $importedLocalFormatting = & $formattingScript $modifiedRemoteFormatting | Should -Be $importedLocalFormatting } It "original local and unimported local should be equal" { Remove-Module $module -Force $unimportedLocalFormatting = & $formattingScript $originalLocalFormatting | Should -Be $unimportedLocalFormatting } } It "Updating type table in a middle of a command has effect on serializer" { $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 | Should -BeNullOrEmpty # 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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Serialization works for top-level properties" { $x = foo $x.text | Should -BeExactly "root" } It "Serialization settings works for deep properties" { $x = foo $x.Son.Grandson.text | Should -BeExactly "Grandson" } It "Serialization settings are preserved even if types.ps1xml is missing on the client" { $y = foo | bar $y | Should -BeExactly "Grandson" } } } Describe "Import-PSSession functional tests" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $session = New-RemoteSession # Define a remote function Invoke-Command -Session $session { function MyFunction { param($x) "x = '$x'; args = '$args'" } } # 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 $session { param($x) Invoke-Expression $x } -Arg $remoteProxyDeclaration $remoteAliasDeclaration = "set-alias gvalias Get-Variable" 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 $session -Name MyFunction,Get-VariableProxy,Get-Variable,gvalias,cmd -AllowClobber -Type All } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } It "Import-PSSession should return a PSModuleInfo object" { $module | Should -Not -BeNullOrEmpty } It "Import-PSSession should return a PSModuleInfo object" { ($module -as [System.Management.Automation.PSModuleInfo]) | Should -Not -BeNullOrEmpty } It "Helper functions should not be imported" { Get-Item function:*PSImplicitRemoting* -ErrorAction SilentlyContinue | Where-Object {$_.ModuleName -eq $module.Name} | Should -BeNullOrEmpty } It "Calls implicit remoting proxies 'MyFunction'" { (MyFunction 1 2 3) | Should -BeExactly "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 } It "NoName-c8aeb5c8-2388-4d64-98c1-a9c6c218d404" { Invoke-Command -Session $session { $env:TestImplicitRemotingVariable = 123 } (cmd.exe /c "echo TestImplicitRemotingVariable=%TestImplicitRemotingVariable%") | Should -BeExactly "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 | Where-Object { $_.Path -eq $module.Path })) ) { $i++ Start-Sleep -Milliseconds 50 } } It "Temporary module should be automatically removed after runspace is closed" { (Get-Module | Where-Object { $_.Path -eq $module.Path }) | Should -BeNullOrEmpty } It "Temporary psm1 file should be automatically removed after runspace is closed" { (Get-Item $module.Path -ErrorAction SilentlyContinue) | Should -BeNullOrEmpty } It "Event should be unregistered when the runspace is closed" { # 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" { @(Get-ChildItem function:*Implicit* -ErrorAction SilentlyContinue | Where-Object {$_.ModuleName -eq $module.Name}).Count | Should -Be 0 } } } Describe "Implicit remoting parameter binding" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $session = New-RemoteSession } AfterAll { if ($skipTest) { return } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } It "Binding of ValueFromPipeline should work" { 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-Object)" } } # Sanity checks. Invoke-Command $session {"s" | foo} | Should -BeExactly "Bound parameter: string" Invoke-Command $session {[ipaddress]::parse("127.0.0.1") | foo} | Should -BeExactly "Bound parameter: ipaddress" $module = Import-PSSession $session foo -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Pipeline binding works even if it relies on type constraints" { ("s" | foo) | Should -BeExactly "Bound parameter: string" } It "Pipeline binding works even if it relies on type constraints" { ([ipaddress]::parse("127.0.0.1") | foo) | Should -BeExactly "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 -BeExactly "Bound parameter: string" Invoke-Command $session {[ipaddress]::parse("127.0.0.1") | foo} | Should -BeExactly "Bound parameter: ipaddress" $module = Import-PSSession $session foo -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Pipeline binding works even if it relies on type constraints and parameter set is ambiguous" { ("s" | foo) | Should -BeExactly "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 -BeExactly "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-Object)" } } # Sanity checks. Invoke-Command $session {Get-Date | foo} | Should -BeExactly "Bound parameter: date" Invoke-Command $session {[ipaddress]::parse("127.0.0.1") | foo} | Should -BeExactly "Bound parameter: ipaddress" Invoke-Command $session {[ipaddress]::parse("127.0.0.1") | foo -date (Get-Date)} | Should -BeExactly "Bound parameter: date ipaddress" Invoke-Command $session {Get-Date | foo -ipaddress ([ipaddress]::parse("127.0.0.1"))} | Should -BeExactly "Bound parameter: date ipaddress" $module = Import-PSSession $session foo -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Pipeline binding works even when also binding by name" { (Get-Date | foo) | Should -BeExactly "Bound parameter: date" } It "Pipeline binding works even when also binding by name" { ([ipaddress]::parse("127.0.0.1") | foo) | Should -BeExactly "Bound parameter: ipaddress" } It "Pipeline binding works even when also binding by name" { ([ipaddress]::parse("127.0.0.1") | foo -date $(Get-Date)) | Should -BeExactly "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 -BeExactly "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-Object)" } } # Sanity checks. Invoke-Command $session {Get-Process -pid $PID | foo} | Should -BeExactly "Bound parameter: PriorityClass TotalProcessorTime" Invoke-Command $session {Get-Process -pid $PID | foo -Total 5} | Should -BeExactly "Bound parameter: PriorityClass TotalProcessorTime" Invoke-Command $session {Get-Process -pid $PID | foo -Priority normal} | Should -BeExactly "Bound parameter: PriorityClass TotalProcessorTime" $module = Import-PSSession $session foo -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Pipeline binding works by property name" { (Get-Process -Id $PID | foo) | Should -BeExactly "Bound parameter: PriorityClass TotalProcessorTime" } It "Pipeline binding works by property name" { (Get-Process -Id $PID | foo -Total 5) | Should -BeExactly "Bound parameter: PriorityClass TotalProcessorTime" } It "Pipeline binding works by property name" { (Get-Process -Id $PID | foo -Priority normal) | Should -BeExactly "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-Object)" } } # Sanity checks. Invoke-Command $session {foo ([ipaddress]::parse("127.0.0.1"))} | Should -BeExactly "Bound parameter: ipaddress" Invoke-Command $session {foo "blah"} | Should -BeExactly "Bound parameter: string" $module = Import-PSSession $session foo -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Positional binding works" { foo "blah" | Should -BeExactly "Bound parameter: string" } It "Positional binding works" { foo ([ipaddress]::parse("127.0.0.1")) | Should -BeExactly "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 -BeExactly "1 2 3 : " Invoke-Command $session {foo 1,2,3 4} | Should -BeExactly "1 2 3 : 4" Invoke-Command $session {foo -p2 4 1,2,3} | Should -BeExactly "1 2 3 : 4" Invoke-Command $session {foo 1 4} | Should -BeExactly "1 : 4" Invoke-Command $session {foo -p2 4 1} | Should -BeExactly "1 : 4" $module = Import-PSSession $session foo -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Positional binding works when binding an array value" { foo 1,2,3 | Should -BeExactly "1 2 3 : " } It "Positional binding works when binding an array value" { foo 1,2,3 4 | Should -BeExactly "1 2 3 : 4" } It "Positional binding works when binding an array value" { foo -p2 4 1,2,3 | Should -BeExactly "1 2 3 : 4" } It "Positional binding works when binding an array value" { foo 1 4 | Should -BeExactly "1 : 4" } It "Positional binding works when binding an array value" { foo -p2 4 1 | Should -BeExactly "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 -BeExactly " : " Invoke-Command $session {foo 1} | Should -BeExactly "1 : " Invoke-Command $session {foo -first 1} | Should -BeExactly "1 : " Invoke-Command $session {foo 1 2 3} | Should -BeExactly "1 : 2 3" Invoke-Command $session {foo -first 1 2 3} | Should -BeExactly "1 : 2 3" Invoke-Command $session {foo 2 3 -first 1 4 5} | Should -BeExactly "1 : 2 3 4 5" Invoke-Command $session {foo -remainingArgs 2,3 1} | Should -BeExactly "1 : 2 3" $module = Import-PSSession $session foo -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Value from remaining arguments works" { $( foo ) | Should -BeExactly " : " } It "Value from remaining arguments works" { $( foo 1 ) | Should -BeExactly "1 : " } It "Value from remaining arguments works" { $( foo -first 1 ) | Should -BeExactly "1 : " } It "Value from remaining arguments works" { $( foo 1 2 3 ) | Should -BeExactly "1 : 2 3" } It "Value from remaining arguments works" { $( foo -first 1 2 3 ) | Should -BeExactly "1 : 2 3" } It "Value from remaining arguments works" { $( foo 2 3 -first 1 4 5 ) | Should -BeExactly "1 : 2 3 4 5" } It "Value from remaining arguments works" { $( foo -remainingArgs 2,3 1 ) | Should -BeExactly "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 -BeExactly " : : " Invoke-Command $session { foo 1 } | Should -BeExactly "1 : : " Invoke-Command $session { foo -first 1 } | Should -BeExactly "1 : : " Invoke-Command $session { foo 1 2 } | Should -BeExactly "1 : 2 : " Invoke-Command $session { foo 1 -second 2 } | Should -BeExactly "1 : 2 : " Invoke-Command $session { foo -first 1 -second 2 } | Should -BeExactly "1 : 2 : " Invoke-Command $session { foo 1 2 3 4 } | Should -BeExactly "1 : 2 : 3 4" Invoke-Command $session { foo -first 1 2 3 4 } | Should -BeExactly "1 : 2 : 3 4" Invoke-Command $session { foo 1 -second 2 3 4 } | Should -BeExactly "1 : 2 : 3 4" Invoke-Command $session { foo 1 3 -second 2 4 } | Should -BeExactly "1 : 2 : 3 4" Invoke-Command $session { foo -first 1 -second 2 3 4 } | Should -BeExactly "1 : 2 : 3 4" $module = Import-PSSession $session foo -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Non cmdlet-based binding works." { foo | Should -BeExactly " : : " } It "Non cmdlet-based binding works." { foo 1 | Should -BeExactly "1 : : " } It "Non cmdlet-based binding works." { foo -first 1 | Should -BeExactly "1 : : " } It "Non cmdlet-based binding works." { foo 1 2 | Should -BeExactly "1 : 2 : " } It "Non cmdlet-based binding works." { foo 1 -second 2 | Should -BeExactly "1 : 2 : " } It "Non cmdlet-based binding works." { foo -first 1 -second 2 | Should -BeExactly "1 : 2 : " } It "Non cmdlet-based binding works." { foo 1 2 3 4 | Should -BeExactly "1 : 2 : 3 4" } It "Non cmdlet-based binding works." { foo -first 1 2 3 4 | Should -BeExactly "1 : 2 : 3 4" } It "Non cmdlet-based binding works." { foo 1 -second 2 3 4 | Should -BeExactly "1 : 2 : 3 4" } It "Non cmdlet-based binding works." { foo 1 3 -second 2 4 | Should -BeExactly "1 : 2 : 3 4" } It "Non cmdlet-based binding works." { foo -first 1 -second 2 3 4 | Should -BeExactly "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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Initializer run on the remote server" { (MyInitializerFunction) | Should -Be $remotePid } It "Initializer not run when value provided" { (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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Importing by name/type should work" { (Get-Variable -Name PID).Value | Should -Not -Be $PID } It "Test -AsJob parameter" { try { $job = Get-Variable -Name PID -AsJob $job | Should -Not -BeNullOrEmpty ($job -is [System.Management.Automation.Job]) | Should -BeTrue ($job.Finished.WaitOne([TimeSpan]::FromSeconds(10), $false)) | Should -BeTrue $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" { $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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Test warnings present with '-WarningAction Continue'" { 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'" { 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 -BeExactly "OutVariable = x" $module = Import-PSSession -Session $session -Name foo -Type function -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Implicit remoting: OutVariable is not intercepted for non-cmdlet-bound functions" { foo -OutVariable x | Should -BeExactly "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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "Switch parameters work fine" { $proxiedPid = Get-RemoteVariable -Name pid -ValueOnly $remotePid | Should -Be $proxiedPid } It "Positional parameters work fine" { $proxiedPid = Get-RemoteVariable pid $remotePid | Should -Be ($proxiedPid.Value) } } } Describe "Implicit remoting on restricted ISS" -tags "Feature","RequireAdminOnWindows","Slow" { BeforeAll { if ($skipTest) { return } $sessionConfigurationDll = [IO.Path]::Combine([IO.Path]::GetTempPath(), "ImplicitRemotingRestrictedConfiguration$(Get-Random).dll") Add-Type -OutputAssembly $sessionConfigurationDll -TypeDefinition @" using System; using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Management.Automation.Remoting; namespace MySessionConfiguration { 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); // add Out-String for testing stuff iss.Commands["Out-String"][0].Visibility = SessionStateEntryVisibility.Public; // remove all commands that are not public List<string> commandsToRemove = new List<string>(); foreach (SessionStateCommandEntry entry in iss.Commands) { List<SessionStateCommandEntry> sameNameEntries = new List<SessionStateCommandEntry>(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 */); } return iss; } } } "@ Get-PSSessionConfiguration ImplicitRemotingRestrictedConfiguration* | Unregister-PSSessionConfiguration -Force ## The 'Register-PSSessionConfiguration' call below raises an AssemblyLoadException in powershell: ## "Could not load file or assembly 'Microsoft.Powershell.Workflow.ServiceCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. The system cannot find the file specified." ## Issue #2555 is created to track this issue and all tests here are skipped for CoreCLR for now. $myConfiguration = Register-PSSessionConfiguration ` -Name ImplicitRemotingRestrictedConfiguration ` -ApplicationBase (Split-Path $sessionConfigurationDll) ` -AssemblyName (Split-Path $sessionConfigurationDll -Leaf) ` -ConfigurationTypeName "MySessionConfiguration.MySessionConfiguration" ` -Force $session = New-RemoteSession -ConfigurationName $myConfiguration.Name $session | Should -Not -BeNullOrEmpty } AfterAll { if ($skipTest) { return } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } if ($null -ne $myConfiguration) { Unregister-PSSessionConfiguration -Name ($myConfiguration.Name) -Force -ErrorAction SilentlyContinue } if ($null -ne $sessionConfigurationDll) { Remove-Item $sessionConfigurationDll -Force -ErrorAction SilentlyContinue } } Context "restrictions works" { It "Get-Variable is private" { @(Invoke-Command $session { Get-Command -Name Get-Variabl* }).Count | Should -Be 0 } It "Only 9 commands are public" { @(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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } 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 } It "Invoking an implicit remoting proxy works against the ISS-restricted runspace (Out-String)" { $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)" { $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","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $session = New-RemoteSession } AfterAll { if ($skipTest) { return } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } Context "Get-Command <Imported-Module> and <Imported-Module.Name> work (Windows 7: #334112)" { BeforeAll { if ($skipTest) { return } $module = Import-PSSession $session Get-Variable -Prefix My -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } It "PSModuleInfo.Name shouldn't contain a psd1 extension" { ($module.Name -notlike '*.psd1') | Should -BeTrue } It "PSModuleInfo.Name shouldn't contain a psm1 extension" { ($module.Name -notlike '*.psm1') | Should -BeTrue } It "PSModuleInfo.Name shouldn't contain a path" { ($module.Name -notlike "${env:TMP}*") | Should -BeTrue } # Test temporarily disabled because of conflict with DG UMCI tests. # Re-enable after DG UMCI tests moved to a separate test process. It "Get-Command returns only 1 public command from implicit remoting module (1)" -Pending { $c = @(Get-Command -Module $module) $c.Count | Should -Be 1 $c[0].Name | Should -BeExactly "Get-MyVariable" } # Test temporarily disabled because of conflict with DG UMCI tests. # Re-enable after DG UMCI tests moved to a separate test process. It "Get-Command returns only 1 public command from implicit remoting module (2)" -Pending { $c = @(Get-Command -Module $module.Name) $c.Count | Should -Be 1 $c[0].Name | Should -BeExactly "Get-MyVariable" } } Context "progress bar should be 1) present and 2) completed also" { BeforeAll { if ($skipTest) { return } $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 ($null -ne $file) { Remove-Item $file -Recurse -Force -ErrorAction SilentlyContinue } } It "'Completed' progress record should be present" { ($powerShell.Streams.Progress | Select-Object -Last 1).RecordType.ToString() | Should -BeExactly "Completed" } } 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() } # 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" { ($x | Out-String).Trim() | Should -Be $expected } It "Display of remote property-less objects" { (Invoke-Command $session { Import-Module Microsoft.PowerShell.Utility; New-Object random } | Out-String).Trim() | Should -Be $expected } } It "piping between remoting proxies should work" { try { $module = Import-PSSession -Session $session -Name Write-Output -AllowClobber $result = Write-Output 123 | Write-Output $result | Should -Be 123 } finally { if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } } It "Strange parameter names should trigger an error" { try { Invoke-Command $session { function attack(${foo="$(calc)"}){Write-Output "It is done."}} $module = Import-PSSession -Session $session -CommandName attack -ErrorAction SilentlyContinue -ErrorVariable expectedError -AllowClobber $expectedError | Should -Not -Be $null } finally { if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } } It "Non-terminating error from remote end got duplicated locally" { 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 -BeNullOrEmpty $msg = [string]($expectedError[0]) $msg.Contains("blah") | Should -BeTrue } finally { if ($null -ne $module) { 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" { 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 -BeNullOrEmpty $msg = [string]($expectedError[0]) $msg.Contains("notRequested") | Should -BeTrue } finally { if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } Invoke-Command $session { ${function:Get-Command} = $oldGetCommand } } } It "Get-Command returns something that is not CommandInfo" { Invoke-Command $session { $oldGetCommand = ${function:Get-Command} } Invoke-Command $session { function Get-Command { Microsoft.PowerShell.Utility\Get-Variable } } $e = { $module = Import-PSSession -Session $session -AllowClobber } | Should -Throw -PassThru $msg = [string]($e) $msg.Contains("Get-Command") | Should -BeTrue if ($null -ne $module) { 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" { 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 ($null -ne $module) { 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" { 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 ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } (Get-Item function:Get-MyVariable -ErrorAction SilentlyContinue) | Should -BeNullOrEmpty } 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" { 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 ($null -ne $module) { $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" { 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 | Should -BeNullOrEmpty ## BadVerb-Variable should be a function, not an alias (1) Get-Item Function:\BadVerb-Variable -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty ## BadVerb-Variable should be a function, not an alias (2) Get-Item Alias:\BadVerb-Variable -ErrorAction SilentlyContinue | Should -BeNullOrEmpty (BadVerb-Variable -Name pid).Value | Should -Be $remotePid } finally { if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } } It "Test warning is supressed by '-DisableNameChecking'" { 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 ($null -ne $module) { $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" { 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 | Should -BeNullOrEmpty ## BadVerb-Variable should be a function, not an alias (1) Get-Item Function:\BadVerb-Variable -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty ## BadVerb-Variable should be a function, not an alias (2) Get-Item Alias:\BadVerb-Variable -ErrorAction SilentlyContinue | Should -BeNullOrEmpty (BadVerb-Variable -Name pid).Value | Should -Be $remotePid } finally { if ($null -ne $module) { 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" { 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 ($null -ne $module) { $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" { 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 | Should -BeNullOrEmpty ## BadVerb-Variable should be an alias, not a function (2) Get-Item Alias:\BadVerb-Variable -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty (BadVerb-Variable -Name pid).Value | Should -Be $remotePid } finally { if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } } } It "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 $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 @(Get-ChildItem function:*Implicit* -ErrorAction SilentlyContinue | Where-Object {$_.ModuleName -eq $module.Name}).Count | Should -Be 0 } } Describe "Export-PSSession function" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $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 } AfterAll { if ($skipTest) { return } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } if ($null -ne $tempdir) { Remove-Item $tempdir -Force -Recurse -ErrorAction SilentlyContinue } } It "Test the module created by Export-PSSession" { try { Export-PSSession -Session $session -OutputModule $tempdir\Diag -CommandName New-Guid -AllowClobber > $null # Only the snapin Microsoft.PowerShell.Core is loaded $iss = [initialsessionstate]::CreateDefault2() $ps = [PowerShell]::Create($iss) $result = $ps.AddScript(" & $tempdir\TestBug450687.ps1").Invoke() ## The module created by Export-PSSession is imported successfully ($null -ne $result -and $result.Count -eq 1 -and $result[0].Name -eq "Diag") | Should -BeTrue ## The command Add-BitsFile is imported successfully $c = $result[0].ExportedCommands["New-Guid"] ($null -ne $c -and $c.CommandType -eq "Function") | Should -BeTrue } finally { $ps.Dispose() } } } Describe "Implicit remoting with disconnected session" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $session = New-RemoteSession -Name Session102 $remotePid = Invoke-Command $session { $PID } $module = Import-PSSession $session Get-Variable -Prefix Remote -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } It "Remote session PID should be different" { $sessionPid = Get-RemoteVariable pid $sessionPid.Value | Should -Be $remotePid } It "Disconnected session should be reconnected when calling proxied command" { Disconnect-PSSession $session $dSessionPid = Get-RemoteVariable pid $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 the Windows Azure DevOps builder, so mark this test '-pending'. ## Also, this feature doesn't work on macOS or Linux It "Should have a new session when the disconnected session cannot be re-connected" -Pending { ## Disconnect session and make it un-connectable. Disconnect-PSSession $session Start-Process powershell -arg 'Get-PSSession -cn localhost -name Session102 | Connect-PSSession' -Wait Start-Sleep -Seconds 3 ## This time a new session is created because the old one is unavailable. $dSessionPid = Get-RemoteVariable pid $dSessionPid.Value | Should -Not -Be $remotePid } } Describe "Select-Object with implicit remoting" -tags "Feature","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $session = New-RemoteSession Invoke-Command $session { function foo { "a","b","c" } } $module = Import-PSSession $session foo -AllowClobber } AfterAll { if ($skipTest) { return } if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } It "Select-Object -First should work with implicit remoting" { $bar = foo | Select-Object -First 2 $bar | Should -Not -BeNullOrEmpty $bar.Count | Should -Be 2 $bar[0] | Should -BeExactly "a" $bar[1] | Should -BeExactly "b" } } Describe "Get-FormatData used in Export-PSSession should work on DL targets" -tags "Feature","RequireAdminOnWindows" { BeforeAll { # Skip tests for CoreCLR for now # Skip tests if .NET 2.0 and PS 2.0 are not installed on the machine $skipThisTest = $skipTest -or $IsCoreCLR -or (! (Test-Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727')) -or (! (Test-Path 'HKLM:\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine')) if ($skipThisTest) { return } ## The call to 'Register-PSSessionConfiguration -PSVersion 2.0' below raises an exception: ## `Cannot bind parameter 'PSVersion' to the target. Exception setting "PSVersion": "Windows PowerShell 2.0 is not installed. ## Install Windows PowerShell 2.0, and then try again."` ## Issue #2556 is created to track this issue and the test here is skipped for CoreCLR for now. $configName = "DLConfigTest" $null = Register-PSSessionConfiguration -Name $configName -PSVersion 2.0 -Force $session = New-RemoteSession -ConfigurationName $configName } AfterAll { if ($skipThisTest) { return } if ($null -ne $session) { 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:$skipThisTest { 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","RequireAdminOnWindows" { BeforeAll { if ($skipTest) { return } $session = New-RemoteSession } AfterAll { if ($skipTest) { return } if ($null -ne $session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } It "Verifies that the number of local cmdlet command count is the same as remote cmdlet command count." { $localCommandCount = (Get-Command -Type Cmdlet).Count $remoteCommandCount = Invoke-Command { (Get-Command -Type Cmdlet).Count } $localCommandCount | Should -Be $remoteCommandCount } } Describe "Import-PSSession on Restricted Session" -tags "Feature","RequireAdminOnWindows","Slow" { BeforeAll { if ($skipTest) { return } $configName = "restricted_" + (Get-RandomFileName) New-PSSessionConfigurationFile -Path $TestDrive\restricted.pssc -SessionType RestrictedRemoteServer Register-PSSessionConfiguration -Path $TestDrive\restricted.pssc -Name $configName -Force $session = New-RemoteSession -ConfigurationName $configName } AfterAll { if ($skipTest) { return } if ($session -ne $null) { Remove-PSSession -Session $session -ErrorAction SilentlyContinue } Unregister-PSSessionConfiguration -Name $configName -Force -ErrorAction SilentlyContinue } It "Verifies that Import-PSSession works on a restricted session" { $errorVariable = $null try { $module = Import-PSSession -Session $session -AllowClobber -ErrorVariable $errorVariable -CommandName Get-Help } finally { if ($module -ne $null) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } $errorVariable | Should -BeNullOrEmpty } } } finally { $global:PSDefaultParameterValues = $originalDefaultParameterValues $WarningPreference = $originalWarningPreference }