1

About a month ago I decided to try my hand at making a WinForms application in PowerShell. To my pleasant surprise I found it easier than doing the same in C#. All was well until I tried to inherit from WinForms Forms.

This:

using namespace System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms
class MainForm : Form {
}

Produces this error:

Unable to find type [Form].

I'm still realitively new to PowerShell, so at the time I had no clue what was going on. After considerable searching I finally had to slowed myself down and begin reading entire answers/comments even if they didn't seem to relate to the problem. It was the note at the bottom of an answer given by mklement0 that finally began shedding some light on the situation for me.

Apparently in an effort to prevent accidental code execution, assemblies are not loaded during parsing, but later during the script's execution, but classes, and any references a class makes to external assemblies, are fully parsed. Thus creating a catch-22 parsing issue that can only be resolved by loading assemblies prior to running a PowerShell script. (Or at least that is my current understanding - follow the links in mklement0's note to get the details.)

Now, by chance, the code I was working on was almost the perfect workaround, or solution, to the problem. All I had to do was change this CMD file from this:

<# :
@ECHO OFF
    SET f0=%~f0
    PowerShell -NoProfile -Command .([scriptblock]::Create((get-content -raw $Env:f0)))
GOTO :EOF
#>
using namespace System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms
class MainForm : Form {
}

To this :

<# :
@ECHO OFF
    SET f0=%~f0
    PowerShell -NoProfile -Command Add-Type -AssemblyName System.Windows.Forms;.([scriptblock]::Create((get-content -raw $Env:f0)))
GOTO :EOF
#>
using namespace System.Windows.Forms
class MainForm : Form {
}

And with that small change I was back in business, and with VSCode I can tell it the CMD script is really a PowerShell script and that makes almost everything functional - other than VSCode complaining about things related to catch-22 parsing issue.

But this also helped me realize that in a pure PowerShell script this would work:

using namespace System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms
.([scriptblock]::Create(@'
class MainForm : Form {
}
'@))

And it does work, only all the code in the string @' '@ is now treated by VSCode as a string. So trying to create a class inside a string is about the same as using Windows notepad, which is doable, but not pleasant.

So, how are people working around this? Is the solutions I've presented here about as good as it gets?

PowerShell does NOT appear to have a Preload-Assembly -ScriptBlock {} command that parses and executes the script block prior to parsing and executing the rest of the script. Are there plans for such a solution in the future? Does such a command by another name already exist and I've just failed come across it?

Perhaps this question will not provide me any answers beyond what I already have. But maybe this will help others reduce the time they spend trying to figure out why they can't inherit a WinForms Form - along with giving them the options I found.

On the other, if someone has better answers - please share!

Darin
  • 1,423
  • 1
  • 10
  • 12
  • why not just use the full TypeName ? `class MainForm : System.Windows.Forms.Form` – Santiago Squarzon Jul 19 '22 at 02:12
  • 1
    @SantiagoSquarzon, the code `class MainForm : System.Windows.Forms.Form {}` throws the same error. The only way around it is to run `Add-Type -AssemblyName System.Windows.Forms` first, and then run the script that has `class MainForm : System.Windows.Forms.Form {}` in it. If you are using VSCode, and you are doing work in WinForms with other scripts, then you may never know that you have the problem because that copy of PowerShell in VSCode remembers the `Add-Type` commands ran by other scripts. – Darin Jul 19 '22 at 02:20
  • I don't know if everything still applies to the current PowerShell (Core) versions, but [this answer](https://stackoverflow.com/a/42839957/45375) tries to summarize solution options - none of them trivial, and the `Invoke-Expression` solution is the equivalent of your `. ([scriptblock]::Create(...)` approach. – mklement0 Jul 19 '22 at 02:22
  • I thought the question was related to how you can make your class inherit using the type name instead of full type name. As for using `Add-Type` to load an assembly I don't see how that could be bad or a better workaround for what IS intended to begin with. – Santiago Squarzon Jul 19 '22 at 02:26
  • @Santiago, this issue isn't about mere vs. namespace-qualified type names. It is about PowerShell's _parsing_ of a script breaking if a class definition inside it references a type from an assembly that hasn't yet been loaded into the session. In that case, `Add-Type` never gets to execute. See the link in my previous comment. – mklement0 Jul 19 '22 at 02:29
  • 1
    @mklement0 ahh I see now, was unaware of this bug. Well in that case this seems a duplicate of that question. easiest workaround seems to have the assembly and the class in different `.ps1` – Santiago Squarzon Jul 19 '22 at 02:44
  • 1
    @mklement0, thank you! I have yet to created a module manifest, but I have done some work creating modules. Not exactly the answer I was hoping for, but very glad to have it! – Darin Jul 19 '22 at 02:48
  • 1
    Glad to hear it, @Darin. Yes, it is unfortunate that a module is currently needed - let's hope that gets fixed some day. Based on the conversation here, I've closed your question as a duplicate. – mklement0 Jul 19 '22 at 02:50

0 Answers0