How to export variables from a PowerShell module - powershell

I've defined a variable in a psm1 file but when I try to access it in another script, after importing the module, I'm not seeing the value set in the psm1 file.
globals.psm1
$blah = "hello world"
my-script.ps1
Import-Module "$PSScriptRoot\globals.psm1" -Force -Verbose
Write-Output "blah: ${blah}"
output
PS C:\blah> .\my-script.ps1
VERBOSE: Loading module from path 'C:\blah\globals.psm1'.
blah: ''
I thought all variables get exported by default. I must be interrupting this wrong:
Specifies the variables that the module exports to the caller's session state. Wildcard characters are permitted. By default, all variables ('*') are exported
source: MSFT Docs -> How to write a PowerShell module manifest
(CTRL + F on 'VariablesToExport' to find the quoted text)
And yes, if I export the variable, I can access it but the documentation says: 'By default, all varialbes ('*') are exported so what am I doing wrong or misunderstanding? 🤔
globals.psm1
$blah = "hello world"
Export-ModuleMember -Variable blah

Your module is not using a module manifest (a companion .psd1 file whose RootModule entry points to your .psm1 file in the case of script modules), whereas the documentation you quote pertains to module manifest-based modules.
If a module consists only of a .psm1 file, and that file contains no Export-ModuleMember calls, the following rule applies:
Only functions and aliases are automatically exported.
Conversely, this means: in order to also export variables, you must use an Export-ModuleMember call - and if you do, the slate is wiped clean, so to speak, and you must explicitly specify all definitions you want to export (in the simplest case, use Export-ModuleMember -Function * -Alias * -Variable *).
Also, be sure to place this call at the end of your .psm1 file, to ensure that all definitions to export have already been defined.
Caveat, if a manifest (.psd1) is used:
The manifest's *ToExport keys apply on top of what the .psm1 file - implicitly or explicitly - exports, i.e. you can use it to further narrow what is to be exported, by explicitly enumerating the elements to export, which not only makes the module more self-describing, but also helps performance when PowerShell auto-discovers the commands in available, but not-(yet)-imported modules.
Therefore, if a manifest-based module wants to export variables, it too must have an explicit Export-ModuleMember call in its .psm1 file, with the manifest potentially narrowing down what variables are ultimately to be exported.
Generally, exporting variables from modules is best avoided, because:
it increases the risk of name collisions with variables of the same name defined elsewhere.
discovering which variables are exported by what module isn't as well-known as use of Get-Command is in order to determine what module a given function, cmdlet, or alias comes from. This is because (a) modules that export variables are rare and users generally don't expect it, and (b) the Get-Variable cmdlet - which can tell you what module a variable is defined in - isn't often used in practice.
To see which definitions a given module exports, pass -Verbose to the Import-Module call that imports it. Additionally, pass -Force in order to force re-loading of an already imported module.

Related

A Variable from a module will not be exported

I have a PowerShell Modul: SQLModul.psm1 with a variable:
$logFile="C:\logfiles\SQLLog.txt".
I also have a manifest for this module: SQLModul.psd1 where I state:
VariablesToExport = '*'
When I Import this module: import-modul SQLModul all Functions but not this variable will be exported.
Thansk for your help.
Think of the module manifest as a facade or proxy in front of the module(s) it load(s) - the manifest exporting * variables will only affect whatever variables were already exported from a nested psm1 or managed module.
Place Export-ModuleMember -Variable list,of,variable,names at the bottom of your psm1 file, in your case:
# define module functions, variables etc.
# ...
Export-Module -Variable logfile
Placing Export-ModuleMember -Variable * in the psm1 should also work, although that's probably a pretty bad idea, and is generally considered an anti-pattern

How to expose sub module function, from a module

How do you expose a function and an alias from a module, from a sub module
profile.ps1:
Import-Module module_one.psm1
module_one.psm1:
Import-Module module_two.psm1:
module_two.psm1:
Set-Alias readme -Value "Read-Me"
function Read-Me() {
Write-Host "Hello..."
}
Export-ModuleMember -Function Read-Me, readme
I want to be able to call this function from the pwsh terminal, function name or alias?
I get (readme or Read-Me):
Read-Me : The term 'Read-Me' 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
+ co
+ ~~
+ CategoryInfo : ObjectNotFound: (co:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Note: it works for functions in module_one.psm1.
Assuming that you call Import-Module module_one.psm1 and later readme / Read-Me in the same scope domain (either outside a module or from the same (other) module), your code works in principle:
The exported elements of the indirectly imported module are (also) imported into the top scope of the caller's scope domain.
Your problem is that you're trying to export alias readme as a function; you need to use the separate -Alias parameter instead:
Export-ModuleMember -Function Read-Me -Alias readme # Note the -Alias parameter
Also note that in the absence of an Export-ModuleMember call it is all (top-level) functions and aliases (but not variables) that are exported by default, so in your particular case simply omitting the call would have fixed the problem too.
In general, though, it is better to be explicit about what elements are exported, and that is best done via a full-fledged module that is not just a single *.psm1 file, but an entire directory named for the module, housing the *.psm1 file with an associated module manifest, which is a *.psd1 file that describes the module, notably also in terms of its exports.
If such a module is placed in one of the directories listed in $env:PSModulePath, its exported commands can be discovered (e.g., with Get-Command or tab-completion) even before the module is imported, via a feature called auto-loading - see about_Modules.
Note that Import-Module -Global is rarely needed and should generally be avoided, because it makes a module's exported elements available to all loaded modules too, which can have unintended side effects (modules should declare their dependencies explicitly).

What is the purpose of the *.psm1 files in a Powershell module?

So I implemented my first Powershell module with a bunch of .ps1 files (one per function) and the .psd1 manifest file.
I am trying to understand what is the purpose of the .psm1 files - do I need them at all in my module?
What is their added value?
EDIT 1
Here is my .psd1 file:
#{
ModuleVersion = "0.0.19106.59054"
GUID = "59bc8fa6-b480-4226-9bcc-ec243102f3cc"
Author = "..."
CompanyName = "..."
Copyright = "..."
Description = "..."
ScriptsToProcess = "vsts\config.ps1"
VariablesToExport = #(
"TfsInstanceUrl",
"TfsApiVersion",
"QANuGetRepoUrl"
)
NestedModules = #(
"db\Backup-Database.ps1",
...
"vsts\work\Get-WorkItems.ps1"
)
FunctionsToExport = #(
"Assert-ExtractionDestFolder",
...
"Write-HostIfNotVerbose"
)
PrivateData = #{
PSData = #{
ExternalModuleDependencies = "SqlServer"
}
}
}
Like I said, each function is in its own file.
what is the purpose of the .psm1 files - do I need them at all in my module?
In script modules, i.e., modules authored in PowerShell (as opposed to compiled binary cmdlets), it is only *.psm1 files that provide the module-specific behaviors distinct from regular *.ps1 script files (separate, isolated scope, private commands, control over exported commands).
Typically, a script module manifest has a RootModule entry pointing to (the main) *.psm1 file; for smaller modules it is not uncommon for all of the module's functionality to be implemented in this one *.psm1 file.
In fact, a stand-alone *.psm1 file can also act as a module, though it doesn't integrate with PowerShell's module auto-discovery and auto-loading feature.
Note that if you were to use a regular *.ps1 script directly in RootModule, its definitions would be loaded into the caller's scope, not the module's; that is, you would lose the benefits of a module.
Even though you're listing regular *.ps1 scripts in your NestedModules manifest entry, by virtue of using that specific entry these scripts are dot-sourced in the module's context and thereby become part of the module:
This is conceptually equivalent to creating and referencing a root *.psm1 script in RootModule, and - instead of defining a NestedModules entry - explicitly dot-sourcing your *.ps1 scripts from there - see bottom section.
Note that if you were to reference *.psm1 files in NestedModules, they would truly become nested modules, with their own scopes; a nested module is usable from the enclosing module, but not visible to the outside world (though you can list it among the loaded modules with Get-Module -All).
Listing *.ps1 files in NesteModules vs. dot-sourcing them from the RootModule
While there should be no difference in functionality, using a *.psm1 RootModule to dot-source the *.ps1 files containing your module's functions can potentially simplify things, if you simply need to dot-source all *.ps1 files located in your module directory's subtree:
# Add this to the *.psm1 file specified in 'RootModule'
# Find all *.ps1 files in the module's subtree and dot-source them
foreach ($script in
(Get-ChildItem -File -Recurse -LiteralPath $PSScriptRoot -Filter *.ps1)
) {
. $script
}
If you need to load the scripts in a specific order, need only a subset of the scripts, or want to speed things up slightly (though I doubt that the speed difference will be noticeable), you can dot-source the files individually from a RootModule *.psm1 file, as an alternative to listing them in the NestedModules entry:
# Add this to the *.psm1 file specified in 'RootModule'
# Dot-source the *.ps1 files individually.
. "$PSScriptRoot/db/Backup-Database.ps1"
# ...
. "$PSScriptRoot/vsts/work/Get-WorkItems.ps1"
Again, all of the above approaches are functionally equivalent.
Given that you explicitly export functions via the ExportedFunctions entry (as is advisable), the use of *.ps1 files that must be dot-sourced is ultimately an implementation detail that is not relevant for the purposes of command discovery and module auto-loading - it only matters at actual import time.
.psm1 file is powershellmodule file. When we create script module we write all the functions of a module in a .psm1 file then we export the functions and then we can use those functions by importing module.
.psm1 is basically refers the powershellmodule. Powershell directly identifies anything which is written in this file will be a part of module.

How do I make my own alias override a module's alias?

I have an alias for touch created in my $profile, however I also use PSCX seems to have it's own touch. When I run touch the PSCX one runs:
$ which touch
Pscx\Set-FileTime
How do I make PowerShell prefer my alias over the modules alias?
In your $PROFILE:
First, explicitly import the PSCX module - at that point, that module's touch alias is imported and would replace any existing definition: Import-Module PSCX
Note: Add Import-Module PSCX even if it currently isn't in your $PROFILE; typically, the module is auto-loaded, on demand, by virtue of being located in one of the directories listed in the $env:PSModulePath environment variable.
It is that on-demand importing - whose timing is unpredictable (whenever you first happen to call a command from the module) - that prevents reliable alias replacement; forcing import of the module in your $PROFILE before you define your version of the alias solves that problem.
Then redefine the alias as needed; e.g.: Set-Alias touch touch.exe
Caveat: If someone forcefully re-imports the module later with Import-Module -Force PSCX, your alias will again be replaced.
Note: It's not necessary for the specific alias in question, but in order to replace aliases that are defined with -Option AllScope (true for built-in aliases such as dir and select), you're faced with two suboptimal choices:
Either: Redefine it with -Option AllScope, which, however, means that all scopes will see the new definition; e.g., Set-Alias -Option AllScope touch touch.exe.
Or: Remove the original alias first - e.g., Remove-Item alias:touch (PSv6+: Remove-Alias touch) - and then define it as needed, which, however, means that other scopes will no longer see the original definition.
Making this an answer rather than comment so I can type more text.
See https://serverfault.com/questions/452430/how-to-override-the-default-dir-alias-in-powershell
Opinion follows:
However, depending on your usage, I would strongly recommend against using aliases in any script or module as they can be changed and may not perform the actions you want.
If you really want to use an alias, I might just avoid all this and create a non-conflicting alias that still makes sense. Maybe tf or tfile for TouchFile.

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.