Summary: The Microsoft Scripting Guy, Ed Wilson, shows how to use Windows PowerShell to find and to explore .NET Framework classes.
PoshReflectionExplorer? Or not.
Microsoft Scripting Guy, Ed Wilson, is here. Well the day finally arrived. This morning the Scripting Wife dropped me off at the airport, and I begin my trek across the United States to Seattle Washington for the Microsoft internal conference, TechReady 15. I do not know if I have mentioned it or not, but there are 38 Windows PowerShell sessions going on this week at TechReady 15. Dude, I can tell you that I will have my work cut out for me attempting to see all of them. So I have my laptop running the latest build of Windows 8, and the customer preview of Office 2013, and I got a free upgrade to First Class, and 5 ½ hours of free time during the flight to enjoy. Sweet.
I seem to remember an email or a comment on a recent Hey, Scripting Guy! Blog post about exploring .NET Framework classes via Windows PowerShell. I wrote a Windows PowerShell script to do this very thing more than four years ago when I was working on the Windows PowerShell Scripting Guide book for Microsoft Press. I am not going to show you the script (which is a rather ugly Windows PowerShell 1.0 script), but I will show you the techniques that I used in the script to create my explorer.
First find the current appdomain
The first thing to do is to find the current appdomain. There is one used by the Windows PowerShell console, and a different one used for the Windows PowerShell ISE. To find the current appdomain, use the static currentdomain property from the system.appdomain .NET Framework class. (By the way, this works in Windows PowerShell 3.0 as well). First, the current appdomain for the Windows PowerShell console.
PS C:\> [appdomain]::CurrentDomain
FriendlyName : DefaultDomain
Id : 1
ApplicationDescription :
BaseDirectory : C:\WINDOWS\system32\WindowsPowerShell\v1.0\
DynamicDirectory :
RelativeSearchPath :
SetupInformation : System.AppDomainSetup
ShadowCopyFiles : False
Now, using the same command in the Windows PowerShell ISE, you can see different results.
PS C:\Users\ed.IAMMRED> [appdomain]::currentdomain
FriendlyName : PowerShell_ISE.exe
Id : 1
ApplicationDescription :
BaseDirectory : C:\Windows\system32\WindowsPowerShell\v1.0\
DynamicDirectory :
RelativeSearchPath :
SetupInformation : System.AppDomainSetup
ShadowCopyFiles : False
The currentdomain static property returns a system.appdomain object. This object contains a number of methods in addition to the displayed properties. I can find this information by piping the results from the currentdomain static property to the Get-Member cmdlet. This command is shown here.
[appdomain]::CurrentDomain | get-member
The method I want to use is the getassemblies method. The getassemblies method is not a static method, but because the currentdomain static property returns a system.appdomain object. I can call the method directly from that object. Here is the command and associated output from the Windows PowerShell console (on a Windows PowerShell 2.0 machine. In Windows PowerShell 3.0, the versions are all v4.0.xxxxx).
PS C:\> [appdomain]::currentdomain.GetAssemblies()
GAC Version Location
--- ------- --------
True v2.0.50727 C:\Windows\Microsoft.NET\Framework64\v2.0.50727\mscorlib.dll
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\Microsoft.PowerShell.ConsoleHo...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e0...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\System.Management.Automation\1...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\Microsoft.PowerShell.Commands....
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\System.Core\3.5.0.0__b77a5c561...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\System.Configuration.Install\2...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\Microsoft.WSMan.Management\1.0...
True v2.0.50727 C:\Windows\assembly\GAC_64\System.Transactions\2.0.0.0__b77...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\Microsoft.PowerShell.Commands....
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\Microsoft.PowerShell.Commands....
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\Microsoft.PowerShell.Security\...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\System.Xml\2.0.0.0__b77a5c5619...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\System.Management\2.0.0.0__b03...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\System.DirectoryServices\2.0.0...
True v2.0.50727 C:\Windows\assembly\GAC_64\System.Data\2.0.0.0__b77a5c56193...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\System.Configuration\2.0.0.0__...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\System.Security\2.0.0.0__b03f5...
True v2.0.50727 C:\Windows\assembly\GAC_MSIL\System.Data.SqlXml\2.0.0.0__b7...
The getassemblies method returns instances of the System.Reflection.Assembly .NET Framework class. This class contains a number of very interesting methods and properties. The output from Get-Member on the returned system.reflection.assembly .NET framework class is shown here.
PS C:\> [appdomain]::currentdomain.GetAssemblies() | Get-Member
TypeName: System.Reflection.Assembly
Name MemberType Definition
---- ---------- ----------
ModuleResolve Event System.Reflection.ModuleResolveEventHandler ...
CreateInstance Method System.Object CreateInstance(string typeName...
Equals Method bool Equals(System.Object o)
GetCustomAttributes Method System.Object[] GetCustomAttributes(bool inh...
GetExportedTypes Method type[] GetExportedTypes()
GetFile Method System.IO.FileStream GetFile(string name)
GetFiles Method System.IO.FileStream[] GetFiles(), System.IO...
GetHashCode Method int GetHashCode()
GetLoadedModules Method System.Reflection.Module[] GetLoadedModules(...
GetManifestResourceInfo Method System.Reflection.ManifestResourceInfo GetMa...
GetManifestResourceNames Method string[] GetManifestResourceNames()
GetManifestResourceStream Method System.IO.Stream GetManifestResourceStream(t...
GetModule Method System.Reflection.Module GetModule(string name)
GetModules Method System.Reflection.Module[] GetModules(), Sys...
GetName Method System.Reflection.AssemblyName GetName(), Sy...
GetObjectData Method System.Void GetObjectData(System.Runtime.Ser...
GetReferencedAssemblies Method System.Reflection.AssemblyName[] GetReferenc...
GetSatelliteAssembly Method System.Reflection.Assembly GetSatelliteAssem...
GetType Method type GetType(string name), type GetType(stri...
GetTypes Method type[] GetTypes()
IsDefined Method bool IsDefined(type attributeType, bool inhe...
LoadModule Method System.Reflection.Module LoadModule(string m...
ToString Method string ToString()
CodeBase Property System.String CodeBase {get;}
EntryPoint Property System.Reflection.MethodInfo EntryPoint {get;}
EscapedCodeBase Property System.String EscapedCodeBase {get;}
Evidence Property System.Security.Policy.Evidence Evidence {get;}
FullName Property System.String FullName {get;}
GlobalAssemblyCache Property System.Boolean GlobalAssemblyCache {get;}
HostContext Property System.Int64 HostContext {get;}
ImageRuntimeVersion Property System.String ImageRuntimeVersion {get;}
Location Property System.String Location {get;}
ManifestModule Property System.Reflection.Module ManifestModule {get;}
ReflectionOnly Property System.Boolean ReflectionOnly {get;}
For instance, one thing you might be interested in finding out is if the assembly resides in the Global Assembly Cache (GAC). In the Windows PowerShell 2.0 console, all assemblies are in fact in the GAC. But in the Windows PowerShell 2.0 ISE, and in the Windows PowerShell 3.0 console, this is not the case. If you find yourself using an assembly very often, you might want the assembly in the GAC. Here is how to find assemblies from the current appdomain that are not in the GAC.
PS C:\> [appdomain]::currentdomain.GetAssemblies() | where {!($_.globalassemblycache)}
GAC Version Location
--- ------- --------
False v2.0.50727 C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell_ISE.exe
False v2.0.50727 C:\Windows\system32\WindowsPowerShell\v1.0\CompiledComposition.Microsoft.Po...
Each loaded .NET Framework assembly contributes .NET Framework classes. To see the classes exposed by the assembly, you can use the gettypes method from the System.Reflection.Assembly class returned by the GetAssemblies method from the appdomain class. As you might expect, there are numerous .NET Framework classes. Interestingly enough, the more filter does not appear to work consistently when working interactively via the Windows PowerShell console, and it does not work at all in the Windows PowerShell ISE. So you might want to consider redirecting the output to a text file. One thing that will help is to sort the output by basetype. Here is the command to do that.
PS C:\> [appdomain]::currentdomain.GetAssemblies() | Foreach-Object {$_.gettypes()} | sort basetype
Do not expect to quickly find exotic, little known, unused .NET Framework classes. Most of the output, for the IT Pro will be rather pedestrian, lots of error classes, lots of enum, lots of structures, and the like. The output headings appear here:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
The first couple of pages of output do not event list a base type. Then, when we get to the first grouping of types that do expose a base type, the output is disappointing. Here are the first three lines from that section.
False True ModuleLoadExceptionHandlerException <CrtImplementationDeta...
False False CSharpMemberAttributeConverter Microsoft.CSharp.CShar...
False False CSharpTypeAttributeConverter Microsoft.CSharp.CShar...
False False WmiAsyncCmdletHelper Microsoft.PowerShell.C...
What is going on here? Remember that last year, I wrote a Hey Scripting Guy! blog entitled, Change a PowerShell Preference Variable to Reveal Hidden Data. Well, if you do not remember it, don’t worry, I did not remember the title either. But I did a search for preference variables, and I found it on the first try. Basically, what you need to do is change the $FormatEnumerationLimit preference variable. By default, the enumeration limit value is 4; and so after four items. it does not use any more space. I like to change it to 20.
But unfortunately, this does not solve the problem. The problem here is that the .NET Framework class names are extremely long...in some cases, really long. Therefore, using the basic redirection arrow does not help capture all the output. In this case, you need to move beyond the defaults and specify a custom width for the output. The best way to do this is to use the Out-File cmdlet. By setting the width to 180, you will capture most (but not all) of the really long .NET Framework class names. (Each time you make the file wider, you also increase the file size and make the file a bit more difficult to use.) For example, a width of 500 characters will create a file about 8 MB in size. A width of 180 will be around 3.5 MB in size (with over 10,000 lines in it). Here is the command I used.
PS C:\> [appdomain]::currentdomain.GetAssemblies() | % {$_.gettypes()} | sort basetype | Out-File -FilePath c:\fso\gettypes.txt -Width 180 –Append
Now that you have the list, you can peruse it at your leisure. Use Get-Member or MSDN to help you find things. I can tell you, from experience that it can spend a very long time looking through stuff. Have fun, and I will talk to you on Monday.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy