2020-03-24 11:08:37 -07:00
# Copyright (c) Microsoft Corporation.
2018-07-16 16:58:40 -07:00
# Licensed under the MIT License.
$script:oldModulePath = $env:PSModulePath
function Add-ModulePath
{
param ([ string ] $Path , [ switch ] $Prepend )
$script:oldModulePath = $env:PSModulePath
if ( $Prepend )
{
2022-01-12 10:02:03 -08:00
$env:PSModulePath = $Path + [ System.IO.Path ]:: PathSeparator + $env:PSModulePath
2018-07-16 16:58:40 -07:00
}
else
{
$env:PSModulePath = $env:PSModulePath + [ System.IO.Path ]:: PathSeparator + $Path
}
}
function Restore-ModulePath
{
$env:PSModulePath = $script:oldModulePath
}
# Creates a new dummy module compatible with the given PSEditions
function New-EditionCompatibleModule
{
param (
[ Parameter ( Mandatory = $true )][ string ] $ModuleName ,
[ string ] $DirPath ,
2019-12-09 15:20:40 -08:00
[ string[] ] $CompatiblePSEditions ,
[ string ] $ErrorGenerationCode = '' )
2018-07-16 16:58:40 -07:00
$modulePath = Join-Path $DirPath $ModuleName
$manifestPath = Join-Path $modulePath " $ModuleName .psd1"
$psm1Name = " $ModuleName .psm1"
$psm1Path = Join-Path $modulePath $psm1Name
New-Item -Path $modulePath -ItemType Directory
2019-12-09 15:20:40 -08:00
New-Item -Path $psm1Path -Value " $ErrorGenerationCode function Test- $ModuleName { `$ true } function Test- ${ModuleName} PSEdition { `$ PSVersionTable.PSEdition }" -Force
2018-07-16 16:58:40 -07:00
if ( $CompatiblePSEditions )
{
New-ModuleManifest -Path $manifestPath -CompatiblePSEditions $CompatiblePSEditions -RootModule $psm1Name
}
else
{
New-ModuleManifest -Path $manifestPath -RootModule $psm1Name
}
return $modulePath
}
function New-TestModules
{
param ([ hashtable[] ] $TestCases , [ string ] $BaseDir )
for ( $i = 0 ; $i -lt $TestCases . Count ; $i ++)
{
$path = New-EditionCompatibleModule -ModuleName $TestCases [ $i ]. ModuleName -CompatiblePSEditions $TestCases [ $i ]. Editions -Dir $BaseDir
$TestCases [ $i ]. Path = $path
$TestCases [ $i ]. Name = $TestCases [ $i ]. Editions -join ","
}
}
function New-TestNestedModule
{
param (
[ string ] $ModuleBase ,
[ string ] $ScriptModuleFilename ,
[ string ] $ScriptModuleContent ,
[ string ] $BinaryModuleFilename ,
[ string ] $BinaryModuleDllPath ,
[ string ] $RootModuleFilename ,
[ string ] $RootModuleContent ,
[ string[] ] $CompatiblePSEditions ,
[ bool ] $UseRootModule ,
[ bool ] $UseAbsolutePath
)
$nestedModules = [ System.Collections.ArrayList ]:: new ()
# Create script module
New-Item -Path ( Join-Path $ModuleBase $ScriptModuleFileName ) -Value $ScriptModuleContent
$nestedModules . Add ( $ScriptModuleFilename )
if ( $BinaryModuleFilename -and $BinaryModuleDllPath )
{
# Create binary module
Copy-Item -Path $BinaryModuleDllPath -Destination ( Join-Path $ModuleBase $BinaryModuleFilename )
$nestedModules . Add ( $BinaryModuleFilename )
}
# Create the root module if there is one
if ( $UseRootModule )
{
New-Item -Path ( Join-Path $ModuleBase $RootModuleFilename ) -Value $RootModuleContent
}
# Create the manifest command
$moduleName = Split-Path -Leaf $ModuleBase
$manifestPath = Join-Path $ModuleBase " $moduleName .psd1"
$nestedModules = $nestedModules -join ','
$newManifestCmd = "New-ModuleManifest -Path $manifestPath -NestedModules $nestedModules "
if ( $CompatiblePSEditions )
{
$compatibleModules = $CompatiblePSEditions -join ','
$newManifestCmd += "-CompatiblePSEditions $compatibleModules "
}
if ( $UseRootModule )
{
$newManifestCmd += "-RootModule $RootModuleFilename "
2019-11-18 10:44:55 -08:00
$newManifestCmd += "-FunctionsToExport @('Test-RootModule','Test-RootModulePSEdition') "
2018-07-16 16:58:40 -07:00
}
else
{
2019-11-18 10:44:55 -08:00
$newManifestCmd += "-FunctionsToExport @('Test-ScriptModule','Test-ScriptModulePSEdition') "
2018-07-16 16:58:40 -07:00
}
$newManifestCmd += "-CmdletsToExport @() -VariablesToExport @() -AliasesToExport @() "
# Create the manifest
[ scriptblock ]:: Create ( $newManifestCmd ). Invoke ()
}
2022-01-12 10:02:03 -08:00
function Get-DesktopModuleToUse {
$system32Path = " $env:windir \system32\WindowsPowerShell\v1.0\Modules"
$persistentMemoryModule = "PersistentMemory"
$remoteDesktopModule = "RemoteDesktop"
if ( Test-Path -PathType Container " $system32Path \ $persistentMemoryModule " ) {
return $persistentMemoryModule
} elseif ( Test-Path -PathType Container " $system32Path \ $remoteDesktopModule " ) {
return $remoteDesktopModule
} else {
return $null
}
}
$desktopModuleToUse = Get-DesktopModuleToUse
2018-07-16 16:58:40 -07:00
Describe "Get-Module with CompatiblePSEditions-checked paths" -Tag "CI" {
BeforeAll {
if ( -not $IsWindows )
{
return
}
$successCases = @ (
@ { Editions = "Core" , "Desktop" ; ModuleName = "BothModule" },
@ { Editions = "Core" ; ModuleName = "CoreModule" }
)
$failCases = @ (
@ { Editions = "Desktop" ; ModuleName = "DesktopModule" },
@ { Editions = $null ; ModuleName = "NeitherModule" }
)
$basePath = Join-Path $TestDrive "EditionCompatibleModules"
New-TestModules -TestCases $successCases -BaseDir $basePath
New-TestModules -TestCases $failCases -BaseDir $basePath
# Emulate the System32 module path for tests
[ System.Management.Automation.Internal.InternalTestHooks ]:: SetTestHook ( "TestWindowsPowerShellPSHomeLocation" , $basePath )
}
AfterAll {
[ System.Management.Automation.Internal.InternalTestHooks ]:: SetTestHook ( "TestWindowsPowerShellPSHomeLocation" , $null )
}
Context "Loading from checked paths on the module path with no flags" {
BeforeAll {
Add-ModulePath $basePath
$modules = Get-Module -ListAvailable
}
AfterAll {
Restore-ModulePath
}
It "Lists compatible modules from the module path with -ListAvailable for PSEdition <Editions>" -TestCases $successCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName )
$modules . Name | Should -Contain $ModuleName
}
It "Does not list incompatible modules with -ListAvailable for PSEdition <Editions>" -TestCases $failCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName )
$modules . Name | Should -Not -Contain $ModuleName
}
}
Context "Loading from checked paths by absolute path with no flags" {
It "Lists compatible modules with -ListAvailable for PSEdition <Editions>" -TestCases $successCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName )
$modules = Get-Module -ListAvailable ( Join-Path -Path $basePath -ChildPath $ModuleName )
$modules . Name | Should -Contain $ModuleName
}
It "Does not list incompatible modules with -ListAvailable for PSEdition <Editions>" -TestCases $failCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName )
$modules = Get-Module -ListAvailable ( Join-Path -Path $basePath -ChildPath $ModuleName )
$modules . Name | Should -Not -Contain $ModuleName
}
}
Context "Loading from checked paths on the module path with -SkipEditionCheck" {
BeforeAll {
Add-ModulePath $basePath
$modules = Get-Module -ListAvailable -SkipEditionCheck
}
AfterAll {
Restore-ModulePath
}
It "Lists all modules from the module path with -ListAvailable for PSEdition <Editions>" -TestCases ( $successCases + $failCases ) -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName )
$modules . Name | Should -Contain $ModuleName
}
}
Context "Loading from checked paths by absolute path with -SkipEditionCheck" {
It "Lists compatible modules with -ListAvailable for PSEdition <Editions>" -TestCases ( $successCases + $failCases ) -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName )
$modules = Get-Module -ListAvailable -SkipEditionCheck ( Join-Path -Path $basePath -ChildPath $ModuleName )
$modules . Name | Should -Contain $ModuleName
}
}
}
Describe "Import-Module from CompatiblePSEditions-checked paths" -Tag "CI" {
BeforeAll {
$successCases = @ (
@ { Editions = "Core" , "Desktop" ; ModuleName = "BothModule" ; Result = $true },
@ { Editions = "Core" ; ModuleName = "CoreModule" ; Result = $true }
)
$failCases = @ (
@ { Editions = "Desktop" ; ModuleName = "DesktopModule" ; Result = $true },
@ { Editions = $null ; ModuleName = "NeitherModule" ; Result = $true }
)
$basePath = Join-Path $TestDrive "EditionCompatibleModules"
New-TestModules -TestCases $successCases -BaseDir $basePath
New-TestModules -TestCases $failCases -BaseDir $basePath
2019-12-10 10:40:13 -08:00
$allCases = $successCases + $failCases
$allModules = $allCases . ModuleName
$versionTestCases = @ ()
foreach ( $versionString in @ ( '1.0' , '2.0' , '3.0' , '4.0' , '5.0' , '5.1' , '5.1.14393.0' ))
{
foreach ( $case in $allCases )
{
$versionTestCases += $case + @ { WinPSVersion = $versionString }
}
}
2018-07-16 16:58:40 -07:00
2019-11-18 10:44:55 -08:00
# make sure there are no ImplicitRemoting leftovers from previous tests
Get-Module | Where-Object { $_ . PrivateData . ImplicitRemoting } | Remove-Module -Force
Get-PSSession -Name WinPSCompatSession -ErrorAction SilentlyContinue | Remove-PSSession
2018-07-16 16:58:40 -07:00
# Emulate the System32 module path for tests
[ System.Management.Automation.Internal.InternalTestHooks ]:: SetTestHook ( "TestWindowsPowerShellPSHomeLocation" , $basePath )
}
AfterAll {
[ System.Management.Automation.Internal.InternalTestHooks ]:: SetTestHook ( "TestWindowsPowerShellPSHomeLocation" , $null )
}
AfterEach {
Get-Module $allModules | Remove-Module -Force
}
Context "Imports from module path" {
BeforeAll {
Add-ModulePath $basePath
}
AfterAll {
Restore-ModulePath
}
It "Successfully imports compatible modules from the module path with PSEdition <Editions>" -TestCases $successCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result )
Import-Module $ModuleName -Force
& "Test- $ModuleName " | Should -Be $Result
}
2019-11-18 10:44:55 -08:00
It "Successfully imports incompatible modules from the module path with PSEdition <Editions> using WinCompat" -TestCases $failCases -Skip: ( -not $IsWindows ) {
2018-07-16 16:58:40 -07:00
param ( $Editions , $ModuleName , $Result )
2019-11-18 10:44:55 -08:00
Import-Module $ModuleName -Force -ErrorAction 'Stop'
& "Test- $ModuleName " | Should -Be $Result
2018-07-16 16:58:40 -07:00
}
It "Imports an incompatible module from the module path with -SkipEditionCheck with PSEdition <Editions>" -TestCases ( $successCases + $failCases ) -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result )
Import-Module $ModuleName -SkipEditionCheck -Force
& "Test- $ModuleName " | Should -Be $Result
}
2019-11-18 10:44:55 -08:00
It "Imports any module using WinCompat from the module path with -UseWindowsPowerShell with PSEdition <Editions>" -TestCases ( $successCases + $failCases ) -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result )
Import-Module $ModuleName -UseWindowsPowerShell -Force
& "Test- ${ModuleName} PSEdition" | Should -Be 'Desktop'
}
2019-12-10 10:40:13 -08:00
It "WinCompat works only with Windows PS 5.1 (when PSEdition <Editions> and WinPSVersion <WinPSVersion>)" -TestCases $versionTestCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result , $WinPSVersion )
try {
[ System.Management.Automation.Internal.InternalTestHooks ]:: SetTestHook ( "TestWindowsPowerShellVersionString" , $WinPSVersion )
if ( $WinPSVersion . StartsWith ( '5.1' )) {
Import-Module $ModuleName -UseWindowsPowerShell -Force
& "Test- ${ModuleName} PSEdition" | Should -Be 'Desktop'
}
else {
{ Import-Module $ModuleName -UseWindowsPowerShell -Force } | Should -Throw -ErrorId "InvalidOperationException"
}
}
finally {
[ System.Management.Automation.Internal.InternalTestHooks ]:: SetTestHook ( "TestWindowsPowerShellVersionString" , $null )
}
}
2020-02-11 11:44:30 -08:00
It "Current location in Windows PS mirrors local current location" -TestCases $failCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result )
$pwdBackup = $PWD
$location = Join-Path $TestDrive "Custom dir" ( New-Guid ). ToString ()
$null = New-Item -Path $location -ItemType Directory
Push-Location -Path $location
try
{
# right after module import remote $PWD should be synchronized
Import-Module $ModuleName -UseWindowsPowerShell
$s = Get-PSSession -Name WinPSCompatSession
( Invoke-Command -Session $s { Get-Location }). Path | Should -BeExactly $PWD . Path
# after local $PWD changes remote $PWD should be synchronized
Set-Location -Path ..
( Invoke-Command -Session $s { Get-Location }). Path | Should -BeExactly $PWD . Path
# after WinCompat cleanup local $PWD changes should not cause errors
2020-05-07 13:00:30 +01:00
Remove-Module $ModuleName -Force
2020-02-11 11:44:30 -08:00
Pop-Location
}
finally
{
Set-Location $pwdBackup
}
}
2020-03-09 23:31:44 -07:00
It "-NoClobber and -Scope work with implicit WinCompat" -TestCases $failCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result )
try
{
Set-Item function : Test - $ModuleName { "OriginalFunctionImplementation" }
Import-Module $ModuleName -Force -WarningAction Ignore -Scope Local -NoClobber
& "Test- $ModuleName " | Should -BeExactly "OriginalFunctionImplementation"
}
finally
{
Remove-Item function : Test - $ModuleName
}
}
2018-07-16 16:58:40 -07:00
}
Context "Imports from absolute path" {
It "Successfully imports compatible modules from an absolute path with PSEdition <Editions>" -TestCases $successCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result )
$path = Join-Path -Path $basePath -ChildPath $ModuleName
Import-Module $path -Force
& "Test- $ModuleName " | Should -Be $Result
}
2019-11-18 10:44:55 -08:00
It "Successfully imports incompatible modules from an absolute path with PSEdition <Editions> using WinCompat" -TestCases $failCases -Skip: ( -not $IsWindows ) {
2018-07-16 16:58:40 -07:00
param ( $Editions , $ModuleName , $Result )
$path = Join-Path -Path $basePath -ChildPath $ModuleName
2019-11-18 10:44:55 -08:00
Import-Module $path -Force -ErrorAction 'Stop'
& "Test- $ModuleName " | Should -Be $Result
2018-07-16 16:58:40 -07:00
}
It "Imports an incompatible module from an absolute path with -SkipEditionCheck with PSEdition <Editions>" -TestCases ( $successCases + $failCases ) -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result )
$path = Join-Path -Path $basePath -ChildPath $ModuleName
Import-Module $path -SkipEditionCheck -Force
& "Test- $ModuleName " | Should -Be $Result
}
2019-11-18 10:44:55 -08:00
It "Imports any module using WinCompat from an absolute path with -UseWindowsPowerShell with PSEdition <Editions>" -TestCases ( $successCases + $failCases ) -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result )
$path = Join-Path -Path $basePath -ChildPath $ModuleName
Import-Module $path -UseWindowsPowerShell -Force
& "Test- ${ModuleName} PSEdition" | Should -Be 'Desktop'
}
}
Context "Imports using CommandDiscovery\ModuleAutoload" {
BeforeAll {
Add-ModulePath $basePath
}
AfterAll {
Restore-ModulePath
}
It "Successfully auto-imports compatible modules from the module path with PSEdition <Editions>" -TestCases $successCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result )
& "Test- $ModuleName " | Should -Be $Result
}
It "Successfully auto-imports incompatible modules from the module path with PSEdition <Editions> using WinCompat" -TestCases $failCases -Skip: ( -not $IsWindows ) {
param ( $Editions , $ModuleName , $Result )
& "Test- ${ModuleName} PSEdition" | Should -Be 'Desktop'
}
2018-07-16 16:58:40 -07:00
}
}
2020-01-03 10:32:00 -08:00
Describe "Additional tests for Import-Module with WinCompat" -Tag "Feature" {
2019-12-09 15:20:40 -08:00
BeforeAll {
2020-01-03 10:32:00 -08:00
if ( ! $IsWindows ) {
2023-08-16 11:28:59 -07:00
Push-DefaultParameterValueStack @ { "it:skip" = $true }
return
2020-01-03 10:32:00 -08:00
}
2019-12-09 15:20:40 -08:00
$ModuleName = "DesktopModule"
2020-01-03 10:32:00 -08:00
$ModuleName2 = "DesktopModule2"
2019-12-09 15:20:40 -08:00
$basePath = Join-Path $TestDrive "WinCompatModules"
2020-03-09 23:31:44 -07:00
$allModules = @ ( $ModuleName , $ModuleName2 )
2019-12-09 15:20:40 -08:00
Remove-Item -Path $basePath -Recurse -ErrorAction SilentlyContinue
# create an incompatible module that generates an error on import
New-EditionCompatibleModule -ModuleName $ModuleName -CompatiblePSEditions "Desktop" -Dir $basePath -ErrorGenerationCode '1/0;'
2020-01-03 10:32:00 -08:00
# create an incompatible module
New-EditionCompatibleModule -ModuleName $ModuleName2 -CompatiblePSEditions "Desktop" -Dir $basePath
}
AfterAll {
2023-08-16 11:28:59 -07:00
if ( ! $IsWindows ) {
Pop-DefaultParameterValueStack
return
}
2019-12-09 15:20:40 -08:00
}
Context "Tests that ErrorAction/WarningAction have effect when Import-Module with WinCompat is used" {
BeforeAll {
2020-01-05 12:17:41 +05:00
$pwsh = " $PSHOME /pwsh"
2019-12-09 15:20:40 -08:00
Add-ModulePath $basePath
}
AfterAll {
Restore-ModulePath
}
2020-01-03 10:32:00 -08:00
It "Verify that Error is generated with default ErrorAction" {
2019-12-09 15:20:40 -08:00
$LogPath = Join-Path $TestDrive ( New-Guid ). ToString ()
2020-01-05 12:17:41 +05:00
& $pwsh -NoProfile -NonInteractive -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName " *> $LogPath
2019-12-09 15:20:40 -08:00
$LogPath | Should -FileContentMatch 'divide by zero'
}
2020-01-03 10:32:00 -08:00
It "Verify that Warning is generated with default WarningAction" {
2019-12-09 15:20:40 -08:00
$LogPath = Join-Path $TestDrive ( New-Guid ). ToString ()
2021-02-08 09:42:53 -08:00
& $pwsh -NoProfile -NonInteractive -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName ;Get-Error" *> $LogPath
$LogPath | Should -FileContentMatch 'loaded in Windows PowerShell' -Because ( Get-Content $LogPath )
2019-12-09 15:20:40 -08:00
}
2020-01-03 10:32:00 -08:00
It "Verify that Error is Not generated with -ErrorAction Ignore" {
2019-12-09 15:20:40 -08:00
$LogPath = Join-Path $TestDrive ( New-Guid ). ToString ()
2020-01-05 12:17:41 +05:00
& $pwsh -NoProfile -NonInteractive -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName -ErrorAction Ignore" *> $LogPath
2019-12-09 15:20:40 -08:00
$LogPath | Should -Not -FileContentMatch 'divide by zero'
}
2020-01-03 10:32:00 -08:00
It "Verify that Warning is Not generated with -WarningAction Ignore" {
2019-12-09 15:20:40 -08:00
$LogPath = Join-Path $TestDrive ( New-Guid ). ToString ()
2020-01-05 12:17:41 +05:00
& $pwsh -NoProfile -NonInteractive -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName -WarningAction Ignore" *> $LogPath
2019-12-09 15:20:40 -08:00
$LogPath | Should -Not -FileContentMatch 'loaded in Windows PowerShell'
}
2020-01-03 10:32:00 -08:00
It "Fails to import incompatible module if implicit WinCompat is disabled in config" {
$LogPath = Join-Path $TestDrive ( New-Guid ). ToString ()
$ConfigPath = Join-Path $TestDrive 'powershell.config.json'
'{"DisableImplicitWinCompat" : "True"}' | Out-File -Force $ConfigPath
2020-02-04 16:33:26 -08:00
& $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName2 " *> $LogPath
2020-01-03 10:32:00 -08:00
$LogPath | Should -FileContentMatch 'cannot be loaded implicitly using the Windows Compatibility'
}
It "Fails to auto-import incompatible module during CommandDiscovery\ModuleAutoload if implicit WinCompat is Disabled in config" {
$LogPath = Join-Path $TestDrive ( New-Guid ). ToString ()
$ConfigPath = Join-Path $TestDrive 'powershell.config.json'
2020-02-04 16:33:26 -08:00
'{"DisableImplicitWinCompat" : "True","Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned"}' | Out-File -Force $ConfigPath
& $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' ); Test- $ModuleName2 " *> $LogPath
2020-08-06 10:02:43 +02:00
$LogPath | Should -FileContentMatch 'not recognized as a name of a cmdlet'
2020-01-03 10:32:00 -08:00
}
It "Successfully auto-imports incompatible module during CommandDiscovery\ModuleAutoload if implicit WinCompat is Enabled in config" {
$LogPath = Join-Path $TestDrive ( New-Guid ). ToString ()
$ConfigPath = Join-Path $TestDrive 'powershell.config.json'
2020-02-04 16:33:26 -08:00
'{"DisableImplicitWinCompat" : "False","Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned"}' | Out-File -Force $ConfigPath
& $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' ); Test- $ModuleName2 " *> $LogPath
2020-01-03 10:32:00 -08:00
$LogPath | Should -FileContentMatch 'True'
}
2019-12-09 15:20:40 -08:00
}
2020-02-04 16:33:26 -08:00
Context "Tests around Windows PowerShell Compatibility module deny list" {
BeforeAll {
$pwsh = " $PSHOME /pwsh"
Add-ModulePath $basePath
$ConfigPath = Join-Path $TestDrive 'powershell.config.json'
}
AfterAll {
Restore-ModulePath
}
It "Successfully imports incompatible module when DenyList is not specified in powershell.config.json" {
'{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned"}' | Out-File -Force $ConfigPath
& $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName2 -WarningAction Ignore;Test- ${ModuleName2} PSEdition" | Should -Be 'Desktop'
}
It "Successfully imports incompatible module when DenyList is empty" {
'{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned","WindowsPowerShellCompatibilityModuleDenyList": []}' | Out-File -Force $ConfigPath
& $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName2 -WarningAction Ignore;Test- ${ModuleName2} PSEdition" | Should -Be 'Desktop'
}
It "Blocks DenyList module import by Import-Module <ModuleName> -UseWindowsPowerShell" {
'{"WindowsPowerShellCompatibilityModuleDenyList": ["' + $ModuleName2 + '"]}' | Out-File -Force $ConfigPath
$out = & $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName2 -UseWindowsPowerShell -ErrorVariable z -ErrorAction SilentlyContinue; `$ z.FullyQualifiedErrorId"
$out | Should -BeExactly 'Modules_ModuleInWinCompatDenyList,Microsoft.PowerShell.Commands.ImportModuleCommand'
}
It "Blocks DenyList module import by Import-Module <ModuleName>" {
'{"WindowsPowerShellCompatibilityModuleDenyList": ["' + $ModuleName2 . ToLowerInvariant () + '"]}' | Out-File -Force $ConfigPath # also check case-insensitive comparison
$out = & $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName2 -ErrorVariable z -ErrorAction SilentlyContinue; `$ z.FullyQualifiedErrorId"
$out | Should -BeExactly 'Modules_ModuleInWinCompatDenyList,Microsoft.PowerShell.Commands.ImportModuleCommand'
}
It "Blocks DenyList module import by CommandDiscovery\ModuleAutoload" {
'{"WindowsPowerShellCompatibilityModuleDenyList": ["RandomNameJustToMakeArrayOfSeveralModules","' + $ModuleName2 + '"]}' | Out-File -Force $ConfigPath
$out = & $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' ); `$ ErrorActionPreference = 'SilentlyContinue';Test- $ModuleName2 ; `$ error[0].FullyQualifiedErrorId"
$out | Should -BeExactly 'CouldNotAutoloadMatchingModule'
}
}
2020-03-09 23:31:44 -07:00
2020-04-16 16:57:55 -07:00
Context "Tests around Windows PowerShell Compatibility NoClobber module list" {
BeforeAll {
$pwsh = " $PSHOME /pwsh"
Add-ModulePath $basePath
$ConfigPath = Join-Path $TestDrive 'powershell.config.json'
}
AfterAll {
Restore-ModulePath
}
It "NoClobber WinCompat import works for an engine module through command discovery" {
ConvertFrom-String -InputObject '1,2,3' -Delimiter ',' | Out-Null
$modules = Get-Module -Name Microsoft . PowerShell . Utility
$modules . Count | Should -Be 2
$proxyModule = $modules | Where-Object { $_ . ModuleType -eq 'Script' }
$coreModule = $modules | Where-Object { $_ . ModuleType -eq 'Manifest' }
$proxyModule . ExportedCommands . Keys | Should -Contain "ConvertFrom-String"
$proxyModule . ExportedCommands . Keys | Should -Not -Contain "Get-Date"
$coreModule . ExportedCommands . Keys | Should -Contain "Get-Date"
$coreModule . ExportedCommands . Keys | Should -Not -Contain "ConvertFrom-String"
$proxyModule | Remove-Module -Force
}
It "NoClobber WinCompat import works for an engine module through -UseWindowsPowerShell parameter" {
2022-02-07 17:34:52 -08:00
# pre-test cleanup
Get-Module -Name Microsoft . PowerShell . Management | Remove-Module
Import-Module -Name Microsoft . PowerShell . Management # import the one that comes with PSCore
2020-04-16 16:57:55 -07:00
Import-Module Microsoft . PowerShell . Management -UseWindowsPowerShell
$modules = Get-Module -Name Microsoft . PowerShell . Management
2022-02-07 17:34:52 -08:00
2020-04-16 16:57:55 -07:00
$modules . Count | Should -Be 2
$proxyModule = $modules | Where-Object { $_ . ModuleType -eq 'Script' }
$coreModule = $modules | Where-Object { $_ . ModuleType -eq 'Manifest' }
$proxyModule . ExportedCommands . Keys | Should -Contain "Get-WmiObject"
$proxyModule . ExportedCommands . Keys | Should -Not -Contain "Get-Item"
$coreModule . ExportedCommands . Keys | Should -Contain "Get-Item"
$coreModule . ExportedCommands . Keys | Should -Not -Contain "Get-WmiObject"
$proxyModule | Remove-Module -Force
}
It "NoClobber WinCompat import works with ModuleSpecifications" {
Import-Module -UseWindowsPowerShell -FullyQualifiedName @ { ModuleName = 'Microsoft.PowerShell.Utility' ; ModuleVersion = '0.0' }
$modules = Get-Module -Name Microsoft . PowerShell . Utility
$modules . Count | Should -Be 2
$proxyModule = $modules | Where-Object { $_ . ModuleType -eq 'Script' }
$coreModule = $modules | Where-Object { $_ . ModuleType -eq 'Manifest' }
$proxyModule . ExportedCommands . Keys | Should -Contain "ConvertFrom-String"
$proxyModule . ExportedCommands . Keys | Should -Not -Contain "Get-Date"
$coreModule . ExportedCommands . Keys | Should -Contain "Get-Date"
$coreModule . ExportedCommands . Keys | Should -Not -Contain "ConvertFrom-String"
$proxyModule | Remove-Module -Force
}
It "NoClobber WinCompat list in powershell.config is missing " {
'{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned"}' | Out-File -Force $ConfigPath
& $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName2 -WarningAction Ignore;Test- ${ModuleName2} PSEdition" | Should -Be 'Desktop'
}
It "NoClobber WinCompat list in powershell.config is empty " {
'{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned", "WindowsPowerShellCompatibilityNoClobberModuleList": []}' | Out-File -Force $ConfigPath
& $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Import-Module $ModuleName2 -WarningAction Ignore;Test- ${ModuleName2} PSEdition" | Should -Be 'Desktop'
}
It "NoClobber WinCompat list in powershell.config is working " {
$targetModuleFolder = Join-Path $TestDrive "TempWinCompatModuleFolder"
Copy-Item -Path " $basePath \ $ModuleName2 " -Destination " $targetModuleFolder \ $ModuleName2 " -Recurse -Force
$env:PSModulePath = $targetModuleFolder + [ System.IO.Path ]:: PathSeparator + $env:PSModulePath
$psm1 = Get-ChildItem -Recurse -Path $targetModuleFolder -Filter " $ModuleName2 .psm1"
"function Test- $ModuleName2 { `$ PSVersionTable.PSEdition }" | Out-File -FilePath $psm1 . FullName -Force
# Now Core version of the module has 1 function: Test-$ModuleName2 (returns 'Core')
# and WinPS version of the module has 2 functions: Test-$ModuleName2 (returns '$true'), Test-${ModuleName2}PSEdition (returns 'Desktop')
# when NoClobber WinCompat import is working Test-$ModuleName2 should return 'Core'
'{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned", "WindowsPowerShellCompatibilityNoClobberModuleList": ["' + $ModuleName2 + '"]}' | Out-File -Force $ConfigPath
& $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `' $basePath `' );Test- ${ModuleName2} PSEdition;Test- $ModuleName2 " | Should -Be @ ( 'Desktop' , 'Core' )
}
2022-01-12 10:02:03 -08:00
It "NoClobber WinCompat list in powershell.config is a Desktop-edition module" {
2023-08-16 11:28:59 -07:00
if ( Test-IsWinWow64 ) {
Set-ItResult -Skipped -Because "This test is not applicable to WoW64."
return
}
2022-01-12 10:02:03 -08:00
if ( -not $desktopModuleToUse ) {
throw 'Neither the "PersistentMemory" module nor the "RemoteDesktop" module is available. Please check and use a desktop-edition module that is under the System32 module path.'
}
## The 'Desktop' edition module 'PersistentMemory' (available on Windows Client) or 'RemoteDesktop' (available on Windows Server) should not be imported twice.
$ConfigPath = Join-Path $TestDrive 'powershell.config.json'
@"
{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned", "WindowsPowerShellCompatibilityNoClobberModuleList": ["$desktopModuleToUse"]}
"@ | Out-File -Force $ConfigPath
2024-08-13 10:18:39 -07:00
$env:PSModulePath = $null
2022-01-12 10:02:03 -08:00
## The desktop-edition module is listed in the no-clobber list, so we will first try loading a core-edition
## compatible version of the module before loading the remote one. The 'system32' module path will be skipped
## in this attempt, which is by-design.
## If we don't skip the 'system32' module path in this loading attempt, the desktop-edition module will be
## imported twice as a remote module, and then 'Remove-Module' won't close the WinCompat session.
$script = @"
Import-Module $desktopModuleToUse -UseWindowsPowerShell -WarningAction Ignore
Get-Module $desktopModuleToUse | ForEach-Object { ` $_ .ModuleType.ToString() }
(Get-PSSession | Measure-Object).Count
Remove-Module $desktopModuleToUse
(Get-PSSession | Measure-Object).Count
"@
$scriptBlock = [ scriptblock ]:: Create ( $script )
$results = & $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c $scriptBlock
$results [ 0 ] | Should -BeExactly 'Script'
$results [ 1 ] | Should -BeExactly 1
$results [ 2 ] | Should -BeExactly 0
}
2020-04-16 16:57:55 -07:00
}
2020-03-09 23:31:44 -07:00
Context "Tests around PSModulePath in WinCompat process" {
BeforeAll {
$pwsh = " $PSHOME /pwsh"
Add-ModulePath $basePath
$ConfigPath = Join-Path $TestDrive 'powershell.config.json'
}
AfterAll {
Restore-ModulePath
}
2022-02-07 17:34:52 -08:00
BeforeEach {
Get-PSSession -Name WinPSCompatSession -ErrorAction SilentlyContinue | Remove-PSSession
}
2020-03-09 23:31:44 -07:00
AfterEach {
Get-Module $allModules | Remove-Module -Force
}
It 'WinCompat process does not inherit PowerShell-Core-specific paths' {
# these paths were copied from test\powershell\engine\Module\ModulePath.Tests.ps1
$pscoreUserPath = Join-Path -Path $HOME -ChildPath "Documents\PowerShell\Modules"
$pscoreSharedPath = Join-Path -Path $env:ProgramFiles -ChildPath "PowerShell\Modules"
$pscoreSystemPath = Join-Path -Path $PSHOME -ChildPath 'Modules'
$pscorePaths = $env:psmodulepath
$pscorePaths | Should -BeLike "* $pscoreUserPath *"
$pscorePaths | Should -BeLike "* $pscoreSharedPath *"
$pscorePaths | Should -BeLike "* $pscoreSystemPath *"
Import-Module $ModuleName2 -UseWindowsPowerShell -Force -WarningAction Ignore
$s = Get-PSSession -Name WinPSCompatSession
$winpsPaths = Invoke-Command -Session $s -ScriptBlock { $env:psmodulepath }
$winpsPaths | Should -Not -BeLike "* $pscoreUserPath *"
$winpsPaths | Should -Not -BeLike "* $pscoreSharedPath *"
$winpsPaths | Should -Not -BeLike "* $pscoreSystemPath *"
}
It 'WinCompat process inherits user added paths' {
$mypath = Join-Path $env:SystemDrive MyDir
$originalModulePath = $env:PSModulePath
try {
$env:PSModulePath += "; $mypath "
Import-Module $ModuleName2 -UseWindowsPowerShell -Force -WarningAction Ignore
$s = Get-PSSession -Name WinPSCompatSession
$winpsPaths = Invoke-Command -Session $s -ScriptBlock { $env:psmodulepath }
$winpsPaths | Should -BeLike "* $mypath *"
}
finally {
$env:PSModulePath = $originalModulePath
}
}
It 'Windows PowerShell does not inherit path defined in powershell.config.json' {
'{ "PSModulePath": "C:\\MyTestDir" }' | Out-File -Force $ConfigPath
$winpsPaths = & $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "Import-Module $ModuleName2 -UseWindowsPowerShell -WarningAction Ignore; `$ s = Get-PSSession -Name WinPSCompatSession;Invoke-Command -Session `$ s -ScriptBlock { `$ env:psmodulepath}"
$winpsPaths | Should -Not -BeLike "*MyTestDir*"
}
}
2019-12-09 15:20:40 -08:00
}
2018-07-16 16:58:40 -07:00
Describe "PSModulePath changes interacting with other PowerShell processes" -Tag "Feature" {
2020-01-03 10:32:00 -08:00
BeforeAll {
2020-01-05 12:17:41 +05:00
$pwsh = " $PSHOME /pwsh"
2020-01-03 10:32:00 -08:00
if ( ! $IsWindows ) {
2023-08-16 11:28:59 -07:00
Push-DefaultParameterValueStack @ { "it:skip" = $true }
return
2020-01-03 10:32:00 -08:00
}
}
AfterAll {
2023-08-16 11:28:59 -07:00
if ( ! $IsWindows ) {
Pop-DefaultParameterValueStack
}
2020-01-03 10:32:00 -08:00
}
2018-08-03 11:22:30 -07:00
Context "System32 module path prepended to PSModulePath" {
BeforeAll {
if ( -not $IsWindows )
{
return
}
Add-ModulePath ( Join-Path $env:windir "System32\WindowsPowerShell\v1.0\Modules" ) -Prepend
2018-07-16 16:58:40 -07:00
}
2018-08-03 11:22:30 -07:00
AfterAll {
if ( -not $IsWindows )
{
return
}
Restore-ModulePath
}
2020-01-13 19:35:33 +00:00
It "Allows Windows PowerShell subprocesses to call `$ PSHOME modules still" {
2018-08-03 11:22:30 -07:00
$errors = powershell . exe -Command "Get-ChildItem" 2 >& 1 | Where-Object { $_ -is [ System.Management.Automation.ErrorRecord ] }
$errors | Should -Be $null
}
2020-04-13 23:08:01 -07:00
It "Allows Windows PowerShell subprocesses to load WinPS version of `$ PSHOME modules" {
powershell . exe -Command "Get-ChildItem | Out-Null;(Get-Module Microsoft.PowerShell.Management).Path" | Should -BeLike "*system32*"
}
2019-05-10 15:34:27 -07:00
It "Allows PowerShell subprocesses to call core modules" {
2020-01-05 12:17:41 +05:00
$errors = & $pwsh -Command "Get-ChildItem" 2 >& 1 | Where-Object { $_ -is [ System.Management.Automation.ErrorRecord ] }
2018-08-03 11:22:30 -07:00
$errors | Should -Be $null
2018-07-16 16:58:40 -07:00
}
}
2020-02-04 16:33:26 -08:00
# Remove Pending status and update test after issue #11575 is fixed
2020-01-14 08:39:02 -08:00
It "Does not duplicate the System32 module path in subprocesses" -Pending: $true {
2020-01-05 12:17:41 +05:00
$sys32ModPathCount = & $pwsh -C {
2020-01-13 19:15:17 +00:00
& " $PSHOME /pwsh" -C '$null = $env:PSModulePath -match ([regex]::Escape((Join-Path $env:windir "System32" "WindowsPowerShell" "v1.0" "Modules"))); $Matches.Count'
2018-08-03 11:22:30 -07:00
}
2018-07-16 16:58:40 -07:00
2018-08-03 11:22:30 -07:00
$sys32ModPathCount | Should -Be 1
2018-07-16 16:58:40 -07:00
}
}
Describe "Get-Module nested module behaviour with Edition checking" -Tag "Feature" {
BeforeAll {
$testConditions = @ {
SkipEditionCheck = @ ( $true , $false )
UseRootModule = @ ( $true , $false )
UseAbsolutePath = @ ( $true , $false )
MarkedEdition = @ ( $null , "Desktop" , "Core" , @ ( "Desktop" , "Core" ))
}
# Combine all the test conditions into a list of test cases
$testCases = @ ( @ {})
foreach ( $condition in $testConditions . Keys )
{
$list = [ System.Collections.Generic.List[hashtable] ]:: new ()
foreach ( $obj in $testCases )
{
foreach ( $value in $testConditions [ $condition ])
{
$list . Add ( $obj + @ { $condition = $value })
}
}
$testCases = $list
}
# Define nested script module
$scriptModuleName = "NestedScriptModule"
$scriptModuleFile = " $scriptModuleName .psm1"
$scriptModuleContent = 'function Test-ScriptModule { return $true }'
# Define nested binary module
$binaryModuleName = "NestedBinaryModule"
$binaryModuleFile = " $binaryModuleName .dll"
$binaryModuleContent = 'public static class TestBinaryModuleClass { public static bool Test() { return true; } }'
$binaryModuleSourcePath = Join-Path $TestDrive $binaryModuleFile
Add-Type -OutputAssembly $binaryModuleSourcePath -TypeDefinition $binaryModuleContent
# Define root module definition
$rootModuleName = "RootModule"
$rootModuleFile = " $rootModuleName .psm1"
$rootModuleContent = 'function Test-RootModule { Test-ScriptModule }'
# Module directory structure: $TestDrive/$compatibility/$guid/$moduleName/{module parts}
$compatibleDir = "Compatible"
$incompatibleDir = "Incompatible"
$compatiblePath = Join-Path $TestDrive $compatibleDir
$incompatiblePath = Join-Path $TestDrive $incompatibleDir
foreach ( $basePath in $compatiblePath , $incompatiblePath )
{
New-Item -Path $basePath -ItemType Directory
}
}
Context "Modules ON the System32 test path" {
BeforeAll {
[ System.Management.Automation.Internal.InternalTestHooks ]:: SetTestHook ( "TestWindowsPowerShellPSHomeLocation" , $incompatiblePath )
}
AfterAll {
[ System.Management.Automation.Internal.InternalTestHooks ]:: SetTestHook ( "TestWindowsPowerShellPSHomeLocation" , $null )
}
BeforeEach {
# Create the module directory
$guid = New-Guid
$compatibilityDir = $incompatibleDir
$containingDir = Join-Path $TestDrive $compatibilityDir $guid
$moduleName = "CpseTestModule"
$moduleBase = Join-Path $containingDir $moduleName
New-Item -Path $moduleBase -ItemType Directory
Add-ModulePath $containingDir
}
AfterEach {
Restore-ModulePath
}
It "Get-Module -ListAvailable gets all compatible modules when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases -Skip: ( -not $IsWindows ) {
param ([ bool ] $SkipEditionCheck , [ bool ] $UseRootModule , [ bool ] $UseAbsolutePath , [ string[] ] $MarkedEdition )
New-TestNestedModule `
-ModuleBase $moduleBase `
-ScriptModuleFilename $scriptModuleFile `
-ScriptModuleContent $scriptModuleContent `
-BinaryModuleFilename $binaryModuleFile `
-BinaryModuleDllPath $binaryModuleSourcePath `
-RootModuleFilename $rootModuleFile `
-RootModuleContent $rootModuleContent `
-CompatiblePSEditions $MarkedEdition `
-UseRootModule $UseRootModule `
-UseAbsolutePath $UseAbsolutePath
if ( $UseAbsolutePath )
{
if (( -not $SkipEditionCheck ) -and ( -not ( $MarkedEdition -contains "Core" )))
{
Get-Module -ListAvailable $moduleBase -ErrorAction Stop | Should -Be $null
return
}
$modules = if ( $SkipEditionCheck )
{
Get-Module -ListAvailable $moduleBase -SkipEditionCheck
}
else
{
Get-Module -ListAvailable $moduleBase
}
$modules . Count | Should -Be 1
$modules [ 0 ]. Name | Should -Be $moduleName
return
}
$modules = if ( $SkipEditionCheck )
{
Get-Module -ListAvailable -SkipEditionCheck
}
else
{
Get-Module -ListAvailable
}
$modules = $modules | Where-Object { $_ . Path . Contains ( $guid ) }
if (( -not $SkipEditionCheck ) -and ( -not ( $MarkedEdition -contains "Core" )))
{
$modules . Count | Should -Be 0
return
}
$modules . Count | Should -Be 1
$modules [ 0 ]. Name | Should -Be $moduleName
}
It "Get-Module -ListAvailable -All gets all compatible modules when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases -Skip: ( -not $IsWindows ){
param ([ bool ] $SkipEditionCheck , [ bool ] $UseRootModule , [ bool ] $UseAbsolutePath , [ string[] ] $MarkedEdition )
New-TestNestedModule `
-ModuleBase $moduleBase `
-ScriptModuleFilename $scriptModuleFile `
-ScriptModuleContent $scriptModuleContent `
-BinaryModuleFilename $binaryModuleFile `
-BinaryModuleDllPath $binaryModuleSourcePath `
-RootModuleFilename $rootModuleFile `
-RootModuleContent $rootModuleContent `
-CompatiblePSEditions $MarkedEdition `
-UseRootModule $UseRootModule `
-UseAbsolutePath $UseAbsolutePath
# Modules specified with an absolute path should only return themselves
if ( $UseAbsolutePath ) {
$modules = if ( $SkipEditionCheck )
{
Get-Module -ListAvailable -All -SkipEditionCheck $moduleBase
}
else
{
Get-Module -ListAvailable -All $moduleBase
}
$modules . Count | Should -Be 1
$modules [ 0 ]. Name | Should -BeExactly $moduleName
return
}
$modules = if ( $SkipEditionCheck )
{
Get-Module -ListAvailable -All -SkipEditionCheck | Where-Object { $_ . Path . Contains ( $guid ) }
}
else
{
Get-Module -ListAvailable -All | Where-Object { $_ . Path . Contains ( $guid ) }
}
if ( $UseRootModule )
{
$modules . Count | Should -Be 4
}
else
{
$modules . Count | Should -Be 3
}
$names = $modules . Name
$names | Should -Contain $moduleName
$names | Should -Contain $scriptModuleName
$names | Should -Contain $binaryModuleName
}
}
Context "Modules OFF the System32 module path" {
BeforeEach {
# Create the module directory
$guid = New-Guid
$compatibilityDir = $compatibleDir
$containingDir = Join-Path $TestDrive $compatibilityDir $guid
$moduleName = "CpseTestModule"
$moduleBase = Join-Path $containingDir $moduleName
New-Item -Path $moduleBase -ItemType Directory
Add-ModulePath $containingDir
}
AfterEach {
Restore-ModulePath
}
It "Get-Module -ListAvailable gets all compatible modules when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases {
param ([ bool ] $SkipEditionCheck , [ bool ] $UseRootModule , [ bool ] $UseAbsolutePath , [ string[] ] $MarkedEdition )
New-TestNestedModule `
-ModuleBase $moduleBase `
-ScriptModuleFilename $scriptModuleFile `
-ScriptModuleContent $scriptModuleContent `
-BinaryModuleFilename $binaryModuleFile `
-BinaryModuleDllPath $binaryModuleSourcePath `
-RootModuleFilename $rootModuleFile `
-RootModuleContent $rootModuleContent `
-CompatiblePSEditions $MarkedEdition `
-UseRootModule $UseRootModule `
-UseAbsolutePath $UseAbsolutePath
if ( $UseAbsolutePath )
{
$modules = if ( $SkipEditionCheck )
{
Get-Module -ListAvailable $moduleBase -SkipEditionCheck
}
else
{
Get-Module -ListAvailable $moduleBase
}
$modules . Count | Should -Be 1
$modules [ 0 ]. Name | Should -Be $moduleName
return
}
$modules = if ( $SkipEditionCheck )
{
Get-Module -ListAvailable -SkipEditionCheck
}
else
{
Get-Module -ListAvailable
}
$modules = $modules | Where-Object { $_ . Path . Contains ( $guid ) }
$modules . Count | Should -Be 1
$modules [ 0 ]. Name | Should -Be $moduleName
}
It "Get-Module -ListAvailable -All gets all compatible modules when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases {
param ([ bool ] $SkipEditionCheck , [ bool ] $UseRootModule , [ bool ] $UseAbsolutePath , [ string[] ] $MarkedEdition )
New-TestNestedModule `
-ModuleBase $moduleBase `
-ScriptModuleFilename $scriptModuleFile `
-ScriptModuleContent $scriptModuleContent `
-BinaryModuleFilename $binaryModuleFile `
-BinaryModuleDllPath $binaryModuleSourcePath `
-RootModuleFilename $rootModuleFile `
-RootModuleContent $rootModuleContent `
-CompatiblePSEditions $MarkedEdition `
-UseRootModule $UseRootModule `
-UseAbsolutePath $UseAbsolutePath
# Modules specified with an absolute path should only return themselves
if ( $UseAbsolutePath )
{
$modules = Get-Module -ListAvailable -All $moduleBase
$modules . Count | Should -Be 1
$modules [ 0 ]. Name | Should -BeExactly $moduleName
return
}
$modules = if ( $SkipEditionCheck )
{
Get-Module -ListAvailable -All -SkipEditionCheck | Where-Object { $_ . Path . Contains ( $guid ) }
}
else
{
Get-Module -ListAvailable -All | Where-Object { $_ . Path . Contains ( $guid ) }
}
if ( $UseRootModule )
{
$modules . Count | Should -Be 4
}
else
{
$modules . Count | Should -Be 3
}
$names = $modules . Name
$names | Should -Contain $moduleName
$names | Should -Contain $scriptModuleName
$names | Should -Contain $binaryModuleName
}
}
}
Describe "Import-Module nested module behaviour with Edition checking" -Tag "Feature" {
BeforeAll {
$testConditions = @ {
SkipEditionCheck = @ ( $true , $false )
UseRootModule = @ ( $true , $false )
UseAbsolutePath = @ ( $true , $false )
MarkedEdition = @ ( $null , "Desktop" , "Core" , @ ( "Desktop" , "Core" ))
2019-11-18 10:44:55 -08:00
UseWindowsPowerShell = @ ( $true , $false )
2018-07-16 16:58:40 -07:00
}
# Combine all the test conditions into a list of test cases
$testCases = @ ( @ {})
foreach ( $condition in $testConditions . Keys )
{
$list = [ System.Collections.Generic.List[hashtable] ]:: new ()
foreach ( $obj in $testCases )
{
foreach ( $value in $testConditions [ $condition ])
{
$list . Add ( $obj + @ { $condition = $value })
}
}
$testCases = $list
}
# Define nested script module
$scriptModuleName = "NestedScriptModule"
$scriptModuleFile = " $scriptModuleName .psm1"
2019-11-18 10:44:55 -08:00
$scriptModuleContent = 'function Test-ScriptModule { return $true } function Test-ScriptModulePSEdition { $PSVersionTable.PSEdition }'
2018-07-16 16:58:40 -07:00
# Define root module definition
$rootModuleName = "RootModule"
$rootModuleFile = " $rootModuleName .psm1"
2019-11-18 10:44:55 -08:00
$rootModuleContent = 'function Test-RootModule { Test-ScriptModule } function Test-RootModulePSEdition { Test-ScriptModulePSEdition }'
2018-07-16 16:58:40 -07:00
# Module directory structure: $TestDrive/$compatibility/$guid/$moduleName/{module parts}
$compatibleDir = "Compatible"
$incompatibleDir = "Incompatible"
$compatiblePath = Join-Path $TestDrive $compatibleDir
$incompatiblePath = Join-Path $TestDrive $incompatibleDir
foreach ( $basePath in $compatiblePath , $incompatiblePath )
{
New-Item -Path $basePath -ItemType Directory
}
2019-11-18 10:44:55 -08:00
# make sure there are no ImplicitRemoting leftovers from previous tests
Get-Module | Where-Object { $_ . PrivateData . ImplicitRemoting } | Remove-Module -Force
Get-PSSession -Name WinPSCompatSession -ErrorAction SilentlyContinue | Remove-PSSession
2018-07-16 16:58:40 -07:00
}
Context "Modules ON the System32 test path" {
BeforeAll {
[ System.Management.Automation.Internal.InternalTestHooks ]:: SetTestHook ( "TestWindowsPowerShellPSHomeLocation" , $incompatiblePath )
}
AfterAll {
[ System.Management.Automation.Internal.InternalTestHooks ]:: SetTestHook ( "TestWindowsPowerShellPSHomeLocation" , $null )
}
BeforeEach {
# Create the module directory
$guid = New-Guid
$compatibilityDir = $incompatibleDir
$containingDir = Join-Path $TestDrive $compatibilityDir $guid
$moduleName = "CpseTestModule"
$moduleBase = Join-Path $containingDir $moduleName
New-Item -Path $moduleBase -ItemType Directory
Add-ModulePath $containingDir
}
AfterEach {
Get-Module $moduleName | Remove-Module -Force
Restore-ModulePath
}
2019-11-18 10:44:55 -08:00
It "Import-Module when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>, UseWindowsPowerShell: <UseWindowsPowerShell>" -TestCases $testCases -Skip: ( -not $IsWindows ) {
param ([ bool ] $SkipEditionCheck , [ bool ] $UseRootModule , [ bool ] $UseAbsolutePath , [ string[] ] $MarkedEdition , [ bool ] $UseWindowsPowerShell )
2018-07-16 16:58:40 -07:00
New-TestNestedModule `
-ModuleBase $moduleBase `
-ScriptModuleFilename $scriptModuleFile `
-ScriptModuleContent $scriptModuleContent `
-RootModuleFilename $rootModuleFile `
-RootModuleContent $rootModuleContent `
-CompatiblePSEditions $MarkedEdition `
-UseRootModule $UseRootModule `
-UseAbsolutePath $UseAbsolutePath
if ( $UseAbsolutePath )
{
if (( -not $SkipEditionCheck ) -and ( -not ( $MarkedEdition -contains "Core" )))
{
2019-11-18 10:44:55 -08:00
# this goes through WinCompat code
2020-01-11 15:41:59 +00:00
{ Import-Module $moduleBase -ErrorAction Stop } | Should -Not -Throw
Get-Module -Name $moduleName | Should -Not -BeNullOrEmpty
2018-07-16 16:58:40 -07:00
return
}
2019-11-18 10:44:55 -08:00
if ( $SkipEditionCheck -and $UseWindowsPowerShell )
{
{ Import-Module $moduleBase -SkipEditionCheck -UseWindowsPowerShell } | Should -Throw -ErrorId "AmbiguousParameterSet"
return
}
elseif ( $SkipEditionCheck )
2018-07-16 16:58:40 -07:00
{
Import-Module $moduleBase -SkipEditionCheck
}
2019-11-18 10:44:55 -08:00
elseif ( $UseWindowsPowerShell )
{
Import-Module $moduleBase -UseWindowsPowerShell
}
2018-07-16 16:58:40 -07:00
else
{
Import-Module $moduleBase
}
if ( $UseRootModule )
{
Test-RootModule | Should -BeTrue
{ Test-ScriptModule } | Should -Throw -ErrorId "CommandNotFoundException"
2019-11-18 10:44:55 -08:00
if ( $UseWindowsPowerShell )
{
Test-RootModulePSEdition | Should -Be 'Desktop'
{ Test-ScriptModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException"
}
2018-07-16 16:58:40 -07:00
return
}
Test-ScriptModule | Should -BeTrue
{ Test-RootModule } | Should -Throw -ErrorId "CommandNotFoundException"
2019-11-18 10:44:55 -08:00
if ( $UseWindowsPowerShell )
{
Test-ScriptModulePSEdition | Should -Be 'Desktop'
{ Test-RootModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException"
}
2018-07-16 16:58:40 -07:00
return
}
if (( -not $SkipEditionCheck ) -and ( -not ( $MarkedEdition -contains "Core" )))
{
2019-11-18 10:44:55 -08:00
# this goes through WinCompat code
2020-01-11 15:41:59 +00:00
{ Import-Module $moduleName -ErrorAction Stop } | Should -Not -Throw
Get-Module -Name $moduleName | Should -Not -BeNullOrEmpty
2018-07-16 16:58:40 -07:00
return
}
2019-11-18 10:44:55 -08:00
if ( $SkipEditionCheck -and $UseWindowsPowerShell )
{
{ Import-Module $moduleName -SkipEditionCheck -UseWindowsPowerShell } | Should -Throw -ErrorId "AmbiguousParameterSet"
return
}
elseif ( $SkipEditionCheck )
2018-07-16 16:58:40 -07:00
{
Import-Module $moduleName -SkipEditionCheck
}
2019-11-18 10:44:55 -08:00
elseif ( $UseWindowsPowerShell )
{
Import-Module $moduleName -UseWindowsPowerShell
}
2018-07-16 16:58:40 -07:00
else
{
Import-Module $moduleName
}
if ( $UseRootModule )
{
Test-RootModule | Should -BeTrue
{ Test-ScriptModule } | Should -Throw -ErrorId "CommandNotFoundException"
2019-11-18 10:44:55 -08:00
if ( $UseWindowsPowerShell )
{
Test-RootModulePSEdition | Should -Be 'Desktop'
{ Test-ScriptModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException"
}
2018-07-16 16:58:40 -07:00
return
}
Test-ScriptModule | Should -BeTrue
{ Test-RootModule } | Should -Throw -ErrorId "CommandNotFoundException"
2019-11-18 10:44:55 -08:00
if ( $UseWindowsPowerShell )
{
Test-ScriptModulePSEdition | Should -Be 'Desktop'
{ Test-RootModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException"
}
2018-07-16 16:58:40 -07:00
}
}
Context "Modules OFF the System32 module path" {
BeforeEach {
# Create the module directory
$guid = New-Guid
$compatibilityDir = $compatibleDir
$containingDir = Join-Path $TestDrive $compatibilityDir $guid
$moduleName = "CpseTestModule"
$moduleBase = Join-Path $containingDir $moduleName
New-Item -Path $moduleBase -ItemType Directory
Add-ModulePath $containingDir
}
AfterEach {
Get-Module $moduleName | Remove-Module -Force
Restore-ModulePath
}
2019-11-18 10:44:55 -08:00
It "Import-Module when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>, UseWindowsPowerShell: <UseWindowsPowerShell>" -TestCases $testCases {
param ([ bool ] $SkipEditionCheck , [ bool ] $UseRootModule , [ bool ] $UseAbsolutePath , [ string[] ] $MarkedEdition , [ bool ] $UseWindowsPowerShell )
if ( $UseWindowsPowerShell -and ( -not $IsWindows ))
{
Set-ItResult -Skipped -Because 'UseWindowsPowerShell parameter is supported only on Windows'
}
2018-07-16 16:58:40 -07:00
New-TestNestedModule `
-ModuleBase $moduleBase `
-ScriptModuleFilename $scriptModuleFile `
-ScriptModuleContent $scriptModuleContent `
-RootModuleFilename $rootModuleFile `
-RootModuleContent $rootModuleContent `
-CompatiblePSEditions $MarkedEdition `
-UseRootModule $UseRootModule `
-UseAbsolutePath $UseAbsolutePath
if ( $UseAbsolutePath )
{
2019-11-18 10:44:55 -08:00
if ( $SkipEditionCheck -and $UseWindowsPowerShell )
{
{ Import-Module $moduleBase -SkipEditionCheck -UseWindowsPowerShell } | Should -Throw -ErrorId "AmbiguousParameterSet"
return
}
elseif ( $SkipEditionCheck )
2018-07-16 16:58:40 -07:00
{
Import-Module $moduleBase -SkipEditionCheck
}
2019-11-18 10:44:55 -08:00
elseif ( $UseWindowsPowerShell )
{
Import-Module $moduleBase -UseWindowsPowerShell
}
2018-07-16 16:58:40 -07:00
else
{
Import-Module $moduleBase
}
}
2019-11-18 10:44:55 -08:00
elseif ( $SkipEditionCheck -and $UseWindowsPowerShell )
{
{ Import-Module $moduleName -SkipEditionCheck -UseWindowsPowerShell } | Should -Throw -ErrorId "AmbiguousParameterSet"
return
}
2018-07-16 16:58:40 -07:00
elseif ( $SkipEditionCheck )
{
Import-Module $moduleName -SkipEditionCheck
}
2019-11-18 10:44:55 -08:00
elseif ( $UseWindowsPowerShell )
{
Import-Module $moduleName -UseWindowsPowerShell
}
2018-07-16 16:58:40 -07:00
else
{
Import-Module $moduleName
}
if ( $UseRootModule )
{
Test-RootModule | Should -BeTrue
{ Test-ScriptModule } | Should -Throw -ErrorId "CommandNotFoundException"
2019-11-18 10:44:55 -08:00
if ( $UseWindowsPowerShell )
{
Test-RootModulePSEdition | Should -Be 'Desktop'
{ Test-ScriptModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException"
}
2018-07-16 16:58:40 -07:00
return
}
Test-ScriptModule | Should -BeTrue
{ Test-RootModule } | Should -Throw -ErrorId "CommandNotFoundException"
2019-11-18 10:44:55 -08:00
if ( $UseWindowsPowerShell )
{
Test-ScriptModulePSEdition | Should -Be 'Desktop'
{ Test-RootModulePSEdition } | Should -Throw -ErrorId "CommandNotFoundException"
}
2018-07-16 16:58:40 -07:00
}
}
}
2022-01-12 10:02:03 -08:00
Describe "WinCompat importing should check availablity of built-in modules" -Tag "CI" {
BeforeAll {
if ( -not $IsWindows ) {
2023-08-16 11:28:59 -07:00
Push-DefaultParameterValueStack @ { "it:skip" = $true }
2022-01-12 10:02:03 -08:00
return
}
## Copy the current PowerShell instance to a temp location
$tempDir = Join-Path ([ System.IO.Path ]:: GetTempPath ()) "WinCompat"
$pwshDir = Join-Path $tempDir "pwsh"
$moduleDir = Join-Path $tempDir "Modules"
$savedModulePath = $env:PSModulePath
if ( Test-Path $tempDir ) {
Remove-Item $tempDir -Recurse -Force
}
Write-Host "Making a copy of the running PowerShell instance ..." -ForegroundColor Yellow
Copy-Item $PSHOME $pwshDir -Recurse -Force
Move-Item $pwshDir \ Modules $moduleDir -Force
Write-Host "-- Done copying!" -ForegroundColor Yellow
}
AfterAll {
if ( -not $IsWindows ) {
2023-08-16 11:28:59 -07:00
Pop-DefaultParameterValueStack
2022-01-12 10:02:03 -08:00
return
}
$env:PSModulePath = $savedModulePath
Remove-Item $tempDir -Recurse -Force
}
It "Missing built-in modules will trigger error instead of loading the non-compatible ones in System32 directory. Running '<Command>'" -TestCases @ (
@ {
Command = 'Start-Transcript' ;
FullyQualifiedErrorId = "CouldNotAutoloadMatchingModule" ;
ExceptionMessage = "*'Start-Transcript'*'Microsoft.PowerShell.Host'*'Microsoft.PowerShell.Host'*'Core'* `$ PSHOME*'Import-Module Microsoft.PowerShell.Host'*"
}
@ {
Command = 'Import-Module Microsoft.PowerShell.Host' ;
FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand"
ExceptionMessage = "*'Microsoft.PowerShell.Host'*'Core'* `$ PSHOME*"
}
@ {
Command = 'Import-Module CimCmdlets'
FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand"
ExceptionMessage = "*'CimCmdlets'*'Core'* `$ PSHOME*"
}
@ {
Command = 'Import-Module Microsoft.PowerShell.Utility'
FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand"
ExceptionMessage = "*'Microsoft.PowerShell.Utility'*'Core'* `$ PSHOME*"
}
) {
param (
$Command ,
$FullyQualifiedErrorId ,
$ExceptionMessage
)
$template = @'
try {{
{0}
}} catch {{
$_.FullyQualifiedErrorId
$_.Exception.Message
}}
'@
2024-08-13 10:18:39 -07:00
$env:PSModulePath = $null
2022-01-12 10:02:03 -08:00
$script = $template -f $Command
$scriptBlock = [ scriptblock ]:: Create ( $script )
$result = & " $pwshDir \pwsh.exe" -NoProfile -NonInteractive -c $scriptBlock
$result | Should -HaveCount 2
$result [ 0 ] | Should -BeExactly $FullyQualifiedErrorId
$result [ 1 ] | Should -BeLike $ExceptionMessage
}
It "Attempt to load a 'Desktop' edition module should fail because 'Export-PSSession' cannot be found" {
2023-08-16 11:28:59 -07:00
if ( Test-IsWinWow64 ) {
Set-ItResult -Skipped -Because "This test is not applicable to WoW64."
return
}
2022-01-12 10:02:03 -08:00
if ( -not $desktopModuleToUse ) {
throw 'Neither the "PersistentMemory" module nor the "RemoteDesktop" module is available. Please check and use a desktop-edition module that is under the System32 module path.'
}
$script = @"
try {
Import-Module $desktopModuleToUse -ErrorAction Stop
} catch {
` $_ .FullyQualifiedErrorId
` $_ .Exception.Message
}
"@
2024-08-13 10:18:39 -07:00
$env:PSModulePath = $null
2022-01-12 10:02:03 -08:00
$scriptBlock = [ scriptblock ]:: Create ( $script )
$result = & " $pwshDir \pwsh.exe" -NoProfile -NonInteractive -c $scriptBlock
$result | Should -HaveCount 2
$result [ 0 ] | Should -BeExactly "CommandNotFoundException,Microsoft.PowerShell.Commands.ImportModuleCommand"
$result [ 1 ] | Should -BeLike "*' $desktopModuleToUse '*'Export-PSSession'*'Microsoft.PowerShell.Utility'*"
}
It "When built-in modules are available but not in `$ PSHOME module path, things should work" {
2024-08-13 10:18:39 -07:00
$env:PSModulePath = $null
2022-01-12 10:02:03 -08:00
$result = & " $pwshDir \pwsh.exe" -NoProfile -NonInteractive -c @"
` $env:PSModulePath += '; $moduleDir '
Import-Module Microsoft.PowerShell.Utility -UseWindowsPowerShell -WarningAction Ignore
Get-Module Microsoft.PowerShell.Utility | ForEach-Object ModuleType
Get-Module Microsoft.PowerShell.Utility | Where-Object ModuleType -eq 'Manifest' | ForEach-Object Path
Get-Module Microsoft.PowerShell.Utility | Where-Object ModuleType -eq 'Script' | ForEach-Object { ` $_ .ExportedCommands.Keys }
"@
$result | Should -HaveCount 6
$result [ 0 ] | Should -BeExactly 'Manifest'
$result [ 1 ] | Should -BeExactly 'Script'
$result [ 2 ] | Should -BeExactly " $moduleDir \Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1"
$result [ 3 ] | Should -BeExactly 'Convert-String'
$result [ 4 ] | Should -BeExactly 'ConvertFrom-String'
$result [ 5 ] | Should -BeExactly 'CFS'
}
2023-09-26 16:07:00 -07:00
It 'ErrorAction should be used for cmdlet' {
try {
$out = Invoke-Expression 'get-AppLockerFileInformation NoSuch.exe -ErrorAction Stop; "after"'
}
catch {
# do nothing as we expect an error, but execution should not continue
}
$out | Should -Not -Contain 'after'
}
2022-01-12 10:02:03 -08:00
}