Although there are already plenty of questions and blogs regarding PowerShell module imports, I did not found any that would answer my question.
I'm trying to import modules which are grouped within subfolders without adding all those subfolders to the $env:PSModulePath. Example of the folder structure:
...\WindowsPowerShell\Modules\
...\WindowsPowerShell\Modules\Avengers
...\WindowsPowerShell\Modules\Avengers\ModuleHulk\ModuleHulk.psd1
...\WindowsPowerShell\Modules\Avengers\ModuleThor\ModuleThor.psd1
...\WindowsPowerShell\Modules\X-Men
...\WindowsPowerShell\Modules\X-Men\ModuleProfX\ModuleProfX.psd1
...\WindowsPowerShell\Modules\X-Men\ModuleMagneto\ModuleMagneto.psd1
...\WindowsPowerShell\Modules\FantasticFour
...\WindowsPowerShell\Modules\FantasticFour\ModuleMisterFantastic\ModuleMisterFantastic.psd1
...\WindowsPowerShell\Modules\FantasticFour\ModuleInvisibleWoman\ModuleInvisibleWoman.psd1
According to the Microsoft Docs the module .psd1 filename has to be the same as the module folder name. This is satisfied above. The modules have to be within a location of the $env:PSModulePath, all good.
Unfortunately, PowerShell fails to import these modules:
PS> Import-Module ModuleHulk
Import-Module : The specified module 'ModuleHulk' was not loaded because no valid module file was found in any module directory.
At line:1 char:1
+ Import-Module ModuleHulk
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (ModuleHulk:String) [Import-Module], FileNotFoundException
+ FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand
PS>
However, recently I found out one can also import modules using the fairly new using module statement (since PowerShell Version 5). For doing that I could add the following code at the very beginning in my scripts or execute that even in the console:
#Requires -Version 5
using module ModuleHulk
PS> using module ModuleHulk
PS>
No issue, all good, so the module itself is fine and can be imported as usual. But, this seems to be a hacky solution and it needs at least PowerShell 5. Now the question:
Is there any way to achieve that using the usual Import-Module cmdlet, without adding all the subfolders to the $env:PSModulePath?
I found one possible solution thanks to another SO question: Import-Module works only when piped from Get-Module.
The Import-Module cmdlet also accepts one or more [PSModuleInfo] objects, which are what Get-Module returns. So one can do the following:
Get-Module ModuleHulk -ListAvailable | Import-Module
PS> Get-Module ModuleHulk -ListAvailable
Directory: ...\WindowsPowerShell\Modules\Avengers\
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 1.0.0 ModuleHulk
PS> Get-Module ModuleHulk -ListAvailable | Import-Module
PS> Get-Module ModuleHulk
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 1.0.0 ModuleHulk {...}
PS>
And it worked! If anyone knows why the Get-Module cmdlet finds the modules in a different way than Import-Module, please let me know.
Related
I am trying to install the PSReadLine module:
PS> Find-Module PSReadLine | Install-Module -scope currentUser -force
This command runs and terminates without any message or any indication that something was unsuccessful.
However, when I want to load the module, I get an error message:
PS> import-module PSReadLine
import-module : The specified module 'PSReadLine' was not loaded
because no valid module file was found in any module directory.
So, I am wondering how I can install and/or load this module.
Feedback on comments
As per #Lee_Dailey's comment, I do indeed have a path with a lowercase l:
PS> ls 'C:\Program Files\WindowsPowerShell\Modules\psread*' -name
PSReadline
Unfortunately, I cannot change that lowercase l to a L because I have this issue on a machine without admin privileges.
As per #Scepticalist comment, I should find the module in one of the module paths' locations. It turns out: I don't. The following command returns/prints nothing:
foreach ($mp in $Env:PSModulePath -split ';') { ls "$mp\psread*" }
Thus, also the command suggested by #pwnosh returns nothing:
($env:PSModulePath -split ';' | Get-ChildItem -Filter 'PSReadLine' | Get-ChildItem).FullName
Find-Module PSReadLine returns (echoes)
Version Name Repository Description
------- ---- ---------- -----------
2.1.0 PSReadLine PSGallery Great command line editing in the PowerShell console host
Running Register-PSRepository -Default results in an error message:
PackageManagement\Register-PackageSource : Module Repository 'PSGallery' exists.
Set-PSRepository 'PSGallery' -InstallationPolicy Trusted runs and terminates without any message echoed to the console. Yet, after running this command, I am still unable to import the module.
I'm working on a Quick Module CLI builder. The idea is you have my Module, which has commands like Add-QuickFunction, Add-QuickAlias, New-QuickModule, Remove-QuickCommand, etc.
As you build out the functions, your Modules continue to get built out. The plan is to create an Export-QuickModule and Export-QuickModule command, so that when you're ready to Export your module it's ready to be published, to your organization, or to a public gallery.
So the problem is two-fold.
The exported members do not match the commands in the module
PS C:\WINDOWS\system32> Get-Module -Name QuickModuleCLI
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.1.3 QuickModuleCLI {New-QuickModule, Update-QuickModule}
Notice how it only includes 2 Exported commands. This is after I manually added Export-ModuleMember to my local system. Without having Export-ModuleMember, only New-QuickModule appears as exported.
But compare it to the actual list of Commands:
PS C:\WINDOWS\system32> Get-Command -Module QuickModuleCLI
CommandType Name Version Source
----------- ---- ------- ------
Function Add-QuickAlias 0.1.3 QuickModuleCLI
Function Add-QuickFunction 0.1.3 QuickModuleCLI
Function ConvertTo-PowershellEncodedString 0.1.3 QuickModuleCLI
Function Edit-QuickCommand 0.1.3 QuickModuleCLI
Function New-QuickModule 0.1.3 QuickModuleCLI
Function Remove-QuickCommand 0.1.3 QuickModuleCLI
Function Rename-QuickCommand 0.1.3 QuickModuleCLI
Function Reset-QuickCommand 0.1.3 QuickModuleCLI
Function Set-Env 0.1.3 QuickModuleCLI
Function Update-QuickModule 0.1.3 QuickModuleCLI
The Nested Module isn't getting imported into the PowerShell session until after I've imported the Module. I can deal with it by adding Import-Module to my $Profile if I have to, but I just want to make sure I'm not doing anything wrong.
What this means is, after opening a new session, I see the following:
PS C:\WINDOWS\system32> Write-Test
Write-Test : The term 'Write-Test' is not recognized as the name of a cmdlet, function, script file, or operable
program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ Write-Test
+ ~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Write-Test:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS C:\WINDOWS\system32> Import-Module QuickModuleCLI
PS C:\WINDOWS\system32> Write-Test
Hello
I use a dynamic self-rolled PowerShell generator to update the psd1 file and add to the FunctionsToExport. You can verify that when I Import-Module, it calls the NestedModules and imports it properly. However, on the computer I use to develop this Module in, I don't need to Import-Module in order for Write-Test to appear in my PowerShell Options. Is this an environmental thing that I have to just "deal with" or is this something you that can be fixed?
The code can be found here: https://github.com/EdLichtman/QuickModuleCLI
And you can try it out yourself with Import-Module QuickModuleCLI
Part 1 - ExportedCommands
Here's a minimal repro for your issue with the ExportedCommands list not matching the result from Get-Command. I distilled it down to this by progressively removing chunks of code from your repo at https://github.com/EdLichtman/QuickModuleCLI until there was nothing left to remove:
MyModule.psd1
#{
RootModule = "MyModule.psm1"
ModuleVersion = "0.1.4"
FunctionsToExport = #(
"Add-MyAlias",
"New-MyModule"
)
}
MyModule.psm1
function global:Add-MyAlias {
write-host "global:Add-MyAlias"
}
function New-MyModule {
write-host "New-MyModule"
}
Interactive
PS> Import-Module .\MyModule\MyModule.psd1 -Force
PS> Get-Module MyModule
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.1.4 MyModule New-MyModule
PS> Get-Command -Module MyModule
CommandType Name Version Source
----------- ---- ------- ------
Function Add-MyAlias 0.1.4 MyModule
Function New-MyModule 0.1.4 MyModule
I don't know what the exact implication is when you use global: on an exported function (I'm assuming the function gets imported into the global scope rather than the module scope), but if you remove global: the function will suddenly appear in the ExportedCommands list again:
Part 2 - Importing Modules
PowerShell has a feature that will cause it to search for unknown commands in a set of predefined folder locations. This is called "Module Auto-Loading" and you can enable or disable the feature using the $PSModuleAutoLoadingPreference variable as per About Preference Variables (hint: it's enabled by default).
The locations it searches are defined in an environment variable PSModulePath - see About PSModulePath.
If you want the commands in your module to be discoverable and auto-loaded on first invocation, either install your module in one of the paths already added to the PSModulePath environment variable, or add your module's location to the path.
So to follow up on part 2, it turns out the reason why is related to, in fact it's exactly the same behavior described in this answer:
Invoking functions from nested modules in a script module do not always trigger a module to autoload
My workaround was to add to the FunctionsToExport list, the functions within the NestedModules. Initially I did not think I could export functions that were not directly part of the parent module.
Why does Get-Module not show all the installed Modules?
Some PCs don't have the NetTCPIP module installed. Mine does, but when I open a new PowerShell ISE window
Get-Module | Where-Object Name -eq 'NetTCPIP' | Format-Table
Returns blank.
After one minute it returns:
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 1.0.0.0 NetTCPIP {Find-NetRoute, ...}
The strange thing is:
Get-Command -Name Test-NetConnection
Always shows that the command exists in the Source: NetTCPIP.
Now the even strange thing is that on a Server OS running the same version of PowerShell, never shows the NetTCPIP module but does have the command Test-NetConnection.
Why does Get-Module not show all the installed Modules?
Get-Module by default lists only the modules which are currently imported. If you want to see all the modules, you can use Get-Module -ListAvailable.
Starting with PowerShell 3.0, modules are dynamically imported first time you use the cmdlet from that module. So if you test the cmdlet to see if it exists (or you use Get-Command Test-NetConnection), PowerShell silently imports the module and it's displayed next time you use Get-Module.
As far as I can tell they do the same, so what is the reason to use one over the other?
(Sorry, SO question rule engine - have no idea what else to say)
Get-InstalledModule is part of PowerShellGet and will list installed modules using Install-Module cmdlet, But Get-Module -ListAvailable shows modules from all locations mentioned in $env:PsModulePath location.
Please, observe:
C:\> (Get-InstalledModule xyz.ps.core).RepositorySourceLocation
http://devstatic.xyz.com/prdnuget/nuget
C:\> (Get-Module xyz.ps.core -ListAvailable).RepositorySourceLocation.AbsoluteUri
http://devstatic.xyz.com/prdnuget/nuget
file:///C:/Users/mkharitonov/LocalTestPSRepository
The module xyz.ps.core was installed both times using Install-Module. Only once it was installed from a local repository and the other time - from a remote NuGet repository.
In both cases the module is installed into the system location, i.e. it is in PSModulePath. What does matter, apparently, is whether it was downloaded from a remote location.
I wish Microsoft documentation explained it and provided a rationale for having this method.
Get-InstalledModule will list installed modules using Install-Module and which are not naively installed. However Get-Module -ListAvailable shows modules from all locations mentioned in $env:PsModulePath location.
See below Snip for reference :
*PS C:\Users\xyz> Get-InstalledModule
`PS C:\Users\xyz> Get-Module -ListAvailable
Directory: C:\Program Files\WindowsPowerShell\Modules
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 1.0.1 Microsoft.PowerShell.Operation.V... {Get-OperationValidation, Invoke-OperationValidation}
Binary 1.0.0.1 PackageManagement {Find-Package, Get-Package, Get-PackageProvider, Get-Pac...
Script 3.4.0 Pester {Describe, Context, It, Should...}
Script 1.0.0.1 PowerShellGet {Install-Module, Find-Module, Save-Module, Update-Module...
Script 2.0.0 PSReadline {Get-PSReadLineKeyHandler, Set-PSReadLineKeyHandler, Rem...
Directory: C:\Windows\system32\WindowsPowerShell\v1.0\Modules
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Manifest 1.0.0.0 AppBackgroundTask {Disable-AppBackgroundTaskDiagnosticLog, Enable-AppBackg...*`
However if the module is not installed both will throw same exception.
PS C:\Users\Asim> $Error.Exception.Message[-1]
Unable to find type [Microsoft.PowerShell.Commands.PowerShellGet.Telemetry]
.
I have some PowerShell functions and I want to import them into my server 2008 R2 via command Import-Module. So I created a psd1 file and in psm1 I use: Export-ModuleMember -Function "*-*" -Alias *.
However when I execute command:
PS C:\Windows\system32> import-module Myloader -DisableNameChecking -Verbose
VERBOSE: Loading module from path 'C:\Tools\Myloader.psd1'.
VERBOSE: Loading module from path 'C:\Tools\Myloader.psm1'.
I expect it should import my functions but it does not and there is no errors shown up. Then I execute Get-Module -ListAvailable, my function is listed out but there is no information about ExportedCommands
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 3.0 MyLoader
Script 3.0 MyLoader
But when I run it on my local machine it is OK. Have anyone faced with this issue before?
This issue happens on Windows Server 2008 R2, PowerShell 5.0.
When using a module manifest you do the exports inside the .psd1 file. Remove the Export-ModuleMember statement from your .psm1 file and make sure the .psd1 file contains the following lines:
ModuleToProcess = 'MyLoader.psm1'
FunctionsToExport = '*-*'
AliasesToExport = '*'