Function in .psm1 not getting exported - powershell

I have a myFunc.psm1 file like this:
$ApiVersion = "201846465"
Export-ModuleMember -variable ApiVersion
function Get-Something {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Id]
)
process {
# ...
}
When I import this to another setup.ps1 file, I saw this in execution:
VERBOSE: Loading module from path 'D:\myFunc.psm1'.
VERBOSE: Importing variable 'ApiVersion'.
VERBOSE: Hi from setup.ps1
When I remove
$ApiVersion = "201846465"
Export-ModuleMember -variable ApiVersion
I can see:
VERBOSE: Exporting function 'Get-Something'.
VERBOSE: Importing function 'Get-Something'.
VERBOSE: Hi from setup.ps1
Why is this happening and how do I fix it?

In the absence of a Export-ModuleMember call inside a *.psm1 file, all functions and aliases[1] are automatically exported - but not variables.
Once you use an Export-ModuleMember call, the automatic exporting is deactivated, and you must then explicitly name all elements to be exported - including functions and aliases.
Therefore:
Export-ModuleMember -Variable ApiVersion -Function Get-Something
Be sure to place the Export-ModuleMember call at the bottom of your file, to make sure that all elements you want to export have already been defined - otherwise, they're ignored.
[1] Curiously, in dynamic modules created with New-Module, it is functions only (not also aliases) that are automatically exported.

Related

I created a custom powershell .psm1 module but it won't update after an edit

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: Export-ModuleMember with alias; set Scope

I have a Powershell module (.psm1 and .psd1) with several functions and aliases in it.
I added aliases like this:
function MyFunction {
<#
.SYNOPSIS
A Method.
.DESCRIPTION
Method does something.
#>
[Alias("MyAlias")]
param(
[String][Parameter(Mandatory = $true)] $ParamOne,
[String][Parameter(Mandatory = $false)] $ParamTwo
)
# Do Something
}
Later I export the function and the alias like this:
Export-ModuleMember -Function MyFunction -Alias MyAlias
After importing the module (Import-Module (Join-Path $env:MODULE_PATH 'MyModule') -Force), I am able to call MyFunction but I can't call MyAlias, as it is not found.
In the psd1-File I exported all aliases like this AliasesToExport = '*' or this AliasesToExport = #('MyAlias'). I still can't call MyAlias.
However it works, when I remove the Alias-Annotation from the function and export the function and alias like this:
Export-ModuleMember -Function MyFunction
New-Alias -Name MyAlias -Value MyFunction -Scope 'Global' -Force
It only works, when the Scope is set to Global. So my conclusion is that the Export-ModuleMember has a lower scope for aliases.
I think that the initial version has worked before, probably with an earlier version of powershell (but I'm not sure about that...).
Now my question: Can I set the scope for exporting aliases with Export-ModuleMember, or can I set the scope probably in my psd1 file?
Or do I have to change all my Export-ModuleMembers and add New-Alias calls everywhere (There are quite a lot).

Is there a way for a module to create its required assemblies when it is first run?

I am trying to write a PowerShell module that relies on a small assembly. The assembly is created from a C# file (add-type -typeDefinition (get-content -raw xyz.cs) -outputAssembly xyz.dll -outputType library). Because the module requires this assembly, I have to create the assembly manually when I install the module. I am now wondering if it is possible to have PowerShell execute this add-type ... step (or any other module initialization step) automatically when it is first used
Note:
As Mathias R. Jessen points out, there is no strict need to write a helper assembly to disk - creating it in memory is sufficient for your use case (omit the -OutputType and -OutputAssembly arguments, and skip the Add-Type -LiteralPath call in the code below).
The reason that this suffices is that the existence of types used to declare parameters in a function isn't enforced at the time the function is defined (and exported), but rather at the time it is called (or its help is invoked or its name is passed to Get-Command -Syntax).
However, you may still want to use the disk-based approach, shown below, so as to incur the performance penalty that the compilation incurs only once per machine, the very first time you import the module.
Consider also implementing a versioning mechanism so that updates to your module that require an updated type definition recreate the helper assembly when needed.
The following proof of concept uses a stand-alone script module (./foo.psm1) whose top-level code tests for the existence of helper assembly foo.dll in the same location as the module, and, if not found, creates it with Add-Type.
The assembly defines sample type [demo.Foo], which the Use-Foo module function exported by the module uses as a parameter type.
In real-world usage, with a directory-based module, you'd simply point your module manifest's (*.psd1 file's) RootModule entry to the equivalent of the foo.psm1 file.
# Create demo module ./foo.psm1
#'
# Deactivate this to silence the verbose messages.
$VerbosePreference = 'Continue'
$dllPath = "$PSScriptRoot/foo.dll"
if (-not (Test-Path $dllPath)) {
Write-Verbose "Creating assembly $dllPath..."
Add-Type -ErrorAction Stop -OutputType Library -OutputAssembly $dllPath '
namespace demo {
public class Foo {
public string Bar { get { return "I am a Foo instance."; } }
}
}
'
}
else {
Write-Verbose "Using preexisting $dllPath assembly."
}
Write-Verbose "Loading assembly $dllPath..."
Add-Type -ErrorAction Stop -LiteralPath $dllPath
# Define the function to be exported, whose parameter
# uses the type defined by the helper assembly.
function Use-Foo {
param(
[demo.Foo] $foo
)
$foo.Bar
}
'# > ./foo.psm1
# Import the module, at which point the top-level code runs,
# which either creates the helper assembly or loads a previously
# created copy.
Import-Module -Force -Verbose ./foo.psm1
Write-Verbose -vb 'Calling Use-Foo...'
Use-Foo ([demo.Foo]::new())
Running the above for the first time yields the following, proving that the assembly was created on demand, loaded, and that use of the assembly-defined type as the parameter type of the exported Use-Foo function succeeded:
VERBOSE: Loading module from path '/Users/jdoe/demo/foo.psm1'.
VERBOSE: Creating assembly /Users/jdoe/demo/foo.dll...
VERBOSE: Loading assembly /Users/jdoe/demo/foo.dll...
VERBOSE: Exporting function 'Use-Foo'.
VERBOSE: Importing function 'Use-Foo'.
VERBOSE: Calling Use-Foo...
I am a Foo instance.

How to make sure PowerShell module manifest (.psd1) is used

I created a PowerShell module MyUtil.psm1 and a manifest file MyUtil.psd1 for it. In the psd1 file it has the prefix defined to prevent name conflicts for exported functions:
DefaultCommandPrefix = 'MyToolbox'
This way, after running Import-Module .\MyUtil.psd1, a function like Get-Command in the psm1 file will be Get-MyToolboxCommand, everything is fine. But if someone runs Import-Module .\MyUtil.psm1 to import the psm1 file directly, the psd1 file is simply not used and the prefix I want won't be applied.
If I want to prevent this (importing MyUtil.psm1 directly), is there a way to ONLY allow importing the corresponding psd1 manifest file instead of the psm1 file? Or a programmatic way to detect that this module was not imported through psd1 so I can warn the user to use psd1?
Ok, this is a bit annoying, but it works. You can use the Export-ModuleMember cmdlet with no args to stop the psm1 from exporting anything, but you need to send a value from the psd1 to the psm1 during the import. That way we know when we're being called with the psd1. So first off, add a value to the PrivateData hashtable in your PSD:
PrivateData = #{
FromPSD = $true
PSData = #{
Then you need to access it in the psm1 file. You can in a function, but not inline, so we have to stick it into a function.
function Get-PD
{
[CmdletBinding()]
$MyInvocation.MyCommand.Module.PrivateData
}
(I totally stole this from this SO answer Accessing PrivateData during Import-Module).
Then you wrap it all up by calling this code in the module which will get run as the module is loaded.
$MyPD = Get-PD
if($MyPD.Count -eq 0)
{
Export-ModuleMember
}
Now, if you don't want this pesky extra function to be referenced when the module is loaded, you'll need to populate the FunctionsToExport in the psd1 file with the list of functions you want the users to have access to.
You can use: #Requires
For example:
Require that Hyper-V (version 1.1 or greater) is installed.
#Requires -Modules #{ ModuleName="Hyper-V"; ModuleVersion="1.1" }
Requires that Hyper-V (only version 1.1) is installed.
#Requires -Modules #{ ModuleName="Hyper-V"; RequiredVersion="1.1" }
Requires that any version of PSScheduledJob and PSWorkflow, is installed.
#Requires -Modules PSWorkflow, PSScheduledJob
About Requires

Creating functions dynamically in a module in PowerShell

Suppose I have the following code in a module (called MyModule.psm1, in the proper place for a module):
function new-function{
$greeting='hello world'
new-item -path function:\ -name write-greeting -value {write-output $greeting} -Options AllScope
write-greeting
}
After importing the module and running new-function I can successfully call the write-greeting function (created by new-function).
When I try to call the write-greeting function outside the scope of the new-function call, it fails because the function does not exist.
I've tried dot-sourcing new-function, but that doesn't help. I've supplied the -option Allscope, but apparently that only includes it in child scopes.
I've also tried explicitly following the new-item call with an export-modulemember write-greeting which doesn't give an error, but also doesn't create the function.
I want to be able to create a function dynamically (i.e. via new-item because the contents and name of the function will vary based on input) from a function inside a module and have the newly created function available to call outside of the module.
Specifically, I want to be able to do this:
Import-module MyModule
New-Function
write-greeting
and see "hello world" as output
Any ideas?
Making the function visible is pretty easy: just change the name of your function in New-Item to have the global: scope modifier:
new-item -path function:\ -name global:write-greeting -value {write-output $greeting} #-Options AllScope
You're going to have a new problem with your example, though, because $greeting will only exist in the new-function scope, which won't exist when you call write-greeting. You're defining the module with an unbound scriptblock, which means it will look for $greeting in its scope (it's not going to find it), then it will look in any parent scopes. It won't see the one from new-function, so the only way you'll get any output is if the module or global scope contain a $greeting variable.
I'm not exactly sure what your real dynamic functions will look like, but the easiest way to work around the new issue is to create a new closure around your scriptblock like this:
new-item -path function:\ -name global:write-greeting -value {write-output $greeting}.GetNewClosure()
That will create a new dynamic module with a copy of the state available at the time. Of course, that creates a new problem in that the function won't go away if you call Remove-Module MyModule. Without more information, I'm not sure if that's a problem for you or not...
You were close with needing to dot source, but you were missing Export-ModuleMember. Here is a complete example:
function new-function
{
$greeting='hello world'
Invoke-Expression "function write-greeting { write-output '$greeting' }"
write-greeting
}
. new-function
Export-ModuleMember -Function write-greeting
You also did not need or want -Scope AllScope.
Using the global: scope qualifier appears to work, but isn't the ideal solution. First, your function could stomp on another function in the global scope, which modules normally shouldn't do. Second, your global function would not be removed if you remove the module. Last - your global function won't be defined in the scope of the module, so if it needed access to non-exported functions or variables in your module, you can't (easily) get at them.
Thanks to the other solutions i was able to come up with a little helper that allows me to add plain script-files as functions and export them for the module in one step.
I have added the following function to my .psm1
function AddModuleFileAsFunction {
param (
[string] $Name,
[switch] $Export
)
$content = Get-Content (Join-Path $PSScriptRoot "$Name.ps1") -Raw
# Write-Host $content
$expression = #"
function $Name {
$content
}
"#
Invoke-Expression $expression
if ($Export) {
Export-ModuleMember -Function $Name
}
}
this allows me to load scripts as functions:
. AddModuleFileAsFunction "Get-WonderfulThings" -Export
( loads Get-WonderfulThings.ps1 body and exports it as function:Get-WonderfulThings )