Public/Import-MyModule.ps1
Function Import-MyModule { <# .SYNOPSIS Imports a PowerShell module with enhanced error handling and functionality. .DESCRIPTION This function imports a specified PowerShell module with additional error handling, verbose output, and advanced features. It checks if the module is available, handles different versions, and provides options for forced imports and minimum version requirements. It also accepts additional arguments for maximum flexibility. For PowerShell Core (7+), it provides special compatibility handling for Windows PowerShell modules. .PARAMETER Name The name of the module to import. .PARAMETER MinimumVersion The minimum version of the module to import. If specified, the function will import the newest version that meets this criteria. .PARAMETER RequiredVersion The exact version of the module to import. If specified, only this version will be imported. .PARAMETER Force Forces a module to be imported even if it's already imported. .PARAMETER Global Imports the module into the global session state. .PARAMETER PassThru Returns the imported module object. .PARAMETER Prefix Adds a prefix to the imported module's cmdlets and other items. .PARAMETER DisableNameChecking Suppresses the message that warns you when you import a cmdlet or function whose name includes an unapproved verb or a prohibited character. .PARAMETER NoClobber Prevents importing commands that would hide or overwrite existing commands. .PARAMETER Scope Defines the scope of the import, either 'Global' or 'Local'. .PARAMETER SkipEditionCheck Skips the edition check if importing modules designed for Windows PowerShell in PowerShell Core. .PARAMETER UseWindowsPowerShell Forces the module to be imported using Windows PowerShell instead of PowerShell Core. .PARAMETER FallbackToWindowsPowerShell If set to true, when importing a module fails in PowerShell Core, the function will attempt to create proxy functions using Windows PowerShell. This is useful for modules that are not compatible with PowerShell Core. .EXAMPLE Import-MyModule -Name ActiveDirectory Tries to import the ActiveDirectory module, providing verbose output and handling errors if the module is not available. .EXAMPLE Import-MyModule -Name AzureAD -MinimumVersion 2.0.0 -Force -Verbose Imports the AzureAD module with a minimum version of 2.0.0, forcing the import even if it's already loaded, and provides verbose output. .EXAMPLE Import-MyModule -Name ServerManager -FallbackToWindowsPowerShell Attempts to import the ServerManager module, falling back to Windows PowerShell compatibility mode if direct import fails in PowerShell Core. .INPUTS System.String You can pipe module names to this function. .OUTPUTS System.Management.Automation.PSModuleInfo Returns the imported module object when -PassThru is specified. .NOTES Used Functions: Name ║ Module/Namespace ════════════════════════════════════════╬══════════════════════════════ Get-Module ║ Microsoft.PowerShell.Core Import-Module ║ Microsoft.PowerShell.Core Write-Verbose ║ Microsoft.PowerShell.Utility Write-Error ║ Microsoft.PowerShell.Utility Get-FunctionDisplay ║ EguibarIT .NOTES Version: 2.7 DateModified: 23/May/2025 LastModifiedBy: Vicente Rodriguez Eguibar vicente@eguibar.com Eguibar IT http://www.eguibarit.com .LINK https://github.com/vreguibar/EguibarIT/blob/main/Public/Import-MyModule.ps1 .COMPONENT PowerShell Module Management .ROLE System Administration .FUNCTIONALITY Module Import #> [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'low' )] [OutputType([System.Management.Automation.PSModuleInfo])] Param ( # Param1 STRING for the Module Name [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $true, HelpMessage = 'Name of the module to be imported', Position = 0)] [ValidateNotNullOrEmpty()] [Alias('Module', 'ModuleName')] [string] $Name, [Parameter(Mandatory = $false)] [switch] $Force, [Parameter(Mandatory = $false)] [switch] $Global, [Parameter(Mandatory = $false)] [System.Version] $MinimumVersion, [Parameter(Mandatory = $false)] [System.Version] $RequiredVersion, [Parameter(Mandatory = $false)] [switch] $PassThru, [Parameter(Mandatory = $false)] [string] $Prefix, [Parameter(Mandatory = $false)] [switch] $DisableNameChecking, [Parameter(Mandatory = $false)] [switch] $NoClobber, [Parameter(Mandatory = $false)] [ValidateSet('Global', 'Local')] [string] $Scope, [Parameter(Mandatory = $false)] [switch] $SkipEditionCheck, [Parameter(Mandatory = $false)] [switch] $UseWindowsPowerShell, [Parameter(Mandatory = $false)] [switch] $FallbackToWindowsPowerShell ) Begin { # Set strict mode Set-StrictMode -Version Latest # Initialize logging if ($null -ne $Variables -and $null -ne $Variables.Header) { $txt = ($Variables.Header -f (Get-Date).ToString('dd/MMM/yyyy'), $MyInvocation.Mycommand, (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False) ) Write-Verbose -Message $txt } #end If ############################## # Variables Definition # Store original VerbosePreference to restore it later [string]$OriginalVerbosePreference = $VerbosePreference # Define function name for consistent logging [string]$FunctionName = $MyInvocation.MyCommand.Name # Initialize module tracking variables [System.Management.Automation.PSModuleInfo]$AvailableModule = $null [System.Management.Automation.PSModuleInfo]$ImportedModule = $null # Check PowerShell version - are we running in PowerShell Core (7+)? [bool]$IsPSCore = $PSVersionTable.PSVersion.Major -ge 7 Write-Verbose -Message ( '[{0}] Running in PowerShell version {1}.{2}. IsPSCore = {3}' -f $FunctionName, $PSVersionTable.PSVersion.Major, $PSVersionTable.PSVersion.Minor, $IsPSCore ) # List of known Windows PowerShell-only modules that need special handling [string[]]$WindowsPSOnlyModules = @( 'ServerManager', 'GroupPolicy', 'ActiveDirectory', 'DFSN', 'DFSR' ) # Should we try to use Windows PowerShell compatibility? [bool]$TryWindowsCompatibility = $IsPSCore -and ( $FallbackToWindowsPowerShell -or $UseWindowsPowerShell -or ($WindowsPSOnlyModules -contains $Name) ) # Create import parameters hashtable [hashtable]$ImportParams = @{ Name = $Name ErrorAction = 'Stop' } # Add optional parameters based on what was passed to the function if ($Force) { $ImportParams['Force'] = $true } #end If if ($Global) { $ImportParams['Global'] = $true } #end If if ($PSBoundParameters.ContainsKey('MinimumVersion')) { $ImportParams['MinimumVersion'] = $MinimumVersion } #end If if ($PSBoundParameters.ContainsKey('RequiredVersion')) { $ImportParams['RequiredVersion'] = $RequiredVersion } #end If if ($PassThru) { $ImportParams['PassThru'] = $true } #end If if ($PSBoundParameters.ContainsKey('Prefix')) { $ImportParams['Prefix'] = $Prefix } #end If if ($DisableNameChecking) { $ImportParams['DisableNameChecking'] = $true } #end If if ($NoClobber) { $ImportParams['NoClobber'] = $true } #end If if ($PSBoundParameters.ContainsKey('Scope')) { $ImportParams['Scope'] = $Scope } #end If # Only add mutually exclusive parameters if not both set # Priority: UseWindowsPowerShell (for Windows-only modules in PS Core), else SkipEditionCheck if explicitly requested if ($IsPSCore -and ($UseWindowsPowerShell -or ($WindowsPSOnlyModules -contains $Name))) { $ImportParams['UseWindowsPowerShell'] = $true } elseif ($SkipEditionCheck) { $ImportParams['SkipEditionCheck'] = $true } #end If # Handle Verbose parameter correctly if ($PSBoundParameters.ContainsKey('Verbose')) { $ImportParams['Verbose'] = $PSBoundParameters['Verbose'] } } #end Begin Process { try { # First check if the module is available (installed) on the system $AvailableModule = Get-Module -Name $Name -ListAvailable -ErrorAction SilentlyContinue -Verbose:$false if ($null -eq $AvailableModule) { # Special case handling for built-in modules with specific paths if ($Name -eq 'GroupPolicy') { $GpPath = 'C:\Windows\system32\WindowsPowerShell\v1.0\Modules\GroupPolicy\GroupPolicy.psd1' if (Test-Path -Path $GpPath) { $ImportParams['Name'] = $GpPath Write-Verbose -Message ( '[{0}] Using specific path for GroupPolicy module: {1}' -f $FunctionName, $GpPath ) # Reset previously imported module variable to prevent confusion $ImportedModule = $null } else { Write-Error -Message ( 'Module "{0}" is not installed. Please install the module before importing.' -f $Name ) return } #end If-else } elseif ($Name -eq 'ServerManager') { $SmPath = 'C:\Windows\system32\WindowsPowerShell\v1.0\Modules\ServerManager\ServerManager.psd1' if (Test-Path -Path $SmPath) { $ImportParams['Name'] = $SmPath Write-Verbose -Message ( '[{0}] Using specific path for ServerManager module: {1}' -f $FunctionName, $SmPath ) # Reset previously imported module variable to prevent confusion $ImportedModule = $null } else { Write-Error -Message ( 'Module "{0}" is not installed. Please install the module before importing.' -f $Name ) return } #end If-else } else { Write-Error -Message ( 'Module "{0}" is not installed. Please install the module before importing.' -f $Name ) return } #end If-elseIf-else } else { Write-Verbose -Message ( '[{0}] Found module {1} installed on the system.' -f $FunctionName, $Name ) } #end If # Check if the module is already imported (only if we're not using a specific path) # For specific paths (like GroupPolicy loaded from a direct path), we'll always attempt the import if ($ImportParams['Name'] -eq $Name) { $ImportedModule = Get-Module -Name $Name -ErrorAction SilentlyContinue -Verbose:$false if ($null -ne $ImportedModule -and -not $Force) { Write-Verbose -Message ( '[{0}] Module {1} is already imported.' -f $FunctionName, $Name ) if ($PassThru) { return $ImportedModule } #end If return } #end If } #end If # Perform the import if ($PSCmdlet.ShouldProcess($Name, 'Import Module')) { # First try to import directly try { Write-Verbose -Message ('[{0}] Importing module {1}...' -f $FunctionName, $Name) # For PowerShell Core, try to use UseWindowsPowerShell if appropriate if ($IsPSCore -and $TryWindowsCompatibility -and (-not $ImportParams.ContainsKey('UseWindowsPowerShell'))) { Write-Verbose -Message ( '[{0}] Running in PowerShell Core, adding UseWindowsPowerShell parameter' -f $FunctionName ) $ImportParams['UseWindowsPowerShell'] = $true } #end If if ($PassThru) { $ImportedModule = Import-Module @ImportParams -PassThru Write-Verbose -Message ( '[{0}] Successfully imported module {1}' -f $FunctionName, $Name ) # When using a specific path, we may need to return the module by name if ($null -eq $ImportedModule -and ($ImportParams['Name'] -match '\.ps[dm]1$')) { # Get the module by original name since the path-based import might not return the module correctly $ImportedModule = Get-Module -Name $Name -ErrorAction SilentlyContinue -Verbose:$false if ($null -eq $ImportedModule) { # Try to get by path basename as a fallback $BaseName = [System.IO.Path]::GetFileNameWithoutExtension($ImportParams['Name']) $ImportedModule = Get-Module -Name $BaseName -ErrorAction SilentlyContinue -Verbose:$false } #end If } #end If return $ImportedModule } else { Import-Module @ImportParams Write-Verbose -Message ( '[{0}] Successfully imported module {1}' -f $FunctionName, $Name ) } #end If-else } catch { # If we're in PowerShell Core and direct import failed, try compatibility measures if ($IsPSCore -and $FallbackToWindowsPowerShell) { Write-Verbose -Message ( '[{0}] Direct import failed. Attempting Windows PowerShell compatibility for {1}' -f $FunctionName, $Name ) # Explicitly use Windows PowerShell via PowerShell.exe to run the commands try { $ModuleCmds = powershell.exe -Command " `$ErrorActionPreference = 'Stop' Import-Module -Name $Name -Force -DisableNameChecking Get-Command -Module $Name | Select-Object -Property Name, CommandType | ConvertTo-Json " if ($null -ne $ModuleCmds -and $ModuleCmds -ne '') { $Commands = $ModuleCmds | ConvertFrom-Json Write-Verbose -Message ( '[{0}] Successfully retrieved {1} commands from {2} via Windows PowerShell' -f $FunctionName, $Commands.Count, $Name ) # Create a new module in memory for our compatibility wrapper $ModuleManifestPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "$Name-compat.psd1" # Return a "fake" module info if PassThru was specified if ($PassThru) { $ImportedModule = New-Object -TypeName System.Management.Automation.PSModuleInfo -ArgumentList $ModuleManifestPath return $ImportedModule } #end If Write-Verbose -Message ('[{0}] Created compatibility wrapper for {1}' -f $FunctionName, $Name) return } else { throw "Failed to retrieve commands from module $Name via Windows PowerShell" } #end If-else } catch { Write-Error -Message ( '[{0}] Error creating Windows PowerShell compatibility for {1}: {2}' -f $FunctionName, $Name, $_.Exception.Message ) } #end Try-Catch } else { # If not in PowerShell Core or not using compatibility, just report the original error Write-Error -Message ( '[{0}] Error importing module {1}: {2}' -f $FunctionName, $Name, $_.Exception.Message ) } #end if-else } #end try-catch } #end If } catch { Write-Error -Message ( '[{0}] Error importing module {1}: {2}' -f $FunctionName, $Name, $_.Exception.Message ) } #end Try-Catch } #end Process End { # Restore original VerbosePreference $VerbosePreference = $OriginalVerbosePreference if ($null -ne $Variables -and $null -ne $Variables.Footer) { $txt = ($Variables.Footer -f $MyInvocation.InvocationName, 'importing module.' ) Write-Verbose -Message $txt } #end If } #end End } #end Function Import-MyModule |