PowerShell: using module does not resolve "~" home folder correctly
Prerequisites
- Write a descriptive title.
- Make sure you are able to repro it on the latest released version
- Search the existing issues.
- Refer to the FAQ.
- Refer to Differences between Windows PowerShell 5.1 and PowerShell.
Steps to reproduce
- Obtain a MacOS / nix machine
- Create a
.psm1
module and place it in a subfolder under your home~
directly - Create a
.ps1
script and attempt to reference the.psm1
module using theusing module
statement - Run the script
Example
#!/usr/local/bin/pwsh
using module "~/.nuget/packages/<package-name>/1.0.1/scripts/k8s/module.psm1"
Expected behavior
I expected the `using module` statement to successfully load classes from the `.psm1` module
Actual behavior
- I'm receiving an error stating:
The specified module '/Users/<username>/<calling ps1 path>/~/.nuget/packages/<package-name>/1.0.1/scripts/k8s/module.psm1' was not loaded because no valid module file was found in any module directory.
- I’ve verified that the file exists under the
~/.nuget/packages/<package-name>/1.0.1/scripts/k8s/module.psm1'
path - For some reason the calling script’s path and the module’s path are getting merged
### Error details
```console
Exception :
Type : System.Management.Automation.RuntimeException
ErrorRecord :
Exception :
Type : System.Management.Automation.ParentContainsErrorRecordException
Message : The specified module '/Users/maciej.misztal/Projects/@devops/ops-k8s-argocd/content/scripts/~/.nuget/packages/allegropay.devops.
k8s.automation/1.0.1/scripts/k8s/HELM.psm1' was not loaded because no valid module file was found in any module directory.
HResult : -2146233087
TargetObject : /Users/maciej.misztal/Projects/@devops/ops-k8s-argocd/content/scripts/ArgoCD.psm1
CategoryInfo : InvalidOperation: (/Users/maciej.miszt…scripts/ArgoCD.psm1:String) [], ParentContainsErrorRecordException
FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand
InvocationInfo :
ScriptLineNumber : 1
OffsetInLine : 3
HistoryId : 1
Line : . "/Users/maciej.misztal/Projects/@devops/ops-k8s-argocd/content/scripts/Deploy.ArgoCD.ps1"
PositionMessage : At line:1 char:3
+ . "/Users/maciej.misztal/Projects/@devops/ops-k8s-argocd/content/scri …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
InvocationName : .
CommandOrigin : Internal
ScriptStackTrace : at <ScriptBlock>, <No file>: line 1
TargetSite :
Name : LoadModule
DeclaringType : System.Management.Automation.Language.Compiler, System.Management.Automation, Version=7.1.2.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
MemberType : Method
Module : System.Management.Automation.dll
StackTrace :
at System.Management.Automation.Language.Compiler.LoadModule(PSModuleInfo originalModuleInfo)
at System.Management.Automation.Language.Compiler.LoadUsingsImpl(IEnumerable`1 usingAsts, Assembly[]& assemblies)
at System.Management.Automation.Language.Compiler.GenerateLoadUsings(IEnumerable`1 usingStatements, Boolean allUsingsAreNamespaces, List`1 exprs)
at System.Management.Automation.Language.Compiler.GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAndUsings, List`1 exprs)
at System.Management.Automation.Language.Compiler.CompileSingleLambda(ReadOnlyCollection`1 statements, ReadOnlyCollection`1 traps, String
funcName, IScriptExtent entryExtent, IScriptExtent exitExtent, ScriptBlockAst rootForDefiningTypesAndUsings)
at System.Management.Automation.Language.Compiler.CompileNamedBlock(NamedBlockAst namedBlockAst, String funcName, ScriptBlockAst
rootForDefiningTypes)
at System.Management.Automation.Language.Compiler.VisitScriptBlock(ScriptBlockAst scriptBlockAst)
at System.Management.Automation.Language.ScriptBlockAst.Accept(ICustomAstVisitor visitor)
at System.Management.Automation.Language.Compiler.Compile(CompiledScriptBlockData scriptBlock, Boolean optimize)
at System.Management.Automation.CompiledScriptBlockData.ReallyCompile(Boolean optimize)
at System.Management.Automation.CompiledScriptBlockData.CompileUnoptimized()
at System.Management.Automation.CompiledScriptBlockData.Compile(Boolean optimized)
at System.Management.Automation.ScriptBlock.Compile(Boolean optimized)
at System.Management.Automation.PSScriptCmdlet..ctor(ScriptBlock scriptBlock, Boolean useNewScope, Boolean fromScriptFile, ExecutionContext
context)
at System.Management.Automation.CommandProcessor.Init(IScriptCommandInfo scriptCommandInfo)
at System.Management.Automation.CommandProcessor..ctor(IScriptCommandInfo scriptCommandInfo, ExecutionContext context, Boolean useLocalScope,
Boolean fromScriptFile, SessionStateInternal sessionState)
at System.Management.Automation.CommandDiscovery.GetScriptAsCmdletProcessor(IScriptCommandInfo scriptCommandInfo, ExecutionContext context,
Boolean useNewScope, Boolean fromScriptFile, SessionStateInternal sessionState)
at System.Management.Automation.CommandDiscovery.CreateCommandProcessorForScript(ExternalScriptInfo scriptInfo, ExecutionContext context, Boolean
useNewScope, SessionStateInternal sessionState)
at System.Management.Automation.CommandDiscovery.CreateScriptProcessorForSingleShell(ExternalScriptInfo scriptInfo, ExecutionContext context,
Boolean useLocalScope, SessionStateInternal sessionState)
at System.Management.Automation.CommandDiscovery.LookupCommandProcessor(CommandInfo commandInfo, CommandOrigin commandOrigin, Nullable`1
useLocalScope, SessionStateInternal sessionState)
at System.Management.Automation.CommandDiscovery.LookupCommandProcessor(String commandName, CommandOrigin commandOrigin, Nullable`1 useLocalScope)
at System.Management.Automation.ExecutionContext.CreateCommand(String command, Boolean dotSource)
at System.Management.Automation.PipelineOps.AddCommand(PipelineProcessor pipe, CommandParameterInternal[] commandElements, CommandBaseAst
commandBaseAst, CommandRedirection[] redirections, ExecutionContext context)
at System.Management.Automation.PipelineOps.InvokePipeline(Object input, Boolean ignoreInput, CommandParameterInternal[][] pipeElements,
CommandBaseAst[] pipeElementAsts, CommandRedirection[][] commandRedirections, FunctionContext funcContext)
at System.Management.Automation.Interpreter.ActionCallInstruction`6.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
Message : The specified module '/Users/maciej.misztal/Projects/@devops/ops-k8s-argocd/content/scripts/~/.nuget/packages/allegropay.devops.k8s.
automation/1.0.1/scripts/k8s/HELM.psm1' was not loaded because no valid module file was found in any module directory.
Data : System.Collections.ListDictionaryInternal
Source : System.Management.Automation
HResult : -2146233087
TargetObject : /Users/maciej.misztal/Projects/@devops/ops-k8s-argocd/content/scripts/ArgoCD.psm1
CategoryInfo : InvalidOperation: (/Users/maciej.miszt…scripts/ArgoCD.psm1:String) [], RuntimeException
FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand
InvocationInfo :
ScriptLineNumber : 1
OffsetInLine : 3
HistoryId : 1
Line : . "/Users/maciej.misztal/Projects/@devops/ops-k8s-argocd/content/scripts/Deploy.ArgoCD.ps1"
PositionMessage : At line:1 char:3
+ . "/Users/maciej.misztal/Projects/@devops/ops-k8s-argocd/content/scri …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
InvocationName : .
CommandOrigin : Internal
ScriptStackTrace : at <ScriptBlock>, <No file>: line 1
Environment data
Name Value
---- -----
PSVersion 7.1.2
PSEdition Core
GitCommitId 7.1.2
OS Darwin 21.4.0 Darwin Kernel Version 21.4.0: Mon Feb 21 20:34:37 PST 2022; root:xnu-8020.101.4~2/RELEASE_X86_64
Platform Unix
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
Visuals
No response
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 27 (4 by maintainers)
Interestingly, relative paths work differently in
using
statements. They work relative to the file rather than the working directory. Basically theusing
version of$PSScriptRoot
WG-Engine discussed this today. We agree it’s definitely a bug, but given that
~
is technically a PSProvider concept it is somewhat uniquely ill-suited to use inusing module
which is a parse-time concept. We feel it would be unwise to involve the rest of the engine and PSProvider subsystem in the parsing code.As a “mostly good” fix, we feel the better option might be to take the “most common” definition of
~
rather than the “strictly PowerShell-correct” one during parsing, whereusing module
with a path like this would evaluate~
purely based on the appropriate environment variables instead of involving the PSProvider subsystem in the parsing code. This would enable the vast majority of use cases here without being liable to cause odd edge cases where suddenly parsing this kind of code does strange things in unexpected circumstances.Marking this as a bug for now. Thanks for the report! 💖 😊
@237dmitry Our
using
statement is not a dotnet concept. We have full control over how it acts.The question is whether we can consider
~
to be usable in what is a parse time construct, not whether we have the ability to change it.I’d like to see this. The biggest potential disconnect is that
~
is configurable, so it’s not particularly well suited for a parse time concept:Opening up to the Engine WG for discussion.
Also note that
using
in this context is a PowerShell concept. There’s already plenty of custom handling in the path resolution.Sure. Because when I write a module for others to use I don’t know if they will load it with USING or Import-Module, if I want classes to be available I now put them in their own file. In the PSD1 file I then have
ScriptsToProcess = @(‘Classes.ps1’)
https://github.com/jhoneill/OctopusTools is an example, psm1 loads files from public (exported) and private (not exported). PSD1 loads the Psm1 file, the classes, the types and the formats.
. If it is your module you can move the classes out of the PSM1 file and change the PSD1 .But that’s harder to do with someone else’s code - their next update will reverse your change 😃
Considering that relative paths work like:
using module ..\..\TestModule
I don’t see why ~ shouldn’t also work.Import-Module is a no-go. I need to import the classes out of the psm1
Won’t work. It needs to be a top-level statement.
not at all sure that
using module <path to ps module>
is a .net statement and even if it were, somewhere between the PowerShell parser and the .net internals the path should be resolved.@kasini3000 's work round or using
Import-Module
instead should avoid the error, but it does look like a bug to me.