I'm looking for a supported way to reduce the namespace repetition I mentioned in #5940.
about_Using includes the following statement:
The
usingstatement needs to be the first statement in the script.
Modules customarily dot-source individual .ps1 files from their .psm1. Pester is one such example. It's not clear to me how the above statement applies in the context of such a module.
Consider the following files:
@(
'fileA.ps1'
'fileB.ps1'
) |
% { . "$(Split-Path -Path $MyInvocation.MyCommand.Path)\$_" }
function Get1 { [int][MyEnum]::One }
using namespace UsingTest.A
Add-Type @'
namespace UsingTest { namespace A {
public enum MyEnum {
One = 1,
Two
}
}
}
'@
function GetA1 { return [int][MyEnum]::One }
$script:v1 = 1
using namespace UsingTest.B
Add-Type @'
namespace UsingTest { namespace B {
public enum MyEnum {
One = 101,
Two
}
}
}
'@
function GetB1 { [int][MyEnum]::One }
function GetV1 { $script:v1 }
using?GetA1 and GetB1 will return 1 and 101, respectively?using statement needs to be the first statement in the script" apply to such a module?Edit: Add $script:v1 statements.
Sure, I use that pattern in every module I write for 5.1+
Yeah, unless you have both using statements in the psm1 (type resolution state is inherited by child scopes)
Not sure what you mean, that what you quoted is still true in the examples you gave. It needs to be the first statement in a script file, not an entire session
@SeeminglyScience
I've added a few bits to the code in my original post.
- ... (type resolution state is inherited by child scopes)
I'm not sure exactly what you mean by this. I have not successfully reproduced type resolution behavior consistent with inheritance when dot-sourcing .ps1 files from a .psm1 file. On the contrary, consider that Get1 succeeds and outputs 101. This suggests that using namespace UsingTest.B from the "child" fileB.ps1 file is in effect in the "parent" .psm1 file.
- Not sure what you mean, that what you quoted is still true in the examples you gave. It needs to be the first statement in a script file, not an entire session
My apprehension stems from the definition of "script". In particular, what does "script" mean in the context of a module. "script scope", for example, as applied to variables seems to mean "the scope of the entire module". GetV1 outputs 1 despite that the only place $v1 is set is in a different file that is not a parent. If "script" really meant "script file" in the context of dot-sourcing .ps1 files from a .psm1, GetV1 should output null.
I'm not sure exactly what you mean by this. I have not successfully reproduced type resolution behavior consistent with inheritance when dot-sourcing .ps1 files from a .psm1 file.
No, dot sourcing doesn't effect type resolution. Type resolution state is stored at the scope level, but it doesn't follow the same rules as other session state items like commands or variables. What I meant is if you declare a using statement in the psm1, it will be in effect in all child scopes. For example
# myModule.psm1
using namespace System.Management.Automation.Language
& ./myOtherScript.ps1
# myOtherScript.ps1
[Ast]
In that example [Ast] would resolve to System.Management.Automation.Language.Ast.
My apprehension stems from the definition of "script". In particular, what does "script" mean in the context of a module.
That's fair, to be more specific it has to be the first statement in a "thing" that is parsed. Typically this means per file, or input from the command prompt, or a dynamically created script block like [scriptblock]::Create('using namespace x').
Is it certain that GetA1 and GetB1 will return 1 and 101, respectively?
@alx9r I can not find this result :
ipmo C:\temp\UsingTest.psm1
Get1
#101
GetA1
#101
GetB1
#1
And the result of the call to 'Get1' depends on the loading order of the files 'fileA.ps1' and 'fileB.ps1'.
$PSVersionTable
Name Value
---- -----
PSVersion 6.0.1
PSEdition Core
GitCommitId v6.0.1
OS Microsoft Windows 6.1.7601 S
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0, 5.0, 5.1.10032.0, 6.0.1}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
@LaurentDardenne That's an interesting result. The difference seems to be whether the module is loaded from $Env:PSModulePath.
$Env:PSModulePathImport-Module UsingTest; Get1; GetA1; GetB1
outputs
101
1
101
c:\tempImport-Module c:\temp\UsingTest; Get1; GetA1; GetB1
outputs
101
101
1
Edit: So far I've only been able to reproduce this difference in PowerShell 5.1. The results seem to be consistent with @LaurentDardenne's post in PowerShell 6.
I do not know if it helps, but I did these tests out of a module :
. .\fileA.ps1
GetA1
#1
[int][MyEnum]::One
#1
[int][UsingTest.A.MyEnum]::One
#1
. .\fileB.ps1
GetB1
#1
[int][MyEnum]::One
#101
[int][UsingTest.B.MyEnum]::One
#101
GetA1
#1
New session :
```Powershell
. .fileB.ps1
GetB1
#101
#101
#101
. .\fileA.ps1
GetA1
#101
#1
#1
GetB1
#101
Why are you dot sourcing? A module manifest has e.g. the ScriptsToProcess property on it. Does the bug happen when using that as well?
@bergmeister ScriptsToProcess creates the class in the global SessionState instead of the module's, so it gets exported by default and can't access private functions. It also leaves a dummy module named after the script file name imported in the global session. It's more of a sorta dirty work around for class related issues.
A module manifest has e.g. the ScriptsToProcess property on it. Does the bug happen when using that as well?
For reference, ScriptsToProcess "specifies script files that run in the caller's session state". The file specified in RootModule, on the other hand, is associated with the module SessionState. RootModule is scalar, so it seems that modules composed from multiple .ps1 files must consolidate those from the file that is specified in RootModule (ie. by dot-sourcing).
For what it's worth, I haven't come across a viable alternative to this dot-sourcing approach for modules with many files. If there is one, I'd welcome that knowledge.
I'm not aware of an alternative.
When we added classes, I proposed the property RootScripts in the module manifest as an alternative to dot sourcing the RootModule, basically codifying the pattern folks use today, but that proposal wouldn't have helped your scenario.
The using statement does not really mix well with dot sourcing. It was originally meant to be fully static - as in - only affecting the ps1 file that contains the statement, but the dynamic nature of PowerShell forced tracking the using state at runtime, and that naturally happens in a scope.
So mixing using namespace and dot sourcing is unfortunately fragile and I don't recommend it, but I believe you could dot source 1 file with all your using namespace statements and then not use using namespace in any other file in your module - that would behave how you'd like, other than getting good intellisense.
I believe you could dot source 1 file with all your using namespace statements and then not use using namespace in any other file in your module
I tried this and it seems to be the least bad option. It does mean that you have to concern yourself with naming collisions module-wide rather than file-wide, but I'll gladly take that over fragile.
Thank you for the clarity @lzybkr.