Reputation: 11104
I've PowerShell Module
(lets call it PSModule
) in which I've defined as follows in .psd1
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
ScriptsToProcess = @('Enums\MessageType.ps1')
This is added there so that the file MessageType.ps1
that holds the just Enums
can be reused across multiple files.
This file looks like:
enum MessageType {
None
Something
}
An example of this is PSWriteWord
- https://github.com/EvotecIT/PSWriteWord module. This is supposed to keep Enums in it's own folder and still load them when module is started.
This works fine most of the time. I've run scripts without issues. Now I've a weird situation where I've created: script1.ps1 - that I call.
Content of script1.ps1
Import-Module PSModule -Force
Import-Module PSOtherModule -Force
Call-Me -Parameters <params> # part of PSOtherModule
Now within Call-Me function from PSOtherModule I call
Do-Something
do-Something
Call-OtherFunction # function from PSModule
It will work when running in ISE or VSCode...
Now if I rerun the same script from Task Scheduler
it won't load the MessageType
which essentially will fail at some point. It seems that it simply skips the processing of ScriptsToProcess
.
Now if you do:
Do-Something
do-Something
Import-Module PSModule
Call-OtherFunction # function from PSModule
It still won't work ... But this will...
Do-Something
do-Something
Import-Module PSModule -Force
Call-OtherFunction # function from PSModule
So now I'm struggling to find a way to properly add ENUMS as separate files to my modules and keep this running without weird workaround with IMport-Module just before calling PSModule.
I've found this: https://d-fens.ch/2014/11/26/bug-powershell-scripts-in-scriptstoprocess-attribute-appear-as-loaded-modules/ which does what it says it does well but it doesn't solve my problem.
Anyone knows a way to workaround it? I tried putting fullpath to ScriptsToProcess thinking that Enums path in ScriptsToProcess may be somehow overwritten ... but no..
My .psm1 file looks like this:
#Get public and private function definition files.
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
$Enums = @( Get-ChildItem -Path $PSScriptRoot\Enums\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach ($import in @($Public + $Private + $Enums)) {
Try {
. $import.fullname
} Catch {
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
Export-ModuleMember -Function '*'
[string] $ManifestFile = '{0}.psd1' -f (Get-Item $PSCommandPath).BaseName;
$ManifestPathAndFile = Join-Path -Path $PSScriptRoot -ChildPath $ManifestFile;
if ( Test-Path -Path $ManifestPathAndFile) {
$Manifest = (Get-Content -raw $ManifestPathAndFile) | iex;
foreach ( $ScriptToProcess in $Manifest.ScriptsToProcess) {
$ModuleToRemove = (Get-Item (Join-Path -Path $PSScriptRoot -ChildPath $ScriptToProcess)).BaseName;
if (Get-Module $ModuleToRemove) {
Remove-Module $ModuleToRemove;
}
}
}
I added Enums just now but it doesn't change anything... The thing after Export-Module is for removing the Script from Modules (as in the link). It doesn't matter for my problem.
Upvotes: 1
Views: 2406
Reputation: 11104
To work around this issue I've opted to use Add-Type
with C# code that is compiled on demand instead of PowerShell's own enum
construct. It works.
<#
enum MessageType {
Alert
Cancel
Disable
Download
Minus
Check
Add
None
}
#>
Add-Type -TypeDefinition @"
public enum MessageType
{
Alert,
Cancel,
Disable,
Download,
Minus,
Check,
Add,
None
}
"@
Upvotes: 1
Reputation: 438208
Note:
If you're just looking for an effective solution based on on-demand compilation of C# code via Add-Type
, see
MadBoy's own answer.
If you'd like to understand the issue, including why an Add-Type
-based solution helps, read on.
The behavior is as expected:
Your Import-Module PSModule -Force
call in script.ps1
dot-sources Enums\MessageType.ps1
only in script.ps1
's scope.
Therefore, when Call-Me
from module PSOtherModule
runs, it knows nothing about the enums you've loaded into script.ps1
, because PSOtherModule
has its own scope (that doesn't inherit from the caller).
The reason that Import-Module PSModule -Force
in Call-Me
works is that you're forcing reloading of the module in the context of PSOtherModule
, which therefore then dot-sources Enums\MessageType.ps1
, making the enum
available (whereas omitting -Force
would be a no-op, since PowerShell has already loaded the module (which happens session-globally) and therefore doesn't process the module manifest and its ScriptsToProcess
entry again).
Yes, using Add-Type
with a string containing C# code that is compiled on demand solves the problem (as shown in the answer you've since posted), but for an unrelated reason: Add-Type
-added types are invariably session-global, irrespective of in what scope Add-Type
is called.
To achieve the same effect with PowerShell enum
(or class
) definitions - and thereby solve your problem - you need to:
include the enum
definitions directly in your PSModule.psm1
file.
use the using module PSModule
directive instead of an Import-Module PSModule
cmdlet call in order to import your module - only using module
makes PowerShell-based enum
and class
definitions available outside the module, and then also session-globally.
Optional background information:
enum
and class
definitions are a recent addition to PowerShell (v5), and they're not part of the module-export mechanism.
Outside of modules, enum
and class
definitions are local to the scope they're defined in / dot-sourced into.
If you make enum
and class
definitions part of a module (*.psm1
) and you import that module with using module
, these definitions do become available, but invariably all of them, and invariably session-globally (as do Add-Type
-based types, although they do so even with Import-Module
).
Import-Module
instead of using module
, the class
and enum
definitions that are part of the module are effectively private.Many fixes and enhancements to enum
and class
definitions are pending as of Windows PowerShell v5.1 / PowerShell Core v6.1.0:
This meta GitHub issue tracks all of them.
Making Import-Module
see a module's enum
and class
definitions too is discussed in this issue; while fundamentally, possible, the fact that Import-Module
executes at runtime whereas using module
executes at parse time precludes certain use cases with Import-Module
(in short: class
definitions referencing other class
definitions).
Separately, being able to explicitly control which enum
and class
definitions are to be exported - analogous to how you can selectively export functions, aliases, and variables - is being discussed in this issue.
Upvotes: 4