Make PowerShell script run cmdlets in global scope - powershell

I have written the following PowerShell script:
function Reload-Module ([string]$moduleName) {
$module = Get-Module $moduleName
Remove-Module $moduleName -ErrorAction SilentlyContinue
Import-Module $module
}
The only problem with this script is that Import-Module only applies inside that script's scope - it does not import the module in the global scope. Is there any way to make a script import a module so that it stays around after the script finishes?
Note: dot-sourcing like so: . Reload-Module MyModuleName does not work.

From the Powershell help:
-Global [<SwitchParameter>]
Imports modules into the global session state so they are available to all commands in the session. By
default, the commands in a module, including commands from nested modules, are imported into the
caller's session state. To restrict the commands that a module exports, use an Export-ModuleMember
command in the script module.
The Global parameter is equivalent to the Scope parameter with a value of Global.
Required? false
Position? named
Default value False
Accept pipeline input? false
Accept wildcard characters? false
v3 also adds the -Scope parameter, which is a little more general:
-Scope <String>
Imports the module only into the specified scope.
Valid values are:
-- Global: Available to all commands in the session. Equivalent to the
Global parameter.
-- Local: Available only in the current scope.
By default, the module is imported into the current scope, which could be
a script or module.
This parameter is introduced in Windows PowerShell 3.0.
Required? false
Position? named
Default value Current scope
Accept pipeline input? false
Accept wildcard characters? false
Note: the above help snippets are from v3.0 which is what I have installed on my system. The v2.0 help is available at http://msdn.microsoft.com/en-us/library/windows/desktop/dd819454.aspx. I'd heartily recommend getting PowerShell v3.0 if you can, if only because of the new ISE.

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

PowerShell: Controlling verbose output in a module

So I have this module I will share with a group of people . I want all the output for all the cmdlets IN the module to be have like their where passed -Verbose. Without requiring the user to actual pass -verbose. I do not want -verbose output of any other modules cmdlets I call into.
So I tried $Global:VerbosePreference = "Continue" , and then explicitly -verbose:$false for cmdlets I all into. But it seems the global version overrides the specific version , and I get way to much verbose output.
Is this possible ?
My module is a multi file module , it has over 10 ps1 in it.
Add $VerbosePreference = 'Continue' to the top-level scope of the *.psm1 script-module file that is referenced in the RootModule entry of your module's manifest file (*.psd1).
This scopes the preference to the commands in your module, without affecting code outside of it.

Import-Module not working when called after a param() statement

I've a script which uses a parameter to pass details to it, and which needs to import the WebAdministration module.
The start of the script is :
param(
[parameter(position=0)]
[string]$iisAppName
)
Import-Module -name WebAdministration
however when I run the script I get errors from those cmdlets which use the module saying they're not found, since the module obviously hasn't been loaded.
If I put the Import-Module statement before the param() then the parameter isn't loaded. If I don't have the param() statement at all it works fine.
This script is for removing a website, but the companion creation script (which doesn't use param) works fine. In fact if I run that one it works, and if I then run this one (where the module is still loaded from the first) it works fine (annoyingly... since I didn't spot the issue in testing!), so I know I'm calling those cmdlets correctly.
Is there an alternate way I need to call one or both of these to allow both of them to work in my script?
I think this is to do with session states but would need more information to be sure.
https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/import-module#-global
By default, the commands in a module, including commands from nested modules, are imported into the caller's session state.
When you import a module from the global session state, it's available to the console and all modules. When the module is imported from another module, it will only be available to the module(s) that imported it. I think when you include Params it treats it differently, perhaps running it in a script state session instead of the global state session.
Try using Import-Module -Name WebAdministration -Global which, regardless of where it is called, should import it into the global state session and make it available to everything.

How can I use a PowerShell module in a script without leaving the module loaded in the user's session?

I have a script I wish to use interactively from the PowerShell prompt. The script needs to use a local script module.
I cannot see how to import/use the module such that it's not left loaded in the current session.
Example
A module (MyModule.psm1)...
function Test-Method
{
write-host "Test-Method invoked"
}
... and a script (script.ps1)
Import-Module .\MyModule
Test-Method
Now running the script at the PowerShell prompt ...
PS C:\temp> Get-Module | % {$_.Name}
Microsoft.PowerShell.Management
Microsoft.PowerShell.Utility
PS C:\temp> .\script.ps1
Test-Method invoked
PS C:\temp> Get-Module | % {$_.Name}
Microsoft.PowerShell.Management
Microsoft.PowerShell.Utility
MyModule
How can my script import and use MyModule.psm1 without it being left loaded in the caller's current session? Bearing in mind that the call may have already imported the module and would not want it unloaded by the script (so simply removing the module at the completion of the script is not really good enough).
I've considered dot-sourcing the module rather than importing it, but I want the module for the reasons covered in PowerShell Import-Module vs Dot Sourcing
It sounds like you already described in pseudo-code what you wanted. Here it is in actual code:
$checkCmds = Get-Commands -Module MyModule
Import-Module MyModule
# Do stuff here . . .
# unload only if we loaded it
if ($checkCmds -eq $null) { Remove-Module MyModule }
As far as I can tell, you don't get that automatic cleanup behavior from a "script" importing a module. OTOH if you import a module from within another module, when the parent module is removed then any modules it imported will be removed if there are no other modules using them (or unless ipmo -global was specified).
This builds on the previous answer and uses the following property.
If you import a module from within another module, when the parent module is removed then any modules it imported will be removed
You can exploit several techniques to create a wrapper:
Importing a module from a module
Anonymous modules
Call operator with the context of a module
Set script.ps1 to
& (New-Module {
function Invoke-Function {
Import-Module .\MyModule
Test-Method
}
}) { Invoke-Function }
If you run script.ps1 and then (Get-Module).Name then MyModule will not be listed in the output.
Note: In this example Invoke-Function is just another scope, and can be omitted, letting the New-Module just run when defined. In one line:
& (New-Module { Import-Module .\MyModule; Test-Method }) {}
You can import the module with -Scope local to restrict a module to your script's scope. If the module happens to also be loaded in the global scope, then it will still be available after your script exits.

PowerShell Add-PSSnapIn from an advanced (cmdlet) function

I would like to create an advancedmodule with a cmdlet function which performs some logic and adds some pssnapins. This is the code:
function Add-DefaultSnapIns
{
[CmdletBinding()]
param()
begin {}
process {
# ...
Add-PsSnapIn SnapInName
}
end {}
}
export-module -function Add-DefaultSnapIns
If I invoke the function from any point (for instance, a powershell prompt), the operation succeeds, but the snapin is not available outside of the scope of the function. The snap-in appears registered, but none of its functions have been exported to the global scope. How could I solve it?
Well the idea is that Modules are self-contained and don't spill too much of their "stuff" into the global session space except the cmdlets, functions and aliases they export. It might be better to add the snapins yourself as part of the module initialization and then export those snapins' cmdlets via Export-ModuleMember.