Open iRon7 opened 3 years ago
Add-Type
won't generally resolve .NET types. You shouldn't need to call Add-Type
for System.Management.Automation
either; that assembly is always loaded because it's PowerShell's own primary assembly. 🤔
If anything, you might want a using namespace System.Management.Automation
to allow you to shorten type names.
@vexx32, thanks for the comment.
I added the add-type
as one of my first attempt to resolve the issue, but never tried to undo it. 🥴
Anyways, it does not detract from the actual issue (I have changed the title and description).
I've always understood it would work this way.
If you import the the psm1 file, and then try [RuntimeDefinedParameterDictionary]::new()
at the prompt you see the effect of the using namespace
statement is not global. In places like C# I've always though of "using" being like a path for executables, and telling the compiler to try prefixes on unresolved names in that file and they don't apply anywhere else. PowerShell doesn't work like that in all cases. If you put using namespace
in a ps1 file and dot source it, that namespace is seen globally, but if you have it in a psm1 file and import the module, it isn't. There are similar issues with classes written in PowerShell and loaded from modules, which depending how they are loaded may or may not be visible outside. So things like argument completers can break going from a free-standing ps1 being dot sourced, to same file being loaded as part of a module.
@jhoneill, thank you for the explanation.
I was expecting something like this but wanted at least make note of the limitation. I don't mind if this issue is closed in won't fix
.
@iRon7 :-) Things don't work the way most people would expect them to. The question, really, is how practical it is to change it to be logical.
The using namespace
statements (and PS-Defined classes) that are in scope when PowerShell examines a function before running it, and the ones that are in scope when it executes the code in the function body can be different when they get loaded as a module (depending on exactly how the module is done) and I think to most people that's feels wrong.
Yeah, it's a scoped action; it only applies to the scope it's used for. You can scope it to a file, which might be a function, or a module, or a script. But unless you dot source that file (which merges the scope into your current scope) it doesn't apply outside that file.
That said, I would tend to agree it should probably work for modules more consistently. I've seen a few less consistent issues with it in modules in the past... but I've also seen plenty of cases where this does work just fine (I use it in PSKoans in at least one or two functions).
I wonder if the difference is that [outputtype]
(along with other function attributes like [argumentcompleter]
) are evaluated in the global scope or something for some reason? @SeeminglyScience might have some idea there.
@vexx32 yeah, it's whatever scope the attribute happens to be compiled in. Very unlikely to be the scope where the type is resolvable.
Part of the problem is that ITypeName.GetReflectionType()
doesn't really have any visibility into the AST, so it can't check what the current using
statements are. Nor does it have any visibility into what types are defined in that scope. Most of the type using
statements only work based on SessionStateScope.TypeResolutionState
.
If ITypeName
kept track of what using
statements were defined in it's AST (currently it only tracks it's IScriptExtent
) then using
statements could properly be "file scoped". That wouldn't really solve the problem of defined types (except in some cases with using module
maybe, though probably better to limit to using namespace
) but would be nice.
I wonder if the difference is that
[outputtype]
(along with other function attributes like[argumentcompleter]
) are evaluated in the global scope or something for some reason? @SeeminglyScience might have some idea there.
I'm not sure I completely understand the reply. But the way I understood it was when (for example) you type
command -param X | select -property [tab]
, the scope where something is asking for the types that are attributes of -param and asking "What is the output type of command" is a different scope from the one where command is defined. Command
crosses scope but the using
statement doesn't and class might or might not depending how the module loaded it. Since classes loaded by add-type are global, I think it's OK for classes defined with a powershell class
statement to be as well, but just push all used namespaces into the global scope could end up with name clashes and whether there is a risk the wrong namespace wins. It almost needs an "export namespace" command.
the scope where something is asking for the types that are attributes of -param and asking "What is the output type of command" is a different scope from the one where command is defined.
Yeah pretty much. That information is queried from the current interactive scope.
Command
crosses scope but theusing
statement doesn't and class might or might not depending how the module loaded it.
It's less about whether using
does, and more about how tab completion doesn't.
Since classes loaded by add-type are global, I think it's OK for classes defined with a powershell
class
statement to be as well
Without the ability to declare a namespace that would end with a lot of conflicts. For example it'd be interesting to see how many modules have a class named Error
.
but just push all used namespaces into the global scope could end up with name clashes and whether there is a risk the wrong namespace wins. It almost needs an "export namespace" command.
I'm not sure the solution is to change the state of global, I'd rather see tab completion become aware of the callee's state.
I guess we already have such issue.
The still exists:
$PSVersionTable
Name Value
---- -----
PSVersion 7.3.9
PSEdition Core
GitCommitId 7.3.9
OS Microsoft Windows 10.0.19045
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
The issue still exists in 7.4.2
.
I think this issue should be marked as bug and possibly closed with won't-fix.
After creating a module out of a cmdlet, it appeared that I had to add theWhen dot-source the file below as a PowerShell script (Add-Type -AssemblyName
to resolve long .Net types. All shorthand types are now resolved as expected, except for theOutputType
attribute in the[CmdletBinding()]
:. .\OutputType.ps1
file without theExport-ModuleMember
) it works fine, but when converted into a module, the shorten type (RuntimeDefinedParameterDictionary
) doesn't work for theOutputType
:Steps to reproduce
Module (
OutputType.psm1
) file:Expected behavior
Actual behavior
Workaround
Add full qualified type name to the
OutputType
attribute:Environment data