1

i'm trying to write a powershell script file that has Parameters whose types require loading. A simple code example is below: [System.Windows.Forms.MessageBoxButtons] requires loading of system.windows.forms.

The problem is, the Param(...) block must be the very first in the script file. So:

  • i cannot place Add-Type as the first line in the file.
  • i tried with using assembly system.windows.forms but it errors out saying: Cannot load assembly 'System.Windows.Forms'. I think it could be possible by explicitly writing the dll file path, but it's ugly and not device-agnostic

So what can i do? Here's the code sample.

messagebox.ps1

# Add-Type -AssemblyName system.windows.forms # DOESN'T WORK, can't be placed before Param()
# using assembly System.Windows.Forms # DOESN'T WORK, can't find the assembly to load

Param(
    [string] $Text = '',
    [string] $Caption = '',
    [System.Windows.Forms.MessageBoxButtons] $Buttons = [System.Windows.Forms.MessageBoxButtons]::OK # REPORTS: Unable to find type [System.Windows.Forms.MessageBoxButtons].
)
    
[System.Windows.Forms.MessageBox]::Show($Text, $Caption, $Buttons)

Thanks


A similar question (about a user defined type, instead of a system type): Powershell script Param block validation requires a type defined in another script

3
  • Please note: as i implied in my own first answer, i don't want to break parameters autocompletion Commented Mar 17, 2023 at 17:17
  • Maybe put Add-Type in a separate script and do using module Otherscipt.ps1; Commented Mar 17, 2023 at 17:28
  • @Charlieface that's a nice idea, unfortunately i just tried and powershell throws the unable to find type error before importing the module Commented Mar 17, 2023 at 17:40

2 Answers 2

1

Unfortunately, there is no good solution as of PowerShell 7.3.3.

However, there is a - cumbersome - workaround that preserves parameter validation and tab-completion:

[CmdletBinding()]
Param(
    [string] $Text = '',
    [string] $Caption = '',
    # Implement tab-completion.
    [ArgumentCompleter({
      Add-Type -Assembly System.Windows.Forms
      [enum]::GetNames([System.Windows.Forms.MessageBoxButtons])
    })]
    # Given that we cannot strongly type the parameter,
    # make sure whatever value is passed is *convertible* to the desired type.
    [ValidateScript({ 
      Add-Type -Assembly System.Windows.Forms
      $null -ne [System.Windows.Forms.MessageBoxButtons] $_
     })]
    # Do NOT type-constrain the parameter (implies [object]).
    $Buttons = 'OK'
)

# Must still ensure the assembly is loaded here, because
# it hasn't bee loaded yet if neither tab-completion was used
# nor a -Buttons value was passed.
Add-Type -Assembly System.Windows.Forms

[System.Windows.Forms.MessageBox]::Show($Text, $Caption, [System.Windows.Forms.MessageBoxButtons] $Buttons)

Note how the Add-Type calls now happen inside parameter-attribute script blocks and the function body, which bypasses the syntax problem.

As for the variant problem of not being able to use custom PowerShell classes defined in a script or loaded via using module in the same script's param(...) block, see this answer.


Background information:

  • The problem is that parameter declarations are parsed at script parse time, which comes before runtime, i.e. before actual execution of the script, and any .NET types directly referenced in such declarations must already be loaded into the session.

  • Not just Add-Type, but - perhaps surprisingly - also using assembly loads the referenced assembly's type at runtime, and execution of a script never happens if the parsing stage fails.

    • That is, even though a using assembly statement is syntactically allowed before a param(...) block (unlike Add-Type), it does not help here - its execution would come too late (note that the linked documentation is incorrect as of this writing; an issue has been raised).

    • As an aside: As of PowerShell 7.4.1, using assembly - is actually fundamentally broken for well-known assemblies - see GitHub issue #11856.

  • The underlying problem also affects the use of types in class definitions - see GitHub issue #18482 and this answer.

    • Fixing this limitation has been green-lit years ago, but no one has stepped up to implement it yet, as of the time of this writing - see GitHub issue #2074
  • Finally, a related problem exists in conjunction with using module statements that contain class and enum PowerShell definitions: Even though using module generally does make these available at parse time and therefore allows them to be referenced in other class definitions in the the body of the caller's script, they too cannot be used inside param(...) blocks - see GitHub issue #19676.

Sign up to request clarification or add additional context in comments.

5 Comments

Urgh almost feels like PS is getting worse not better at this stuff, and it was bad to start with.
@Charlieface, I wouldn't say it's getting worse, but it's certainly taking its time at getting better - these issues have been known for years.
damn... that's scary. my actual final script would also have 3 more parameters from that assembly: having to do all of this for each parameter would be absurd :\ i thought that i could at least use using assembly with an absolute path but i didn't try it...
@aetonsi, yes, it is inconvenient, but currently the only solution if you want both tab completion and type safety. As noted in the answer, using assembly - whether with a known assembly name or a full path - does not help. A fix has been green-lit many years ago, but no one has stepped to implement it yet - see github.com/PowerShell/PowerShell/issues/…
😢 i'm going to use this for now.. thank you mr. Klement as always 👍
1

The best solution i've found by now is enclosing everything into an IISB (immediately invoked scriptblock) and splatting $args:

Add-Type -AssemblyName system.windows.forms

& {
param(
    [string] $Text = '',
    [string] $Caption = '',
    [System.Windows.Forms.MessageBoxButtons] $Buttons = [System.Windows.Forms.MessageBoxButtons]::OK
)

[System.Windows.Forms.MessageBox]::Show($Text, $Caption, $Buttons)

} @args

but this is ugly and it breaks autocompletion obviously, because the Param() now belongs to the scriptblock instead of the script

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.