MicrosoftDocs / PowerShell-Docs

The official PowerShell documentation sources
https://learn.microsoft.com/powershell
Other
2.01k stars 1.58k forks source link

Side-effect of specifying -PassThru with Add-Type should be documented #11550

Closed alx9r closed 2 days ago

alx9r commented 2 days ago

Prerequisites

Links

Summary

The following

  1. creates then loads assembly for type n.c,
  2. creates but doesn't load .\d1.dll, then
  3. creates and loads assembly .\d2.dll.

The calls for (2) and (3) differ only by whether -PassThru is provided. In other words, adding -PassThru has the side-effect of loading an assembly that otherwise would not have been. I find this to be surprising behavior since the kind of assembly metadata output with -PassThru is obtained separately from loading an assembly when using CSharpCompilation and friends in .Net. (As much is demonstrated in PowerShell/PowerShell#24612.)

Indeed the following test suggests this behavior is so intended (although I do not understand why):

Add-Type -TypeDefinition $code -OutputAssembly $outFile | Should -BeNullOrEmpty
# Without -PassThru we don't load output assembly
{ [type]"System.Management.Automation.AttributeTest$guid" } | Should -Throw

repro.ps1

Remove-Item .\c.dll,.\d1.dll,.\d2.dll -ErrorAction SilentlyContinue

Add-Type                                                  <# doesn't load .\c.dll#> `
    -TypeDefinition 'namespace n { public class c {}}'                              `
    -OutputAssembly .\c.dll
Add-Type -Path .\c.dll                                    <# loads .\c.dll #>
[n.c]::new()

Add-Type                                                  <# doesn't load .\d1.dll #> `
    -TypeDefinition 'namespace n {public class d : c {}}'                             `
    -ReferencedAssemblies .\c.dll `
    -OutputAssembly       .\d1.dll

[n.d]::new()                                              <# fails, n.d hasn't been loaded #>

$null =
Add-Type                                                  <# loads .\d2.dll... #> `
    -TypeDefinition 'namespace n {public class d : c {}}'                         `
    -ReferencedAssemblies .\c.dll                                                 `
    -OutputAssembly       .\d2.dll                                                `
    -PassThru                                             <# ...because of this switch #>

[n.d]::new()                                              <# succeeds #>

outputs

PS \> pwsh.exe {.\repro.ps1 }
n.c
InvalidOperation: C:\repro.ps1:14
Line |
  14 |  [n.d]::new()                                              <# fails, n …
     |  ~~~~~
     | Unable to find type [n.d].
n.d

Suggested Fix

I would find the behavior less surprising if the following were added to the documentation for -PassThru:

-PassThru

... Specifying -PassThru guarantees that the assembly produced is also loaded.

I think that statement is true, but I'm completely sure. That language would be consistent with this other statement:

-LiteralPath

... Using the Path or LiteralPath parameters guarantees that you are loading the assembly that you intended to load.

alx9r commented 2 days ago

@michaeltlombardi Thank you for your help with this issue. I see the help for -PassThru now reads as follows:

Returns a System.Runtime object that represents the types that were added. By default, this cmdlet doesn't generate any output. Use this parameter if you used OutputAssembly to create a DLL file and you want to return the type from the newly created assembly.

I don't think this addresses my original issue. The new wording does not mention that, in addition to outputting the System.Runtime object, -PassThru also has the side-effect of loading the assembly. Without -PassThru the assembly is not loaded. That's surprising behavior, and so I think ought to be documented. Or perhaps that behavior is a bug that should be corrected. As it stands the surprising behavior is still undocumented which isn't ideal. I'm happy to open an issue in the PowerShell repository if you think that's the right solution.