Mounting.ps1
# --------------------------------- # Search Path Extension Point # --------------------------------- $script:searchPathTypes = @{} function Register-EnvironmentModuleSearchPathType([string] $Type, [int] $DefaultPriority, [scriptblock] $Handler) { $script:searchPathTypes[$Type] = New-Object "System.Tuple[scriptblock, int]" -ArgumentList $Handler, $DefaultPriority } Register-EnvironmentModuleSearchPathType ([EnvironmentModuleCore.SearchPath]::TYPE_DIRECTORY) 8 { param([EnvironmentModuleCore.SearchPath] $SearchPath, [EnvironmentModuleCore.EnvironmentModuleInfo] $Module) Write-Verbose "Checking directory search path $($SearchPath.Key)" $SearchKey = Expand-ValuePlaceholders -Value $SearchPath.Key -Module $Module if([string]::IsNullOrEmpty($SearchKey)) { Write-Warning "Directory search path without key specified" } $testResult = Test-ItemExistence $SearchKey $Module.RequiredItems $SearchPath.SubFolder if ($testResult.Exists) { $Module.ModuleRoot = $testResult.Folder return $testResult.Folder } return $null } Register-EnvironmentModuleSearchPathType ([EnvironmentModuleCore.SearchPath]::TYPE_ENVIRONMENT_VARIABLE) 10 { param([EnvironmentModuleCore.SearchPath] $SearchPath, [EnvironmentModuleCore.EnvironmentModuleInfo] $Module) $directory = $([environment]::GetEnvironmentVariable($SearchPath.Key)) if([string]::IsNullOrEmpty($SearchPath.Key)) { Write-Warning "Environment Variable search path without key specified" } if(-not $directory) { Write-Verbose "No directory found under environment variable '$($SearchPath.Key)'" return $null } Write-Verbose "Checking environment search path $($SearchPath.Key) -> $directory" $testResult = (Test-ItemExistence $directory $Module.RequiredItems $SearchPath.SubFolder) if ($testResult.Exists) { $Module.ModuleRoot = $testResult.Folder return $testResult.Folder } return $null } # --------------------------------- # Required Item Extension Point # --------------------------------- $script:requiredItemTypes = @{} function Register-EnvironmentModuleRequiredItemType([string] $Type, [scriptblock] $Handler) { $script:requiredItemTypes[$Type] = $Handler } Register-EnvironmentModuleRequiredItemType ([EnvironmentModuleCore.RequiredItem]::TYPE_FILE) { param([System.IO.DirectoryInfo] $Directory, [EnvironmentModuleCore.RequiredItem] $Item) if([string]::IsNullOrEmpty($Item.Value)) { Write-Warning "Required file without value specified" } $found = $false foreach($testItem in $item.Value.Split(";")) { if (-not (Test-Path (Join-Path "$($Directory.FullName)" "$testItem"))) { Write-Verbose "The file $testItem does not exist in folder $($Directory.FullName)" } else { $found = $true break } } return $found } # --------------------------------- # Functions # --------------------------------- function Test-ItemExistence([string] $FolderPath, [EnvironmentModuleCore.RequiredItem[]] $Items, [string] $SubFolderPath) { <# .SYNOPSIS Check if the given folder contains all items given as second parameter. .DESCRIPTION This function will check if the folder does exist and if it contains all given items. .PARAMETER FolderPath The folder to check. .PARAMETER Items The items to check. .PARAMETER SubFolderPath The subfolder path that should be appended to the folder path. .OUTPUTS An anonymous object containing 2 values. "Exists": True if the folder does exist and if it contains all items, false otherwise. "Folder": The full path to the folder containing the file. The value is $null if no folder was found. #> if (-not $FolderPath) { Write-Error "No folder path given" return @{Exists=$false; Folder=$null} } if (-not $SubFolderPath) { $SubFolderPath = "" } Write-Verbose "Testing item exisiting in folder '$FolderPath' and subfolder '$SubFolderPath'" try { $folderCandidates = (Get-Item (Join-Path "$FolderPath" "$SubFolderPath" -ErrorAction SilentlyContinue) -ErrorAction SilentlyContinue) | Where-Object {$_.PsIsContainer} } catch { $folderCandidates = @() } foreach($folderCandidate in $folderCandidates) { Write-Verbose "Handling folder candidate $($folderCandidate.FUllName)" $match = $true foreach($item in $Items) { $handler = $script:requiredItemTypes[$item.ItemType.ToUpper()] if($null -eq $handler) { Write-Warning "No handler for item type $($item.ItemType) found" $match = $false } else { $found = $handler.InvokeReturnAsIs($folderCandidate, $item) if(-not $found) { $match = $false } } if($match -eq $false) { break } } if($match) { return @{Exists=$true; Folder=$folderCandidate.FullName} } } return @{Exists=$false; Folder=$null} } function Test-EnvironmentModuleRootDirectory([EnvironmentModuleCore.EnvironmentModuleInfo] $Module, [switch] $IncludeDependencies) { <# .SYNOPSIS Check if the given module has a valid root directory (containing all required files). .DESCRIPTION This function will check all defined root directory seach paths of the given module. If at least one of these directories contains all required files, $true is returned. .PARAMETER Module The module to handle. .PARAMETER IncludeDependencies Set this value to $true, if all dependencies should be checked as well. The result is only $true, if the module and all dependencies have a valid root directory. .OUTPUTS True if a valid root directory was found. #> if(($Module.RequiredItems.Length -gt 0) -and ($null -eq (Set-EnvironmentModuleRootDirectory $Module))) { return $false } if($IncludeDependencies) { foreach ($dependency in $Module.Dependencies) { $dependencyModule = Get-EnvironmentModule -ListAvailable $dependency.ModuleFullName if(-not $dependencyModule) { return $false } if(-not (Test-EnvironmentModuleRootDirectory $dependencyModule $IncludeDependencies)) { return $false } } } return $true } function Set-EnvironmentModuleRootDirectory { <# .SYNOPSIS Find the root directory of the module that is either specified by a search path object. Store the value in the object. .DESCRIPTION This function will check the meta parameter of the given module and will identify the root directory of the module. The root directory is the first directory that contains all required items. .PARAMETER Module The module to handle. .OUTPUTS The path to the root directory or $null if it was not found. #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] param ( [EnvironmentModuleCore.EnvironmentModuleInfo] $Module, [bool] $SilentMode ) if((-not ([string]::IsNullOrEmpty($Module.ModuleRoot))) -and (Test-Path ($Module.ModuleRoot))) { return $Module.ModuleRoot } Write-Verbose "Searching root for $($Module.Name) with $($Module.SearchPaths.Count) search paths and $($Module.RequiredItems.Count) required items" if(($Module.SearchPaths.Count -eq 0) -and ($Module.RequiredItems.Count -gt 0)) { if(-not $SilentMode) { Write-Warning "The module $($Module.FullName) has no defined search paths. Please use the function Add-EnvironmentModuleSearchPath to specify the location" } } foreach($searchPath in $Module.SearchPaths) { $handler = $script:searchPathTypes[$searchPath.Type.ToUpper()] if($null -eq $handler) { if(-not $SilentMode) { Write-Warning "No handler for search path type $($searchPath.Type) found" } continue } $result = (($handler.Item1).InvokeReturnAsIs($searchPath, $Module)) Write-Verbose "Handling search path $($searchPath.Key) returned with '$result'" if($null -eq $result) { Write-Verbose "Checking next search path" continue } return $result } $Module = $null return $null } function Import-EnvironmentModuleDescriptionFile([String] $ModuleFile, [switch] $Silent) { <# .SYNOPSIS Import the module dependencies and paramters from the specified module description file (pse1). .PARAMETER ModuleFile The path to the pse1 file to handle. .PARAMETER Silent Set this parameter if no warning messages should be displayed. #> $silentMode = $false if($Silent) { $silentMode = $true } $module = New-EnvironmentModuleInfoFromDescriptionFile -Path $ModuleFile if($null -eq $module) { Write-Error "Unable to load module information from $ModuleFile, please specify a valid pse1 file." return } # Load the defined dependencies $result = Import-ModuleDependencies -Module $module -KnownModules (New-Object "System.Collections.Generic.HashSet[string]") -LoadDependenciesDirectly $true -SilentMode $silentMode if(-not $result) { return $false } # Set the parameter defaults $module.Parameters.Keys | ForEach-Object { $parameter = $module.Parameters[$_] Set-EnvironmentModuleParameterInternal $parameter.Name (Expand-ValuePlaceholders -Value $parameter.Value -Module $module) $ModuleFullName $parameter.IsUserDefined $parameter.VirtualEnvironment } } function Import-EnvironmentModule { <# .SYNOPSIS Import the environment module. .DESCRIPTION This function will import the environment module into the scope of the console. .PARAMETER ModuleFullName The full name of the environment module. .PARAMETER IsLoadedDirectly True if the load was triggered by the user. False if triggered by another module. Default: $true .PARAMETER Silent Do not print output to the console. .OUTPUTS No outputs are returned. #> [CmdletBinding()] Param( [switch] $Silent ) DynamicParam { $runtimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $moduleSet = Get-ConcreteEnvironmentModules -ListAvailable | Select-Object -ExpandProperty "FullName" Add-DynamicParameter 'ModuleFullName' String $runtimeParameterDictionary -Mandatory $false -Position 0 -ValidateSet $moduleSet Add-DynamicParameter 'IsLoadedDirectly' Bool $runtimeParameterDictionary -Mandatory $false -Position 1 -ValidateSet @($true, $false) Add-DynamicParameter 'ModuleFile' String $runtimeParameterDictionary -Mandatory $false -Position 2 return $runtimeParameterDictionary } begin { # Bind the parameter to a friendly variable $ModuleFullName = $PsBoundParameters["ModuleFullName"] $IsLoadedDirectly = $PsBoundParameters["IsLoadedDirectly"] $ModuleFile = $PsBoundParameters["ModuleFile"] } process { $silentMode = $false if($Silent) { $silentMode = $true } if($null -eq $IsLoadedDirectly) { $IsLoadedDirectly = $true } if($null -ne $ModuleFile) { $ModuleFullName = (Get-Module "$ModuleFile" -ListAvailable)[0].Name if($null -eq $ModuleFullName) { Write-Error "Unable to load module information from $ModuleFile, please specify a valid psd1 file." return } } $initialSilentLoadState = $script:silentLoad Import-RequiredModulesRecursive $ModuleFullName $IsLoadedDirectly (New-Object "System.Collections.Generic.HashSet[string]") $null $silentMode $ModuleFile | Out-Null $script:silentLoad = $initialSilentLoadState } } function Import-RequiredModulesRecursive([String] $ModuleFullName, [Bool] $LoadedDirectly, [System.Collections.Generic.HashSet[string]] $KnownModules, [EnvironmentModuleCore.EnvironmentModuleInfo] $SourceModule = $null, [Bool] $SilentMode = $false, [String] $ModuleFile = $null) { <# .SYNOPSIS Import the environment module with the given name and all required environment modules. .DESCRIPTION This function will import the environment module into the scope of the console and will later iterate over all required modules to import them as well. .PARAMETER ModuleFullName The full name of the environment module to import. .PARAMETER LoadedDirectly True if the module was loaded directly by the user. False if it was loaded as dependency. .PARAMETER KnownModules A collection of known modules, used to detect circular dependencies. .PARAMETER SourceModule The module that has triggered the loading of this module (used when module is loaded as dependency). .PARAMETER SilentMode True if no outputs should be printed. .PARAMETER ModuleFile The module file to load. If no file is specified, the module is loaded from the PSModulePath. .OUTPUTS True if the module was loaded correctly, otherwise false. #> if($KnownModules.Contains($ModuleFullName) -and (0 -eq (Get-Module $ModuleFullName).Count)) { Write-Error "A circular dependency between the modules was detected" return $false } $KnownModules.Add($ModuleFullName) | Out-Null Write-Verbose "Importing the module $ModuleFullName recursive" $conflictTestResult = (Test-ConflictsWithLoadedModules $ModuleFullName $script:loadedEnvironmentModules) $module = $conflictTestResult.Module $conflict = $conflictTestResult.Conflict if($conflict) { if(-not ($SilentMode)) { Write-Error ("The module '$ModuleFullName' conflicts with the already loaded module '$($module.FullName)'") } return $false } if($null -ne $module) { Write-Verbose "The module $ModuleFullName has loaded directly state $($module.IsLoadedDirectly) and should be loaded with state $LoadedDirectly" if($module.IsLoadedDirectly -and $LoadedDirectly) { return $true } Write-Verbose "The module $ModuleFullName is already loaded. Increasing reference counter" $module.IsLoadedDirectly = $True $module.ReferenceCounter++ return $true } # Basic setup $module = New-EnvironmentModuleInfo -ModuleFullName $ModuleFullName -ModuleFile $ModuleFile $alreadyLoadedModules = $null if(($module.ModuleType -eq [EnvironmentModuleCore.EnvironmentModuleType]::Meta) -and ($LoadedDirectly)) { $script:silentLoad = $true $alreadyLoadedModules = Get-EnvironmentModule } if ($null -eq $module) { if(-not($SilentMode)) { Write-Error "Unable to read environment module description file of module $ModuleFullName" } return $false } # Identify the root directory $moduleRoot = Set-EnvironmentModuleRootDirectory $module $SilentMode if (($module.RequiredItems.Length -gt 0) -and ($null -eq $moduleRoot)) { if(-not $SilentMode) { Write-InformationColored -InformationAction 'Continue' "Unable to find the root directory of module $($module.FullName) - Is the program correctly installed?" -ForegroundColor $Host.PrivateData.ErrorForegroundColor -BackgroundColor $Host.PrivateData.ErrorBackgroundColor Write-InformationColored -InformationAction 'Continue' "Use 'Add-EnvironmentModuleSearchPath' to specify the location." -ForegroundColor $Host.PrivateData.ErrorForegroundColor -BackgroundColor $Host.PrivateData.ErrorBackgroundColor } return $false } # Perform the merge with the other specified pse1 files foreach($moduleReference in $module.MergeModules) { $otherModuleInfo = New-EnvironmentModuleInfoFromDescriptionFile -Path (Expand-ValuePlaceholders $moduleReference $module) if($null -eq $otherModuleInfo) { continue } $module = Join-EnvironmentModuleInfos $module $otherModuleInfo } # Load the dependencies first $loadDependenciesDirectly = $false if($module.DirectUnload -eq $true) { $loadDependenciesDirectly = $LoadedDirectly } $result = Import-ModuleDependencies -Module $module -LoadDependenciesDirectly $loadDependenciesDirectly -KnownModules $KnownModules -SilentMode $SilentMode if(-not $result) { return $false } # Create the temp directory (New-Item -ItemType directory -Force $module.TmpDirectory) | Out-Null # Set the parameter defaults $module.Parameters.Keys | ForEach-Object { $parameter = $module.Parameters[$_] Set-EnvironmentModuleParameterInternal $parameter.Name (Expand-ValuePlaceholders -Value $parameter.Value -Module $module) $ModuleFullName $parameter.IsUserDefined $parameter.VirtualEnvironment } # Load the module itself $module = New-Object "EnvironmentModuleCore.EnvironmentModule" -ArgumentList ($module, $LoadedDirectly, $SourceModule) Write-Verbose "Importing the module $ModuleFullName into the Powershell environment" $psModuleName = $ModuleFile if([string]::IsNullOrEmpty($ModuleFile)) { $psModuleName = $ModuleFullName } try { Import-Module $psModuleName -Scope Global -Force -ArgumentList $module, $SilentMode } catch { if(-not $SilentMode) { Write-Error $_.Exception.Message } return $false } if($Module.SwitchDirectoryToModuleRoot) { Write-Verbose "Switching to module root" Set-Location "$($Module.ModuleRoot)" } Write-Verbose "The module has direct unload state $($module.DirectUnload)" if($Module.DirectUnload -eq $false) { $isLoaded = Mount-EnvironmentModuleInternal $module $SilentMode Write-Verbose "Importing of module $ModuleFullName done" if(!$isLoaded) { Write-Error "The module $ModuleFullName was not loaded successfully" $script:silentUnload = $true Remove-Module $ModuleFullName -Force $script:silentUnload = $false return $false } } else { [void] (New-Event -SourceIdentifier "EnvironmentModuleLoaded" -EventArguments $module, $LoadedDirectly, $SilentMode) $module.FullyLoaded() Remove-Module $ModuleFullName -Force return $true } # Print the summary if(($module.ModuleType -eq [EnvironmentModuleCore.EnvironmentModuleType]::Meta) -and ($LoadedDirectly) -and (-not $SilentMode)) { Show-EnvironmentSummary -ModuleBlacklist $alreadyLoadedModules } [void] (New-Event -SourceIdentifier "EnvironmentModuleLoaded" -EventArguments $module, $LoadedDirectly, $SilentMode) $module.FullyLoaded() return $true } function Import-ModuleDependencies([EnvironmentModuleCore.EnvironmentModuleInfo] $Module, [bool] $LoadDependenciesDirectly, [System.Collections.Generic.HashSet[string]] $KnownModules, [Bool] $SilentMode = $false) { <# .SYNOPSIS Import all dependencies of the specified module into the active session. .PARAMETER Module The module defining the dependencies to load. .PARAMETER LoadDependenciesDirectly True if the dependencies should be marked as directly loaded. False if the modules should be loaded as dependencies. .PARAMETER KnownModules A collection of known modules, used to detect circular dependencies. .PARAMETER SilentMode True if no outputs should be printed. .OUTPUTS The boolean value $true if the load process was performed successfully. #> Write-Verbose "Children are loaded with directly state $LoadDependenciesDirectly" $loadedDependencies = New-Object "System.Collections.Stack" if($Module.Dependencies.Count -gt 0) { foreach ($dependency in $Module.Dependencies) { Write-Verbose "Importing dependency $dependency" $silentDependencyMode = $SilentMode -or $dependency.IsOptional $loadingResult = (Import-RequiredModulesRecursive $dependency.ModuleFullName $LoadDependenciesDirectly $KnownModules $Module $silentDependencyMode) if (-not $loadingResult) { if(-not ($dependency.IsOptional)) { while ($loadedDependencies.Count -gt 0) { Remove-EnvironmentModule ($loadedDependencies.Pop()) } return $false } } else { $loadedDependencies.Push($dependency.ModuleFullName) } } } return $true } function Update-PathValue([EnvironmentModuleCore.PathInfo] $PathInfo, [EnvironmentModuleCore.PathUpdateEventArgs] $AdditionalArgs) { <# .SYNOPSIS Event handler function that is called when a environment variable value was changed at runtime. .PARAMETER PathInfo The path definition that was changed. .PARAMETER AdditionalArgs Additional event arguments. #> [String] $joinedValue = $PathInfo.Values -join [IO.Path]::PathSeparator Write-Verbose "Handling path for variable $($pathInfo.Variable) with joined value $joinedValue" if ($PathInfo.PathType -eq [EnvironmentModuleCore.PathType]::SET) { Write-Verbose "Joined Set-Path: $($PathInfo.Variable) = $joinedValue" [Environment]::SetEnvironmentVariable($pathInfo.Variable, $joinedValue, "Process") return } [String] $actualValue = [Environment]::GetEnvironmentVariable($pathInfo.Variable) [String] $oldValue = $AdditionalArgs.OldValues -join [IO.Path]::PathSeparator $index = -1 if($PathInfo.PathType -eq [EnvironmentModuleCore.PathType]::APPEND) { $index = $actualValue.IndexOf($oldValue) } else { $index = $actualValue.LastIndexOf($oldValue) } if($index -lt 0) { Write-Warning "The value '$oldValue' is not part of the variable $($pathInfo.Variable)" continue } $actualValue = $actualValue.Substring(0, $index) + $joinedValue + $actualValue.Substring($index + $oldValue.Length) [Environment]::SetEnvironmentVariable($pathInfo.Variable, $actualValue, "Process") Write-Verbose "Changing variable $($pathInfo.Variable) to '$actualValue'" } function Mount-EnvironmentModuleInternal([EnvironmentModuleCore.EnvironmentModule] $Module, [Bool] $SilentMode) { <# .SYNOPSIS Deploy all the aliases, environment variables and functions that are stored in the given module object to the environment. .DESCRIPTION This function will export all aliases and environment variables that are defined in the given EnvironmentModule-object. .PARAMETER Module The module that should be deployed. .PARAMETER SilentMode True if no outputs should be printed. .OUTPUTS A boolean value that is $true if the module was loaded successfully. Otherwise the value is $false. #> process { $SilentMode = $SilentMode -or $script:silentLoad Write-Verbose "Try to load module '$($Module.Name)' with architecture '$($Module.Architecture)', Version '$($Module.Version)' and type '$($Module.ModuleType)'" Write-Verbose "Identified $($Module.Paths.Count) paths" foreach ($pathInfo in $Module.Paths) { Add-EnvironmentModuleVariable $pathInfo $Module $script:loadedEnvironmentModuleSetPaths } foreach ($aliasInfo in $Module.Aliases.Values) { Add-EnvironmentModuleAlias $aliasInfo $Module $SilentMode $script:loadedEnvironmentModuleAliases } foreach ($functionInfo in $Module.Functions.Values) { Add-EnvironmentModuleFunction $functionInfo $Module $script:loadedEnvironmentModuleFunctions } Write-Verbose ("Register environment module with name " + $Module.Name + " and object " + $Module) Write-Verbose "Adding module $($Module.Name) to mapping" $script:loadedEnvironmentModules[$Module.Name] = $Module if($script:configuration["ShowLoadingMessages"]) { Write-InformationColored -InformationAction 'Continue' ("$($Module.FullName) loaded") } # Register the changed events for dynamic handling Register-ObjectEvent -InputObject $module -EventName "OnPathChanged" -Action ${function:Update-PathValue} Register-ObjectEvent -InputObject $module -EventName "OnPathAdded" -MessageData $script:loadedEnvironmentModuleSetPaths -Action { param ( [EnvironmentModuleCore.PathInfo] $PathInfo, [EnvironmentModuleCore.EnvironmentModule] $Module ) Add-EnvironmentModuleVariable $PathInfo $Module $event.MessageData } Register-ObjectEvent -InputObject $module -EventName "OnAliasAdded" -MessageData $script:loadedEnvironmentModuleAliases -Action { param ( [EnvironmentModuleCore.AliasInfo] $AliasInfo, [EnvironmentModuleCore.EnvironmentModule] $Module ) Add-EnvironmentModuleAlias $AliasInfo $Module $True $event.MessageData } Register-ObjectEvent -InputObject $module -EventName "OnFunctionAdded" -MessageData $script:loadedEnvironmentModuleFunctions -Action { param ( [EnvironmentModuleCore.FunctionInfo] $FunctionInfo, [EnvironmentModuleCore.EnvironmentModule] $Module ) Add-EnvironmentModuleFunction $FunctionInfo $Module $event.MessageData } return $true } } function Show-EnvironmentSummary([EnvironmentModuleCore.EnvironmentModuleInfoBase[]] $ModuleBlacklist = $null) { <# .SYNOPSIS Print a summary of the environment that is currenctly loaded. .DESCRIPTION This function will print all modules, functions, aliases and parameters of the current environment to the host console. .OUTPUTS No output is returned. #> $aliases = Get-EnvironmentModuleAlias | Sort-Object -Property "Name" $functions = Get-EnvironmentModuleFunction -ReturnTopLevelFunction | Sort-Object -Property "Name" $parameters = Get-EnvironmentModuleParameter | Sort-Object -Property "Name" $modules = Get-ConcreteEnvironmentModules | Sort-Object -Property "FullName" Write-InformationColored -InformationAction 'Continue' "" Write-InformationColored -InformationAction 'Continue' "--------------------" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor Write-InformationColored -InformationAction 'Continue' "Loaded Modules:" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor $moduleBlackListNames = $ModuleBlacklist | Select-Object -ExpandProperty "FullName" $modules | ForEach-Object { if(-not ($moduleBlackListNames -match $_.FullName)) { Write-InformationColored -InformationAction 'Continue' " * $($_.FullName)" } } Write-InformationColored -InformationAction 'Continue' "Available Functions:" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor $functions | ForEach-Object { if(-not ($moduleBlackListNames -match $_.ModuleFullName)) { Write-InformationColored -InformationAction 'Continue' " * $($_.Name) - " -NoNewline Write-InformationColored -InformationAction 'Continue' $_.ModuleFullName -ForegroundColor $Host.PrivateData.VerboseForegroundColor -BackgroundColor $Host.PrivateData.VerboseBackgroundColor } } Write-InformationColored -InformationAction 'Continue' "Available Aliases:" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor $aliases | ForEach-Object { if(-not ($moduleBlackListNames -match $_.ModuleFullName)) { Write-InformationColored -InformationAction 'Continue' " * $($_.Name) - " -NoNewline Write-InformationColored -InformationAction 'Continue' $_.Description -ForegroundColor $Host.PrivateData.VerboseForegroundColor -BackgroundColor $Host.PrivateData.VerboseBackgroundColor } } Write-InformationColored -InformationAction 'Continue' "Available Parameters:" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor $parameters | ForEach-Object { Write-InformationColored -InformationAction 'Continue' " * $($_.Name) - " -NoNewline Write-InformationColored -InformationAction 'Continue' $_.Value -ForegroundColor $Host.PrivateData.VerboseForegroundColor -BackgroundColor $Host.PrivateData.VerboseBackgroundColor } Write-InformationColored -InformationAction 'Continue' "--------------------" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor Write-InformationColored -InformationAction 'Continue' "" Write-InformationColored -InformationAction 'Continue' "Available Virtual Parameter Environments:" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor $script:virtualEnvironments | ForEach-Object { Write-InformationColored -InformationAction 'Continue' " * $_ " } Write-InformationColored -InformationAction 'Continue' "--------------------" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor Write-InformationColored -InformationAction 'Continue' "" } function Switch-EnvironmentModule { <# .SYNOPSIS Switch a already loaded environment module with a different one. .DESCRIPTION This function will unmount the giben enivronment module and will load the new one instead. .PARAMETER ModuleFullName The name of the environment module to unload. .PARAMETER NewModuleFullName The name of the new environment module to load. .OUTPUTS No output is returned. #> [CmdletBinding()] Param( [switch] $Silent ) DynamicParam { $runtimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $moduleSet = Get-LoadedEnvironmentModules | Select-Object -ExpandProperty FullName Add-DynamicParameter 'ModuleFullName' String $runtimeParameterDictionary -Mandatory $True -Position 0 -ValidateSet $moduleSet $moduleSet = Get-AllEnvironmentModules | Select-Object -ExpandProperty FullName | Where-Object {(Test-EnvironmentModuleLoaded $_) -eq $false} Add-DynamicParameter 'NewModuleFullName' String $runtimeParameterDictionary -Mandatory $True -Position 1 -ValidateSet $moduleSet return $runtimeParameterDictionary } begin { # Bind the parameter to a friendly variable $moduleFullName = $PsBoundParameters['ModuleFullName'] $newModuleFullName = $PsBoundParameters['NewModuleFullName'] } process { $module = Get-EnvironmentModule($moduleFullName) if (!$module) { Write-Error ("No loaded environment module named $moduleFullName") return } $oldUserParameters = Get-EnvironmentModuleParameter "*" -UserDefined Remove-EnvironmentModule $moduleFullName -Force Import-EnvironmentModule $newModuleFullName -IsLoadedDirectly:$module.IsLoadedDirectly -Silent:$Silent foreach($parameter in $oldUserParameters) { $newParameter = (Get-EnvironmentModuleParameter $parameter.Name) if(($null -ne $newParameter) -and ($newParameter.IsUserDefined -eq $False)) { Set-EnvironmentModuleParameterInternal $parameter.Name $parameter.Value "" $True } } [void] (New-Event -SourceIdentifier "EnvironmentModuleSwitched" -EventArguments $moduleFullName, $newModuleFullName) } } function Add-EnvironmentModuleVariable([EnvironmentModuleCore.PathInfo] $PathInfo, [EnvironmentModuleCore.EnvironmentModule] $Module, [System.Collections.Generic.Dictionary[string, System.Collections.Generic.Dictionary[string, string]]] $SetPathRegistration) { <# .SYNOPSIS Add the given environment variable value to the current process environment. .DESCRIPTION This function will append or prepend the new value to the environment variable with the given name. .PARAMETER PathInfo The path to add. .PARAMETER Module The associated module. #> $renderedVariables = [System.Collections.Generic.List[string]]::new() $changed = $false foreach($pathValue in $PathInfo.Values) { $renderedValue = Expand-ValuePlaceholders "$pathValue" $Module $renderedVariables.Add($renderedValue) if($renderedValue -ne $pathValue) { $changed = $true } } if($changed) { $PathInfo.ChangeValues($renderedVariables) } [String] $joinedValue = $PathInfo.Values -join [IO.Path]::PathSeparator [String] $actualValue = [Environment]::GetEnvironmentVariable($PathInfo.Variable) Write-Verbose "Handling path for variable $($PathInfo.Variable) with joined value $joinedValue" if ($PathInfo.PathType -eq [EnvironmentModuleCore.PathType]::SET) { Write-Verbose "Joined Set-Path: $($PathInfo.Variable) = $joinedValue" if($SetPathRegistration.ContainsKey($Module.FullName)) { $SetPathRegistration[$Module.FullName][$PathInfo.Variable] = $actualValue } else { $SetPathRegistration[$Module.FullName] = [System.Collections.Generic.Dictionary[string, string]]::new() $SetPathRegistration[$Module.FullName][$PathInfo.Variable] = $actualValue } [Environment]::SetEnvironmentVariable($PathInfo.Variable, $joinedValue, "Process") return } if($joinedValue -eq "") { Write-Verbose "No path value specified for APPEND or PREPEND" continue } $tmpValue = [environment]::GetEnvironmentVariable($PathInfo.Variable, "Process") if(!$tmpValue) { $tmpValue = $joinedValue } else { if($PathInfo.PathType -eq [EnvironmentModuleCore.PathType]::APPEND) { Write-Verbose "Joined Append-Path: $($PathInfo.Variable) = $joinedValue" $tmpValue = "$tmpValue$([IO.Path]::PathSeparator)$joinedValue" } else { Write-Verbose "Joined Prepend-Path: $($PathInfo.Variable) = $joinedValue" $tmpValue = "$joinedValue$([IO.Path]::PathSeparator)$tmpValue" } } Write-Verbose "Changing variable: $($PathInfo.Variable) = $joinedValue" [Environment]::SetEnvironmentVariable($PathInfo.Variable, $tmpValue, "Process") } function Add-EnvironmentModuleAlias([EnvironmentModuleCore.AliasInfo] $AliasInfo, [EnvironmentModuleCore.EnvironmentModule] $Module, [bool] $SilentMode, [System.Collections.Generic.Dictionary[string, System.Collections.Generic.List[EnvironmentModuleCore.AliasInfo]]] $AliasRegistration) { <# .SYNOPSIS Add a new alias to the active environment. .DESCRIPTION This function will extend the active environment with a new alias definition. The alias is added to the loaded aliases collection. .PARAMETER AliasInfo The definition of the alias. .PARAMETER Module The associated module. .PARAMETER SilentMode True if no mounting information should be printed. .OUTPUTS No output is returned. #> # Check if the alias was already used if($AliasRegistration.ContainsKey($AliasInfo.Name)) { $knownAliases = $AliasRegistration[$AliasInfo.Name] $knownAliases.Add($AliasInfo) } else { $newValue = New-Object "System.Collections.Generic.List[EnvironmentModuleCore.AliasInfo]" $newValue.Add($AliasInfo) $AliasRegistration.Add($AliasInfo.Name, $newValue) } Set-Alias -name $aliasInfo.Name -value $aliasInfo.Definition -scope "Global" if(($aliasInfo.Description -ne "") -and (-not $SilentMode)) { if(-not $SilentMode) { Write-InformationColored -InformationAction 'Continue' $aliasInfo.Description -Foregroundcolor $Host.PrivateData.VerboseForegroundColor -BackgroundColor $Host.PrivateData.VerboseBackgroundColor } } } function Add-EnvironmentModuleFunction([EnvironmentModuleCore.FunctionInfo] $FunctionDefinition, [EnvironmentModuleCore.EnvironmentModule] $Module, [System.Collections.Generic.Dictionary[string, System.Collections.Generic.List[EnvironmentModuleCore.FunctionInfo]]] $FunctionRegistration) { <# .SYNOPSIS Add a new function to the active environment. .DESCRIPTION This function will extend the active environment with a new function definition. The function is added to the loaded functions stack. .PARAMETER FunctionDefinition The definition of the function. .OUTPUTS No output is returned. #> # Check if the function was already used if($FunctionRegistration.ContainsKey($FunctionDefinition.Name)) { $knownFunctions = $FunctionRegistration[$FunctionDefinition.Name] $knownFunctions.Add($FunctionDefinition) } else { $newValue = New-Object "System.Collections.Generic.List[EnvironmentModuleCore.FunctionInfo]" $newValue.Add($FunctionDefinition) $FunctionRegistration.Add($FunctionDefinition.Name, $newValue) } new-item -path function:\ -name "global:$($FunctionDefinition.Name)" -value ([ScriptBlock]$FunctionDefinition.Definition) -Force } function Test-ConflictsWithLoadedModules([string] $ModuleFullName, [hashtable] $LoadedEnvironmentModules) { <# .SYNOPSIS Check if the given module name conflicts with the already loaded modules. .DESCRIPTION This function will compare the given module name with the list of all loaded modules. If the module conflicts in version or architecture, true is returned. .PARAMETER ModuleFullName The name of the module to check. .PARAMETER LoadedEnvironmentModules The already loaded modules to check with. The key must be the short name and the value must be the module info. .OUTPUTS A tuple containing a boolean value as first argument. True if the module does conflict with the already loaded modules, false otherwise. And the identified module as second argument. #> $moduleNameParts = Split-EnvironmentModuleName $ModuleFullName $name = $moduleNameParts.Name $version = $moduleNameParts.Version $architecture = $moduleNameParts.Architecture $module = $null $conflict = $false if($LoadedEnvironmentModules.ContainsKey($name)) { $module = $LoadedEnvironmentModules.Get_Item($name) Write-Verbose "A module matching name '$name' was already found - checking for version or architecture conflict" if(-not ($module.DirectUnload)) { if(-not ([string]::IsNullOrEmpty($version))) { # A specific version is required if([string]::IsNullOrEmpty($module.Version)) { Write-Warning "The already loaded module $($module.FullName) has no version specifier. Don't know if it is compatible to version '$version'" } else { if(-not ($module.Version.StartsWith($version))) { $conflict = $true } } } if(-not ([string]::IsNullOrEmpty($architecture))) { # A specific architecture is required if([string]::IsNullOrEmpty($module.Architecture)) { Write-Warning "The already loaded module $($module.FullName) has no architecture specifier. Don't know if it is compatible to architecture '$architecture'" } else { if($architecture -ne $module.Architecture) { $conflict = $true } } } } } $result = @{} $result.Conflict = $conflict $result.Module = $module return $result } |