Is module auto-loading meant to be reliable? - powershell

Environment
I have the following folder structure where I keep powershell modules:
C:
PsModules
...
util
util.psm1 (this contains implementation of 'Test-Function')
util.test.ps1
ftp
ftp.psm1
http.test.ps1
...
There are about 50 folders and modules in c:\PsModules.
I have set environment variable PSModulePath to include c:\PsModules. This seems to meet the conditions for "well-formed modules" described in Microsoft's documentation and this answer.
Symptoms
Sometimes Test-Function is not found automatically when calling it from ISE. In fact, on any given fresh launch of ISE, there are always some (seemlingly unpredictable) modules that are not found automatically. The failure to automatically find Test-Function, for example, looks like this:
PS C:\> Test-Function
Test-Function : The term 'Test-Function' 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.
...
+ FullyQualifiedErrorId : CommandNotFoundException
At first glance, this seems to indicate that util.psm1 is not "well-formed". If it were not "well-formed", then ListAvailable shouldn't work. But it does work:
c:\> get-module -ListAvailable util
Directory: c:\PsModules\util
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.0 util
PS C:\> Test-Function
...
+ FullyQualifiedErrorId : CommandNotFoundException
Furthermore, after calling Get-Command for the module, the commands in the module are available for general use:
c:\> Get-Command -Module util
CommandType Name ModuleName
----------- ---- ----------
Function Test-Function util
c:\> Test-Function
Call to Test-Function succeeded!
Questions
Is automatic discovery and module auto-loading supposed to be reliable and predictable?
How do I troubleshoot why powershell is sometimes doesn't find a command until a call to Get-Command -module?
Is it bad practice to rely on powershell to automatically load modules? If so, what is the good practice for automatically loading modules?

I can't speak to whether module auto-loading is intended to be reliable, but I personally do not rely on it in finished code.
If I'm writing a script or module, I always use Import-Module TheModule -ErrorAction Stop, and often use #Requires -Module AciveDirectory,TheModule,SQLPs to ensure that the modules are available.
For interactive use in the PowerShell console or ISE, I do generally rely on auto-loading, but if it fails I just Import-Module manually for the session.
In situations where I always want a specific module loaded for an interactive session, I load it in a profile. To see the various profiles run this (from both ISE and Console):
$profile | Get-Member -MemberType NoteProperty
You can decide where you want to place the code for importing the module based on which user(s) and in which host(s) you want the module to be available.
So far I only do this for posh-git, but it seems like it would fit your use case well.

In case this is helpful to anyone else, I think this may be a problem exclusive to ISE. I could not repro but recently went around with MS on inconsistent ISE workflow behavior and after some effort the issue was closed without a solution, with the official answer being to not use ISE which is not approved for production and instead use the native shell. It was a realistic answer for us, never saw the issues in native shell. I wonder if it's the same on this symptom?

I have found that the FunctionsToExport section in the module manifest cannot be set to *
THIS IS BAD:
# Functions to export from this module, for best performance, do not use
# wildcards and do not delete the entry, use an empty array if there are no
# functions to export.
FunctionsToExport = '*'
THIS IS GOOD:
# Functions to export from this module...
FunctionsToExport = 'Test-Function, Test-Function2'

Related

Powershell 7 import-module does not persist across sessions

If I open a new Powershell 7 session and run get-command Get-Website
the response I get is
Get-Command: The term 'Get-Website' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
If I then run import-Module WebAdministration and again run get-command Get-Website, this time I get the expectefd output of
CommandType Name Version Source
----------- ---- ------- ------
Function Get-Website 1.0 WebAdministration
If I close the Powershell 7 session, open a new one and run get-command Get-Website, the new session does not recognise the command.
If I run a session of Windows Powershell, I do not have to import the module, the command is already there.
Anyone able to explain what is going on?
Thanks
You are not the only person having this problem. Unfortunately, I can't speak to whether any of the solutions there work, as neither the asker or anyone else has confirmed them.
Note: I had originally suspected this might be a problem with automatic importing through the WindowsCompatibility module, but it appears that it does not interfere with auto-importing of modules. In addition, as of PowerShell 7 the WindowsCompatibility module features are baked into PowerShell itself.
For whatever reason, WebAdministration may not be able to be automatically imported in PowerShell Core. There are a few reasons for this but they are mostly on the module side, and you can't change the behavior without modifying the module.
You can try setting $PSModuleAutoLoadingPreference = 'All' in your current PowerShell session, but generally that doesn't need to be changed from the default value of ModuleQualified. More information on $PSModuleAutoLoadingPreference can be found here.
If that doesn't work, you'll have to manually import the module in every session.
Fortunately, manually importing modules isn't required for the vast majority of them. For the ones which can't be automatically imported, you must use Import-Module MODULENAME in each new session. You can simplify this by adding the Import-Module cmdlet to one of the following profile locations:
$profile.CurrentUserCurrentHost
$profile.CurrentUserAllHosts
$profile.AllUsersCurrentHost
$profile.AllUsersAllHosts
For scripts, Import-Module should generally be done inside the script to prevent needing profiles in scenarios where profile loading is desired to be disabled such as when PowerShell is invoked in the following way: powershell.exe -NoProfile ....
In your case, it would like like so:
Import-Module WebAdministration
Here is some additional information about the $profile variable.

Write to Profile File After Installing PowerShell Module with PowerShellGet

I have a custom PowerShell module with two cmdlets. I have it successfully, but manually, deployed on my machine. However, I deployed it by placing the binary file and module manifest in a location, and then registering the module. I also had to manually write an Import-Module command into my 'all users' profile.
Now I am sure I can deploy this module with Publish-Module, but how do I get the Install-Module to write the Import-Module statement to the profile file?
As of PowerShell 3.0, a module is automatically imported when a command from the module is invoked. This was a brilliant on Microsoft's part; however, it did require that modules are located in a location where PowerShell looks for modules by default. Makes sense. You can see those locations by running the following command:
$env:PSModulePath -split ';'
Is there a reason you'd rather not use one of the paths stored in the above environmental variable? That said, I'd keep your code out of the "C:\Windows\System32..." path. The other options are better: "C:\Program Files\PowerShell\Modules" (AllUsers) and "C:\Users\tommymaynard\Documents\PowerShell\Modules" (CurrentUser). Depending on your PowerShell version/OS, those path could be different. You won't need to write an Import-Module command into a $PROFILE script if you get the module into a preferred location. Maybe you already know this, but maybe not.
You're not going to get Install-Module to write to any of the $PROFILE scripts.
$PROFILE | Select-Object -Property *
Well, not by default anyway. You could write your own Install-Module function, that runs PowerShellGet's Install-Module function, and includes writing to various $PROFILE scripts. The problem is that you'll need to include logic so you don't blow away the contents of someone's $PROFILE script if it's not empty, and only append to it.
Seriously though, this is turning into a lot of work, when you could drop the module into a location where PowerShell can find it on its own.
Edit: It just occurred to me, you can add a value/path to the $env:PSModulePath environmental variable. It's a single string with semi-colon delimiters:
$env:PSModulePath.GetType().Name
Therefore, it'd look like this:
$env:PSModulePath += ';C:\Another\Path'
That's great and all, but again how might you stage this, right? It takes you back to the write-to-all-the-$PROFILE-scripts problem,... although you may be able to update the variable via Group Policy Preferences. Again, probably better to just relocate your module.

What is the purpose of ImportSystemModules?

What is the purpose of ImportSystemModules? Using help ImportSystemModules -Full produces an empty shell description.
PS 09:12 C:\src
>get-command *module*
CommandType Name Version Source
----------- ---- ------- ------
Function Find-Module 2.2.1 PowerShellGet
Function Get-InstalledModule 2.2.1 PowerShellGet
Function **ImportSystemModules**
Function InModuleScope 3.4.0 Pester
Function Install-Module 2.2.1 PowerShellGet
...
From Roger Lipscombe's blog, it used to do the following:
This runs Powershell.exe -ImportSystemModules, which, in turn, runs the ImportSystemModules command. You can call this command as part of your profile, if you want these modules loaded each time you run PowerShell.
Various places on the Internet state that it loads the available modules from C:\Windows\System32\WindowsPowerShell\v1.0\Modules. Among these are modules for managing IIS, Hyper-V, etc.
It turns out that it also loads snap-ins from C:\Users\rogerl\Documents\WindowsPowerShell\Snapins
However, according to another SO answer:
The -ImportSystemModules switch has no impact in v3, looks like it is going away.
And the Powershell changelog states for v6 beta 9 states the argument has been removed entirely:
Remove parameters -importsystemmodules and -psconsoleFile from powershell.exe. (#4995)
And indeed, running Get-Command ImportSystemModules on Powershell Core v6 cannot find the command anymore:
> Get-Command ImportSystemModules
Get-Command : The term 'ImportSystemModules' is not recognized as the name of a cmdlet, function, script file, or operable program.
As to why it's still there and defined in v5.1 and earlier? Perhaps it's for backwards compatibility, v2 compatibility was guaranteed for a long time.

Is there a way to determine which external assembly methods are being used in a Powershell module?

I currently have a Powershell module that references several external assemblies. Is there a way, other than manually reviewing the module, to determine which methods from those external assemblies are being used? I have a set of Pester unit tests for this module, and I would like to ensure that all external dependencies are being tested.
You say you have a PowerShell Module you are trying to examine.
If the PS module is a script module (.psm1), you can examine the script contents and trace each command.
Use Get-Command command to find out, which DLL is a command located in.
get-command test-path | fl DLL
DLL : C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerShell.Commands.Management\v4.0_3.0.0.0__31bf3856ad
364e35\Microsoft.PowerShell.Commands.Management.dll
Use Trace-Command to examine dependencies.
Trace-Command –Name CommandDiscovery –Expression {Your code here..} -PSHost
Using * as a Name parameter will display all information.
Trace_Command -Name *
Good starting reference can be found here Trace Your Commands by Using Trace-Command
In your comments you mentioned that you were trying to trace the statement
[System.IO.File]::Exists($filePath)
Because this is not a PowerShell command, but a direct call to .NET dll method, the above approach is not going to work.
You will have to examine .NET assemblies using "Reflector" software. There are a number of those, I personally like ILSpy which is free.
The same applies to PS binary modules (DLL). Use "Reflector" software to open, decompile and examine the DLL.

There is no Get-GacAssembly cmdlet

Windows 8.1, PowerShell 4. I'm wanting to use PS to manage the GAC. I find lots of references to Get-GacAssembly for reading a list, getting detailed info, etc.
But running PS as administrator, and PS ISE, I get an error:
>
The term 'Get-GacAssembly' is not recognized as the name of a cmdlet
Do I need to CD to somewhere? How would I discover where to go to find the cmdlets?
The general answer to your question is that you must first install the module (by adding relevant files to Documents\Powershell\Modules or Windows\System32\WindowsPowershell\v1.0\Modules. Then, you should use Import-Module to load the cmdlets. Cmdlets in Windows\System32\WindowsPowershell\v1.0\Modules should be loaded by default. Script modules (e.g., modules that export functions) require at least RemoteSigned execution settings to run.