# Friday, December 07, 2007

As knowledge of PowerShell increases for those new to .NET, there comes a point when people start to notice some shortcomings of the Assembly loading/unloading mechanisms of the 2.0 CLR. Namely, once you load an assembly into PowerShell to use it, you can't unload it again. The only way to remove it from memory is to restart PowerShell. Eventually, you might read something about how Assemblies can be loaded into AppDomains, and AppDomains themselves can be unloaded. This is true, but for the most part it is not much use in PowerShell unless the Types in question where specifically designed with this in mind. For those of you who understand enough of what I'm talking about to get this far without going "huh?", the following script will demonstrate some of the issues at hand:

Before you run this script, please disable PowerTab or any other SnapIns that may load the WinForms assembly into the current AppDomain. In short, this script creates a Form object in a child AppDomain, examines the current AppDomain for the WinForms assembly. It then attempts to manipulate the Form and again examines the current AppDomain for the WinForms assembly.

  1. # full qualified display name to WinForms assembly   
  2. $assembly = "System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"  
  3.   
  4. function IsWinFormsLoaded() {   
  5.     $loaded = [appdomain]::currentdomain.getassemblies()   
  6.     $winforms = $loaded | ? { $_.fullname -like "system.windows*" }   
  7.     return ($winforms -ne $null)       
  8. }   
  9.   
  10. if (-not (IsWinFormsLoaded)) {   
  11.     "Creating child AppDomain..."  
  12.     $child = [appdomain]::Createdomain("child",$null,$null)   
  13.   
  14.     # create a remote instance of a WinForms Form in a child AppDomain   
  15.     "Creating remote WinForms Form in child AppDomain... "  
  16.     $handle = $child.CreateInstance($assembly"System.Windows.Forms.Form")   
  17.   
  18.     # examine returned ObjectHandle   
  19.     "Returned object is a {0}" -f $handle.GetType()   
  20.     $handle | gm # dump methods   
  21.   
  22.     # Did WinForms get pulled into our AppDomain?   
  23.     "Is Windows Forms loaded in this AppDomain? {0}" -f (IsWinFormsLoaded)   
  24.   
  25.     # attempt to manipulate remote object, so unwrap   
  26.     "Unwrapping, examining methods..."  
  27.     $form = $handle.Unwrap()   
  28.     $form | gm | select -first 10   
  29.   
  30.     # is Windows Forms loaded now?   
  31.     "Is Windows Forms loaded in this AppDomain? {0}" -f (IsWinFormsLoaded)   
  32.   
  33. else {   
  34.     write-warning "System.Windows.Forms is already loaded. Please disable PowerTab or other SnapIns that may load System.Windows.Forms and restart PowerShell."  
  35. }  

Hopefully this will clear up any outstanding questions. I'll post more information about this later, or possibly add to this post.

appdomains.ps1 (1.33 KB)

Saturday, December 08, 2007 12:09:14 AM (Eastern Standard Time, UTC-05:00)
As soon as you start returning objects defined in assemblies that aren't already loaded, you loose. I'm sure you already know this, but for the record, you'd have to do something crazy for this to actually work. Something like this.
Saturday, December 08, 2007 9:15:18 AM (Eastern Standard Time, UTC-05:00)
Yep, that's the whole point of the demo script. As soon as you try to interact in any meaningful way with the assembly, it will need to be loaded into the calling AppDomain too. As regards the "crazy" stuff with plugins, yep, a shared assembly is the only way as I explained on the microsoft.public.windows.powershell on the thread that inspired this post. ;-)
Oisin
Comments are closed.