mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
[Breaking Change] When a binary module has the module assembly in GAC, we load the assembly from GAC before trying to load it from module base path. This change attempts to load it from module base path before looking up in GAC.
332 lines
15 KiB
PowerShell
332 lines
15 KiB
PowerShell
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
# Licensed under the MIT License.
|
|
Describe "Import-Module" -Tags "CI" {
|
|
$moduleName = "Microsoft.PowerShell.Security"
|
|
BeforeAll {
|
|
$originalPSModulePath = $env:PSModulePath
|
|
New-Item -ItemType Directory -Path "$testdrive\Modules\TestModule\1.1" -Force > $null
|
|
New-Item -ItemType Directory -Path "$testdrive\Modules\TestModule\2.0" -Force > $null
|
|
$env:PSModulePath += [System.IO.Path]::PathSeparator + "$testdrive\Modules"
|
|
New-ModuleManifest -Path "$testdrive\Modules\TestModule\1.1\TestModule.psd1" -ModuleVersion 1.1
|
|
New-ModuleManifest -Path "$testdrive\Modules\TestModule\2.0\TestModule.psd1" -ModuleVersion 2.0
|
|
}
|
|
|
|
AfterAll {
|
|
$env:PSModulePath = $originalPSModulePath
|
|
}
|
|
|
|
BeforeEach {
|
|
Remove-Module -Name $moduleName -Force
|
|
(Get-Module -Name $moduleName).Name | Should -BeNullOrEmpty
|
|
Remove-Module -Name TestModule -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
AfterEach {
|
|
Import-Module -Name $moduleName -Force
|
|
(Get-Module -Name $moduleName).Name | Should -BeExactly $moduleName
|
|
}
|
|
|
|
It "should be able to add a module with using Name switch" {
|
|
{ Import-Module -Name $moduleName } | Should -Not -Throw
|
|
(Get-Module -Name $moduleName).Name | Should -BeExactly $moduleName
|
|
}
|
|
|
|
It "should be able to load a module with a trailing directory separator: <modulePath>" -TestCases @(
|
|
@{ modulePath = (Get-Module -ListAvailable $moduleName).ModuleBase + [System.IO.Path]::DirectorySeparatorChar; expectedName = $moduleName },
|
|
@{ modulePath = Join-Path -Path $TestDrive -ChildPath "\Modules\TestModule\"; expectedName = "TestModule" }
|
|
) {
|
|
param( $modulePath, $expectedName )
|
|
{ Import-Module -Name $modulePath -ErrorAction Stop } | Should -Not -Throw
|
|
(Get-Module -Name $expectedName).Name | Should -BeExactly $expectedName
|
|
}
|
|
|
|
It "should be able to add a module with using ModuleInfo switch" {
|
|
$a = Get-Module -ListAvailable $moduleName
|
|
{ Import-Module -ModuleInfo $a } | Should -Not -Throw
|
|
(Get-Module -Name $moduleName).Name | Should -BeExactly $moduleName
|
|
}
|
|
|
|
It "should be able to load an already loaded module" {
|
|
Import-Module $moduleName
|
|
{ $script:module = Import-Module $moduleName -PassThru -ErrorAction Stop } | Should -Not -Throw
|
|
Get-Module -Name $moduleName | Should -BeExactly $script:module
|
|
}
|
|
|
|
It "should only load the specified version" {
|
|
Import-Module TestModule -RequiredVersion 1.1
|
|
(Get-Module TestModule).Version | Should -BeIn "1.1"
|
|
}
|
|
}
|
|
|
|
Describe "Import-Module with ScriptsToProcess" -Tags "CI" {
|
|
|
|
BeforeAll {
|
|
$moduleRootPath = Join-Path $TestDrive 'TestModules'
|
|
New-Item $moduleRootPath -ItemType Directory -Force | Out-Null
|
|
Push-Location $moduleRootPath
|
|
|
|
"1 | Out-File out.txt -Append -NoNewline" | Out-File script1.ps1
|
|
"2 | Out-File out.txt -Append -NoNewline" | Out-File script2.ps1
|
|
New-ModuleManifest module1.psd1 -ScriptsToProcess script1.ps1
|
|
New-ModuleManifest module2.psd1 -ScriptsToProcess script2.ps1 -NestedModules module1.psd1
|
|
}
|
|
|
|
AfterAll {
|
|
Pop-Location
|
|
}
|
|
|
|
BeforeEach {
|
|
New-Item out.txt -ItemType File -Force | Out-Null
|
|
}
|
|
|
|
AfterEach {
|
|
$m = @('module1','module2','script1','script2')
|
|
remove-module $m -Force -ErrorAction SilentlyContinue
|
|
Remove-Item out.txt -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
$testCases = @(
|
|
@{ TestNameSuffix = 'for top-level module'; ipmoParms = @{'Name'='.\module1.psd1'}; Expected = '1' }
|
|
@{ TestNameSuffix = 'for top-level and nested module'; ipmoParms = @{'Name'='.\module2.psd1'}; Expected = '21' }
|
|
@{ TestNameSuffix = 'for top-level module when -Version is specified'; ipmoParms = @{'Name'='.\module1.psd1'; 'Version'='0.0.1'}; Expected = '1' }
|
|
@{ TestNameSuffix = 'for top-level and nested module when -Version is specified'; ipmoParms = @{'Name'='.\module2.psd1'; 'Version'='0.0.1'}; Expected = '21' }
|
|
)
|
|
|
|
It "Verify ScriptsToProcess are executed <TestNameSuffix>" -TestCases $testCases {
|
|
param($TestNameSuffix,$ipmoParms,$Expected)
|
|
Import-Module @ipmoParms
|
|
Get-Content out.txt | Should -BeExactly $Expected
|
|
}
|
|
}
|
|
|
|
Describe "Import-Module for Binary Modules in GAC" -Tags 'Feature' {
|
|
Context "Modules are not loaded from GAC" {
|
|
It "Load PSScheduledJob from Windows Powershell Modules folder should fail" -Skip:(-not $IsWindows) {
|
|
# NOTE:
|
|
# This test attempts to load an assembly from the GAC and expects to fail.
|
|
# However, if the assembly is already loaded, the assembly will be resolved before
|
|
# the DisableGACLoading flag comes into effect, meaning no error is thrown and the test fails.
|
|
#
|
|
# Since the System32 module direction is now on the module path and Get-Module -ListAvailable
|
|
# loads assemblies, the assembly is resolved and this test will fail if any other tests before
|
|
# it call Get-Module -ListAvailable.
|
|
#
|
|
# So for now, the solution is to run this test in a fresh process.
|
|
# Since disabling GAC loading is supposed to be a startup option, this is possibly appropriate.
|
|
|
|
$testScript = {
|
|
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('DisableGACLoading', $true)
|
|
$modulePath = Join-Path $env:windir "System32/WindowsPowershell/v1.0/Modules/PSScheduledJob"
|
|
Import-Module $modulePath -SkipEditionCheck
|
|
}
|
|
$err = (& "$PSHOME\pwsh.exe" -c $testScript 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] })[0]
|
|
$err.FullyQualifiedErrorId | Should -Be "FormatXmlUpdateException,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
|
}
|
|
}
|
|
|
|
Context "Modules are loaded from GAC" {
|
|
It "Load PSScheduledJob from Windows Powershell Modules folder" -Skip:(-not $IsWindows) {
|
|
$modulePath = Join-Path $env:windir "System32/WindowsPowershell/v1.0/Modules/PSScheduledJob"
|
|
Import-Module $modulePath -SkipEditionCheck
|
|
(Get-Command New-JobTrigger).Name | Should -BeExactly 'New-JobTrigger'
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe "Import-Module for Binary Modules" -Tags 'CI' {
|
|
|
|
It "PS should try to load the assembly from file path first" {
|
|
$src = @"
|
|
using System.Management.Automation; // Windows PowerShell namespace.
|
|
|
|
namespace ModuleCmdlets
|
|
{
|
|
[Cmdlet(VerbsDiagnostic.Test,"BinaryModuleCmdlet1")]
|
|
public class TestBinaryModuleCmdlet1Command : Cmdlet
|
|
{
|
|
protected override void BeginProcessing()
|
|
{
|
|
WriteObject("BinaryModuleCmdlet1 exported by the ModuleCmdlets module.");
|
|
}
|
|
}
|
|
}
|
|
"@
|
|
|
|
Add-Type -TypeDefinition $src -OutputAssembly $TESTDRIVE\System.dll
|
|
$results = pwsh -noprofile -c "`$module = Import-Module $TESTDRIVE\System.dll -Passthru; `$module.ImplementingAssembly.Location; Test-BinaryModuleCmdlet1"
|
|
|
|
#Ignore slash format difference under windows/Unix
|
|
$path = (Get-ChildItem $TESTDRIVE\System.dll).FullName
|
|
$results[0] | Should -BeExactly $path
|
|
$results[1] | Should -BeExactly "BinaryModuleCmdlet1 exported by the ModuleCmdlets module."
|
|
}
|
|
|
|
It "PS should try to load the assembly from assembly name if file path doesn't exist" {
|
|
|
|
$psdFile = Join-Path $TESTDRIVE test.psd1
|
|
$nestedModule = Join-Path NOExistedPath Microsoft.PowerShell.Commands.Utility.dll
|
|
New-ModuleManifest -Path $psdFile -NestedModules $nestedModule
|
|
try
|
|
{
|
|
$module = Import-Module $psdFile -PassThru
|
|
$module.NestedModules | Should -Not -BeNullOrEmpty
|
|
$assemblyLocation = [Microsoft.PowerShell.Commands.AddTypeCommand].Assembly.Location
|
|
$module.NestedModules.ImplementingAssembly.Location | Should -Be $assemblyLocation
|
|
}
|
|
finally
|
|
{
|
|
Remove-Module $module -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
}
|
|
|
|
It 'Should load from ModuleBase path before looking up in GAC' -Skip:(-not $IsWindows) {
|
|
$module = Get-Module PSScheduledJob -ListAvailable -SkipEditionCheck
|
|
$moduleBasePath = Split-Path $module.Path
|
|
$destPath = New-Item -ItemType Directory -Path "$TestDrive/PSScheduledJob"
|
|
$gacAssemblyPath = (Get-ChildItem "${env:WinDir}\Microsoft.NET\assembly\GAC_MSIL\Microsoft.PowerShell.ScheduledJob" -Recurse -Filter 'Microsoft.PowerShell.ScheduledJob.dll').FullName
|
|
|
|
# Copy format, type and psd1
|
|
Copy-Item "$moduleBasePath/PSScheduledJob*.*" -Destination $destPath -Force
|
|
|
|
# Copy assembly to temp module folder"
|
|
Copy-Item $gacAssemblyPath -Destination $destPath -Force
|
|
|
|
# Use a different pwsh so that we do not have the PSScheduledJob module already loaded.
|
|
$loadedAssemblyLocation = pwsh -noprofile -c "Import-Module $destPath -Force; [Microsoft.PowerShell.ScheduledJob.AddJobTriggerCommand].Assembly.Location"
|
|
$loadedAssemblyLocation | Should -BeLike "$TestDrive*\Microsoft.PowerShell.ScheduledJob.dll"
|
|
}
|
|
}
|
|
|
|
Describe "Import-Module should be case insensitive" -Tags 'CI' {
|
|
BeforeAll {
|
|
$defaultPSModuleAutoloadingPreference = $PSModuleAutoloadingPreference
|
|
$originalPSModulePath = $env:PSModulePath.Clone()
|
|
$modulesPath = "$TestDrive\Modules"
|
|
$env:PSModulePath += [System.IO.Path]::PathSeparator + $modulesPath
|
|
$PSModuleAutoloadingPreference = "none"
|
|
}
|
|
|
|
AfterAll {
|
|
$global:PSModuleAutoloadingPreference = $defaultPSModuleAutoloadingPreference
|
|
$env:PSModulePath = $originalPSModulePath
|
|
}
|
|
|
|
AfterEach {
|
|
Remove-Item -Recurse -Path $modulesPath -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
It "Import-Module can import a module using different casing using '<modulePath>' and manifest:<manifest>" -TestCases @(
|
|
@{modulePath="TESTMODULE/1.1"; manifest=$true},
|
|
@{modulePath="TESTMODULE" ; manifest=$true},
|
|
@{modulePath="TESTMODULE" ; manifest=$false}
|
|
) {
|
|
param ($modulePath, $manifest)
|
|
New-Item -ItemType Directory -Path "$modulesPath/$modulePath" -Force > $null
|
|
if ($manifest) {
|
|
New-ModuleManifest -Path "$modulesPath/$modulePath/TESTMODULE.psd1" -RootModule "TESTMODULE.psm1" -ModuleVersion 1.1
|
|
}
|
|
Set-Content -Path "$modulesPath/$modulePath/TESTMODULE.psm1" -Value "function mytest { 'hello' }"
|
|
Import-Module testMODULE
|
|
$m = Get-Module TESTmodule
|
|
$m | Should -BeOfType "System.Management.Automation.PSModuleInfo"
|
|
$m.Name | Should -BeIn "TESTMODULE"
|
|
mytest | Should -BeExactly "hello"
|
|
Remove-Module TestModule
|
|
Get-Module tESTmODULE | Should -BeNullOrEmpty
|
|
}
|
|
}
|
|
|
|
Describe "Workflow .Xaml module is not supported in PSCore" -tags "Feature" {
|
|
BeforeAll {
|
|
$xamlFile = Join-Path $TestDrive "XamlTest.xaml"
|
|
New-Item -Path $xamlFile -ItemType File -Force
|
|
|
|
$xamlRootModule = Join-Path $TestDrive "XamlRootModule"
|
|
New-Item -Path $xamlRootModule -ItemType Directory -Force
|
|
Copy-Item $xamlFile $xamlRootModule
|
|
$xamlRootModuleManifest = Join-Path $xamlRootModule "XamlRootModule.psd1"
|
|
New-ModuleManifest -Path $xamlRootModuleManifest -RootModule "XamlTest.xaml"
|
|
|
|
$xamlNestedModule = Join-Path $TestDrive "XamlNestedModule"
|
|
New-Item -Path $xamlNestedModule -ItemType Directory -Force
|
|
Copy-Item $xamlFile $xamlNestedModule
|
|
$xamlNestedModuleManifest = Join-Path $xamlNestedModule "XamlNestedModule.psd1"
|
|
New-ModuleManifest -Path $xamlNestedModuleManifest -NestedModules "XamlTest.xaml"
|
|
}
|
|
|
|
It "Import a XAML file directly should raise a 'NotSupported' error" {
|
|
{ Import-Module $xamlFile -ErrorAction Stop } | Should -Throw -ErrorId "Modules_WorkflowModuleNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
|
}
|
|
|
|
It "Import a module with XAML root module should raise a 'NotSupportd' error" {
|
|
{ Import-Module $xamlRootModule -ErrorAction Stop } | Should -Throw -ErrorId "Modules_WorkflowModuleNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
|
}
|
|
|
|
It "Import a module with XAML nested module should raise a 'NotSupported' error" {
|
|
{ Import-Module $xamlNestedModule -ErrorAction Stop } | Should -Throw -ErrorId "Modules_WorkflowModuleNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
|
}
|
|
}
|
|
|
|
Describe "Circular nested module test" -tags "CI" {
|
|
BeforeAll {
|
|
$moduleFolder = Join-Path $TestDrive CircularNestedModuleTest
|
|
$psdPath = Join-Path $moduleFolder CircularNestedModuleTest.psd1
|
|
$psmPath = Join-Path $moduleFolder CircularNestedModuleTest.psm1
|
|
|
|
New-Item -Path $moduleFolder -ItemType Directory -Force > $null
|
|
Set-Content -Path $psdPath -Value "@{ ModuleVersion = '0.0.1'; RootModule = 'CircularNestedModuleTest'; NestedModules = @('CircularNestedModuleTest') }" -Encoding Ascii
|
|
Set-Content -Path $psmPath -Value "function bar {}" -Encoding Ascii
|
|
}
|
|
|
|
AfterAll {
|
|
Remove-Module -Name CircularNestedModuleTest -Force -ErrorAction SilentlyContinue
|
|
Remove-Item -Path $moduleFolder -Force -Recurse
|
|
}
|
|
|
|
It "Loading the module should succeed and return a module with circular nested module" {
|
|
$m = Import-Module $psdPath -PassThru
|
|
$m.NestedModules[0] | Should -Be $m
|
|
}
|
|
}
|
|
|
|
Describe "Import-Module -Force behaviour" -Tag "CI" {
|
|
BeforeAll {
|
|
$moduleContent = 'function Test-One { return 101 }'
|
|
$newModuleContent = "function Test-One { return 93 }`nfunction Test-Two { return 14 }"
|
|
$modulePath = Join-Path $TestDrive 'ipmofTestModule.psm1'
|
|
}
|
|
|
|
AfterEach {
|
|
Get-Module 'ipmofTestModule' | Remove-Module
|
|
}
|
|
|
|
It "Loads new functions when Import-Module is called with -Force" {
|
|
Set-Content -Path $modulePath -Value $moduleContent
|
|
Import-Module $modulePath
|
|
|
|
Test-One | Should -Be 101
|
|
{ Test-Two } | Should -Throw -ErrorId 'CommandNotFoundException'
|
|
|
|
Set-Content -Path $modulePath -Value $newModuleContent -Force
|
|
Import-Module -Force $modulePath
|
|
|
|
Test-One | Should -Be 93
|
|
Test-Two | Should -Be 14
|
|
}
|
|
|
|
It "Does not load new functions when Import-Module is called without -Force" {
|
|
Set-Content -Path $modulePath -Value $moduleContent
|
|
Import-Module $modulePath
|
|
|
|
Test-One | Should -Be 101
|
|
{ Test-Two } | Should -Throw -ErrorId 'CommandNotFoundException'
|
|
|
|
Set-Content -Path $modulePath -Value $newModuleContent -Force
|
|
Import-Module $modulePath
|
|
|
|
Test-One | Should -Be 101
|
|
{ Test-Two } | Should -Throw -ErrorId 'CommandNotFoundException'
|
|
}
|
|
}
|