How do I force powershell to reload a custom module? - powershell

I have created a module 'ActiveDirectory.psm1' which contains a class in powershellv5. I am importing that module in another file called 'test.ps1' and then calling a method from the class.
test.ps1 contains the following:
using module '\\ser01\Shared\Scripts\Windows Powershell\modules\ActiveDirectory\ActiveDirectory.psm1'
Set-StrictMode -version Latest;
$AD = [ActiveDirectory]::New('CS');
$AD.SyncGroupMembership($True);
It all works as expected BUT when I make a change to ActiveDirectory.psm1 & save the changes they aren't reflected immediately. i.e. if ActiveDirectory.psm1 contains:
write-verbose 'do something';
If I change that to
write-verbose 'now the script does something else';
the output remains 'do something'
I'm guessing it has stored the module in memory and doesn't reload it therefore missing the changes I have made. What command do I need to run to load the most recent saved version of the module?

As suggested by wOxxOm, you can try pass the -Force flag:
Import-Module ... -Force
Or if that does not work try to explicitly remove it and then reimport with:
Remove-Module

From what I've gathered. Import-Module does not import classes. You have to use the "using module " and it has to be in the first line of your script. On top of that problem, the classes appear to be "cached" in some esoteric way that precludes any uninstall-module or remove-module options. I've found I basically need to restart my powershell terminal to clear it.
If classes are not involved use import-module OR install-module. In both cases you can do a get-modules -all or get-installedmodule and then remove-module or uninstall-module. You want to make sure you look for all versions and pipe that to remove/uninstall to ensure you wipe everything out.

For anyone else coming across this issue, see https://github.com/PowerShell/PowerShell/issues/2505
It seems that there is a known long-standing bug regarding importing of modules that are anything above rudimentary level in complexity (for example, I have a module with a single class and class method that fails to update).

Related

Why do I have to open a new powershell window to get the changes to my psm1 files? [duplicate]

I created a custom powershell module in the
C:\Program Files\WindowsPowerShell\Modules\PennoniAppManagement directory. Whenever I make changes to a function in the module, then import the module into a script, the updated code won't take effect. Any solutions?
Make sure you remove the already-loaded version of the module from the session before re-importing it:
Remove-Module PennoniAppManagement -Force
Import-Module PennoniAppManagement
Normally, Import-Module-Force - by itself - is enough to force reloading of an updated module into the current session.
Import-Module -Force implicitly performs Remove-Module before reloading the module (if the module isn't currently loaded, -Force just loads the module normally).
Also note that force-reloading a module is not an option if you're loading it via a using module statement (at least as of PowerShell 7.1.2). Notably the using module method of importing is required if a module exports custom class definitions that the caller should see - see this answer for details.
Mathias' two-step approach - Remove-Module -Force, followed by Import-Module - is apparently needed in some cases, and seems to be required in yours.
It would be good to understand when the two-step approach is needed. Mathias thinks it is related to cached versions of custom class definitions (used module-internally) lingering instead of getting reloaded and redefined when Import-Module -Force is called. That is, while the module overall may get reloaded, it may be operating on stale classes. At least in the simple scenario below I was not able to reproduce this problem, neither in Windows PowerShell 5.1, nor in PowerShell (Core) 7.2.1, but there may be scenarios where the problem does surface.
The Remove-Module documentation describes the -Force parameter solely as relating to the - rarely used - .AccessMode property available on a loaded module's module-information object (you can inspect it with (Get-Module ...).AccessMode). The default value is ReadWrite, which allows unloading (removal) of the module anytime. If the property value is ReadOnly, Remove-Module -Force is needed to unload; if it is Constant, the module cannot be removed from the session at all, once loaded - at least not with Remove-Module.
Notably, the implicit unloading that happens with Import-Module -Force is not subject to these restrictions and implicitly unloads a module even if its .AccessMode is Constant (as of PowerShell 7.1.2; I am unclear on whether that is by design).
Test code involving reloading a module with a modified class definition, to see if Import-Module -Force is enough:
# Create a template for the content of a sample script module.
# Note: The doubled { and } are needed for use of the string with
# with the -f operator later.
$moduleContent = #'
class MyClass {{
[string] $Foo{0}
}}
function Get-Foo {{
# Print the property names of custom class [MyClass]
[MyClass]::new().psobject.Properties.Name
}}
'#
# Create the module with property name .Foo1 in the [MyClass] class.
$moduleContent -f 1 > .\Foo.psm1
# Import the module and call Get-Foo to echo the property name.
Import-Module .\Foo.psm1; Get-Foo
# Now update the module on disk by changing the property name
# to .Foo2
$moduleContent -f 2 > .\Foo.psm1
# Force-import (reload) the module and
# see if the property name changed.
Import-Module -Force .\Foo.psm1; Get-Foo
# Clean up.
Remove-Item .\Foo.psm1
In both Windows PowerShell (whose latest and last version is v5.1) and PowerShell (Core) 7.2.1 (current as of this writing), the above yields, as expected:
Foo1 # Original import.
Foo2 # After modifying the class and force-reloading

Difference between using module, Import-Module, and #requires -Modules

Is there any detailed reference for how these 3 different methods for importing PowerShell modules work? I'm currently seeing different behavior with using module vs Import-Module in a script.
It seems importing dependencies works differently. Using Import-Module in order of dependencies can resolve the issue, but with using module it doesn't appear to be able to resolve dependencies.
Is this script defendant on how the import statements are created or is there a documented difference in how these different commands work?
I didn't find any guidelines either, but I made the following comparison to make some sense of what is what.
Import-Module
Well, it's a cmdlet. That means it
Accepts pipelines: 'PSReadLine','PSColor' | Import-Module
Accepts splatting: $params = #{Name = 'PSReadLine'; OutBuffer = 1} ; Import-Module #params
Supports flags and parameters: Import-Module -PassThru PSReadLine
Can be called almost everywhere: function Load {Import-Module PSReadLine}
Therefore, it is well suited for ad-hoc module loading and dynamic reloading.
using module
using is a keyword, so it's not something you can pass around like Import-Module. It doesn't take parameters the same way as cmdlets do, and it does not work with pipelines. In short, it's a primitive. Yet another limitation is that it has to be placed on top of the script, before all other statements.
A case when you need to use using module is when you want to load classes and enums. Neither Import-Module nor #Requires will add classes defined in a module into your scope. Generally speaking, it's designed for casual module loading.
#Requires -Modules
This is used to assert that certain modules are loaded (amongst others). In contrast to the rest, this command fails if the module cannot be loaded with Import-Module. Another difference is that it works only in scripts -- it does nothing in shell.

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.

Is there ever a reason to explicitly Import-Module?

I was just reading the PowerShell Modules guide page and I noticed a line on the Import-Module section:
The following actions trigger automatic importing of a module, also
known as "module auto-loading."
Using a cmdlet in a command. For
example, typing Get-ExecutionPolicy imports the
Microsoft.PowerShell.Security module that contains the
Get-ExecutionPolicy cmdlet.
So given that, why should we ever care about using Import-Module? Isn't it always taken care for us automatically? In what case would I need to explicitly write out Import-Module?
You have to use Import-Module in the following cases :
The module file is not in a path included in $PSModule Path
You have different modules with the same name but in different paths
The module is already loaded and you want to reload it after making modifications to it. (with -Force)
To import only specific cmdlets, functions or variables from that module (with the -Cmdlet, -Function, and -Variable parameters respectively)
To prevent loading cmdlets or functions from the module that would overwrite the commands with the same name and are already loaded in the current session ( with -NoClobber )
To add a prefix to the nouns of the cmdlets in this module ( with -Prefix)
To import a module from a remote computer (with the -PSSession parameter )
The list is not totally exhaustive but these are the main use cases for the Import-Module cmdlet.
I know there is already an accepted answer, but I wanted to add my two cents.
To explicitly document the dependency of a script upon a module
If $PSModuleAutoloadingPreference is set to "none", modules need to be explicitly loaded. You don't know if users have turned this off or not.

Suppressing VERBOSE for Import-Module

I'm importing Carbon into my PowerShell script; however when running my script with -Verbose, Carbon also outputs a lot of VERBOSE statements.
Is it possible to Import-Module silently such that I can ignore the verbose statements in the imported module and leave just my own?
Try Import-Module Carbon -Verbose:$false
I could not get the solutions above to work with all modules (I'm using Powershell 4.0). This is the solution I ended up using and so far it has worked with every module I've used:
At the top of my script file I have this, to make the -Verbose work for the script (the script has no parameters):
[CmdletBinding()]
Param()
Then when I'm ready to import the modules, I do this:
$SaveVerbosePreference = $global:VerbosePreference;
$global:VerbosePreference = 'SilentlyContinue';
Import-module "Whatever";
$global:VerbosePreference = $SaveVerbosePreference;
Then I just call the script like so:
PowerShell -file something.ps1 -Verbose
Import-Module Carbon -Verbose:$false | Out-Null
I think a better solution than the one which is marked here is to redirect the verbose output to a different stream. This way you can print the output if you need it and it doesn't get swollen for ever:
Import-Module Carbon 4>&5
This redirects the verbose stream (4) to the debug stream (5). When you run your script with the Verbose switch, it will not output the verbose lines from Import-Module, but you can bring it back by running your script with the -Debug switch.
As Carbon seems to be a script module, can you try to set the $script:VerbosePreference (or just $VerbosePreference) to 'SilentlyContinue' inside the module itself carbon.psm1. The module scope should do the trick.
First contribution, I hope this helps.
ipmo $dir\$i 3>$null
ipmo: Short-hand/alias for Import-Module
3>$null: Redirect warnings messages to null
Edit:
I wanted to share a table I found at work while I was looking for the best way to help explain this... But I cant find it now. However, the quick an dirty is you may have already noticed that ipmo doesn't act like the rest of those PWshell cmdlet. It has a total of 4 streams in the bg.
I don't remember 1 and 2. 3 is warning. 4 though, 4 is not necessarily error, but it is error at the same time? I don't remember the exact wording. It is something that bypasses errors.
If I do find that award winning table again I'll be sure to share the blessing.