i'm trying to write a powershell script file that has Parameter
s 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:
Add-Type
as the first line in the file.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-agnosticSo what can i do? Here's the code sample.
# 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
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 class
es defined in a script or loaded via using module
in the same script's param(...)
block, see this answer.
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.
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.