Search code examples
powershellmanifestpowershell-module

How to prevent loading of .psd1 RequiredModules when Import-Module fails?


When importing a module fails, it will still load the RequiredModules defined in the .psd1 manifest.


Consider the following module files:


MyModule.psd1
@{
    RootModule = 'MyModule.psm1'
    ModuleVersion = '1.0.0.0'
    GUID = '5b058fa7-5248-4a9a-b0f3-75ba6e3a123c'
    Author = 'Author'
    PowerShellVersion = '5.1'
    RequiredModules = @('NetConnection')
    ScriptsToProcess = @('RunFirst.ps1')
    PrivateData = @{ PSData = @{} }
}

RunFirst.ps1
#Requires -RunAsAdministrator

# <Perform misc checks, initialize environment>



Before importing MyModule:
PS C:\MyModule> Get-Module
ModuleType Version    Name
---------- -------    ----
Manifest   3.1.0.0    Microsoft.PowerShell.Management
Manifest   3.1.0.0    Microsoft.PowerShell.Utility
Script     2.3.5      PSReadline

Failing to load the module from an unelevated console:
PS C:\MyModule> Import-Module .\MyModule.psd1

Import-Module : The script 'RunFirst.ps1' cannot be run because it contains a "#requires"
statement for running as Administrator. The current Windows PowerShell session is not running
as Administrator. Start Windows PowerShell by  using the Run as Administrator option, and then
try running the script again.

After Import-Module failure:
PS C:\MyModule> Get-Module
ModuleType Version    Name
---------- -------    ----
Manifest   3.1.0.0    Microsoft.PowerShell.Management
Manifest   3.1.0.0    Microsoft.PowerShell.Utility
Manifest   2.0.0.0    NetConnection  <-----  How to prevent this from loading?
Script     2.3.5      PSReadline


Questions

  • Is there a dependable way to prevent the RequiredModules from loading (or programmatically unload said modules) if/when Import-Module fails?
  • As a side question:
    • Since the #Requires statement applies to scripts, is RunFirst.ps1 the most appropriate place for #Requires -RunAsAdministrator in a module?
    • NOTE: This isn't an X/Y problem, I simply used #Requires -RunAsAdministrator in order to showcase an Import-Module failure. This is just one of several circumstances that can cause Import-Module to fail, and the variety of different circumstances could very well impact which approaches are deemed better than others depending on the nature of the failure.

Solution

  • While what you're trying to achieve is understandable, in my estimation it is impossible to implement:

    • Of necessity, when a module is imported, all prerequisites must be met first, which requires processing the relevant module-manifest entries, RequiredModules, NestedModules, RequiredAssemblies and (debatably) ScriptsToProcess.

    • While it is hypothetically possible to undo loading of dependent script modules (those that are implemented exclusively via PowerShell code), by removing them in case of importation failure (using Remove-Module),[1] it is fundamentally impossible to reliably undo the effects of the RequiredAssemblies and ScriptsToProcess entries: in the former case, the problem is that .NET doesn't allow unloading of assemblies once loaded, and, in the latter case, the effects of what the script does are unknowable.


    Since the #Requires statement applies to scripts, is RunFirst.ps1 the most appropriate place for #Requires -RunAsAdministrator in a module?

    #requires statements equally work in *.psm1 files (script modules), so there's no need for a separate *.ps1 script file referenced in a ScriptsToProcess module-manifest entry.


    [1] Even then, it is possible - if ill-advised - for such modules to have modified the caller's state, which cannot be anticipated and therefore cannot be reliably undone.
    For modules that involve loading .NET assemblies, the fundamental constraint of not being able to unload assemblies once loaded applies.