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.
Add-Typein a separate script and dousing module Otherscipt.ps1;unable to find typeerror before importing the module