2

I would like to use a .NET Framework class inside a PowerShell script. I've searched and read that it should be possible. What I have tried is the following:

Add-Type -AssemblyName System.Resources
$resx = New-Object [System.Resources.ResXResourceWriter];

But doing so throws an exception:

New-Object : Cannot find type [[System.Resources.ResXResourceWriter]]: verify
that the assembly containing this type is loaded.
At C:\temp\Translation\import_translation.ps1:41 char:25
+ ...             $resx = New-Object [System.Resources.ResXResourceWriter];
+                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidType: (:) [New-Object], PSArgumentException
    + FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand

The assembly does not appear to be found, even though I'm loading it using the Add-Type command above. How can I use .NET Framework classes in PowerShell?

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
developer82
  • 13,237
  • 21
  • 88
  • 153
  • Aside from the primary problem of including the enclosing `[...]` in the `New-Object` call, there are two additional problems: (a) The type of interest is in the `System.Windows.Forms` assembly (`Add-Type -AssemblyName System.Windows.Forms`), and (b) the type of interest doesn't have a _parameter-less_ constructor. – mklement0 Sep 24 '19 at 13:28
  • I'm also curious why `Add-Type -AssemblyName System.Resources` didn't result in an error, given that no such assembly is normally in the GAC. – mklement0 Sep 24 '19 at 13:41

1 Answers1

1

tl;dr:

Note: In addition to the primary problem shown below, there are two additional problems:
* It is the System.Windows.Forms.dll assembly that contains the type of interest, so the Add-Type command should be: Add-Type -AssemblyName System.Windows.Forms; add -Passthru to see what types are being loaded.
* Type [System.Resources.ResXResourceWriter] does not have a parameter-less constructor; run [System.Resources.ResXResourceWriter]::new (without ()) to see the available constructor overloads; see bottom section.

Your primary problem:

Instead of:

$resx = New-Object [System.Resources.ResXResourceWriter] # WRONG, due to [...]

use:

$resx = New-Object System.Resources.ResXResourceWriter  # OK - no [...]

Alternatively, in PSv5+:

$resx = [System.Resources.ResXResourceWriter]::new() # OK

When you pass [System.Resources.ResXResourceWriter] as an argument to a command, it is treated verbatim, not as a type literal - and a type whose full name is literally [System.Resources.ResXResourceWriter], with the enclosing [ and ], doesn't exist.

The reason is that command arguments are parsed in argument mode, where [ has no special meaning as the 1st char. of an argument.

See this answer for an overview of how (unquoted) tokens are parsed in argument mode.

While you can force a token to be interpreted as an expression by enclosing it in (...) - ([System.Resources.ResXResourceWriter]), in this case - that just creates extra work, because the type literal is converted back to a string when the value is bound to the New-Object's -TypeName parameter.

PowerShell v5 introduced the static ::new() method that you can call on type literals in expression mode in order to invoke constructors with the method syntax, analogous to how you call methods in C#, as shown above.

Calling ::new without () is also a convenient way to list the available constructor overloads, i.e. to see what constructor variants with what parameters the type supports; e.g.:

Add-Type -AssemblyName System.Windows.Forms
[System.Resources.ResXResourceWriter]::new

yields:

OverloadDefinitions
-------------------
System.Resources.ResXResourceWriter new(string fileName)
System.Resources.ResXResourceWriter new(string fileName, System.Func[type,string] typeNameConverter)
System.Resources.ResXResourceWriter new(System.IO.Stream stream)
System.Resources.ResXResourceWriter new(System.IO.Stream stream, System.Func[type,string] typeNameConverter)
System.Resources.ResXResourceWriter new(System.IO.TextWriter textWriter)
System.Resources.ResXResourceWriter new(System.IO.TextWriter textWriter, System.Func[type,string] typeNameConverter)

For instance, to call the 2nd overload from the list above, you'd using something like (using bogus arguments):

[System.Resources.ResXResourceWriter]::new('c:\tmp\foo', {})
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • @developer82: Using the usual method syntax, similar to C#; please see my update. – mklement0 Sep 24 '19 at 14:03
  • Do that returns a null object while using `New-Object` returns an object – developer82 Sep 24 '19 at 15:08
  • @developer82: No, both `New-Object` and `::new()` return the newly constructed instance; try `Add-Type -AssemblyName System.Windows.Forms; [System.Resources.ResXResourceWriter]::new('c:\tmp\foo', {}).GetType().FullName` – mklement0 Sep 24 '19 at 15:14