ImplementingAssembly: Find PowerShell Classes in Modules
- Details
- Written by June Blender
- Last Updated: 06 January 2017
- Created: 05 January 2017
- Hits: 21247
Beginning in PowerShell 5.0, you can create your own .NET classes and include them in scripts and modules. I've been having fun working on and teaching A Class of Wine. If you're a PowerShell scripter, but not a developer, creating your own classes really helps you to understand the concepts in .NET classes.
But, for the past year, many of us been complaining about how hard it is to detect PowerShell classes that are defined in modules. We were missing the ImplementingAssembly property! Thanks to PowerShell MVP Bartek Bielawski (@bielawb) for finding it.
Create Classes in a Script or Script Module
You can define classes in a PowerShell script or script module. When you dot-source the script, the classes in your script are exported into the session.
Script modules (.psm1) are a bit different. Neither the #Requires directive nor the Import-Module cmdlet import the classes in a module into the session. To import classes, use the Using statement with its Module parameter.
The Using module statement imports all classes defined in script and binary modules into the session. There's no official way to hide the classes in a script module. The Export-ModuleMember cmdlet doesn't have a Classes parameter and there are no module manifest keys, like ClassesToProcess. There's also no Help for a class, although you can use script help and About topics for classes to describe the classes in a script or module.
But, how do I know that a module even contains classes?
Find classes in a script module
To discover and identify the classes in a module, use the ImplementingAssembly property of modules that was introduced in PowerShell 5.0.
I've written a test script module (.psm1) that defines ImportTester and Dog classes along with a function.
When I import the module into the session (#Requires, Import-Module, or the Using module statement), the ImplementingAssembly property of the imported module (PSModuleInfo object) represents the assembly-like feature of my module that create new types. Its DefinedTypes property returns the classes that I defined.
PS C:\> Import-Module ClassTester -PassThru ModuleType Version Name ExportedCommands ---------- ------- ---- ---------------- Script 1.0.0.0 ClassTester Import-Function PS C:\ > (Get-Module ClassTester).ImplementingAssembly.DefinedTypes IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False ImportTester System.Object False False ImportTester_ System.Object True False Dog System.Object False False Dog_ System.Object
To eliminate static helpers of the class, filter by the IsPublic property.
PS C:\> (Get-Module ClassTester).ImplementingAssembly.DefinedTypes | where IsPublic IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False ImportTester System.Object True False Dog System.Object
And, to get just the class names, select the Name property of DefinedTypes.
PS C:\> ((Get-Module ClassTester).ImplementingAssembly.DefinedTypes | where IsPublic).Name ImportTester Dog
Note that I used Import-Module to import the module, so the classes that I defined in my script module are not imported into the session.
PS C:\> [ImportTester]
Unable to find type [ImportTester].
At line:1 char:1
+ [ImportTester]
+ ~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (ImportTester:TypeName) [], RuntimeException
+ FullyQualifiedErrorId : TypeNotFound
But, I can still detect them by using the ImplementingAssembly property.
It also works when I use the Using Module statement to import the module and its classes.
PS C:\> using module ClassTester PS C:\> (Get-Module ClassTester).ImplementingAssembly.DefinedTypes | where IsPublic | Select Name Name ---- ImportTester Dog PS C:\> [ImportTester] IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False ImportTester System.Object
About the ImplementingAssembly property
ImplemetingAssembly is a property of the PSModuleInfo object that Get-Module returns. It was added in PowerShell 5.0.
The ImplementingAssembly property value is a System.Reflection.Emit.AssemblyBuilder object that represents a dynamic assembly. The AssemblyBuilder class is in the System.Reflection namespace, which contains types that get information about managed code by examining the code metadata; a bit like the PowerShell AST.
ImplementingAssembly is populated only for script modules and binary modules. It is null on manifest modules and it does not capture classes defined in nested modules.
ImplementingAssembly is also null until a module is imported into the session.
PS C:\> (Get-Module -ListAvailable ClassTester).ImplementingAssembly PS C:\> PS C:\> Import-Module ClassTester PS C:\> (Get-Module ClassTester).ImplementingAssembly GAC Version Location --- ------- -------- False v4.0.30319
So, to detect classes in a module, you have it import the module, then look for a non-null value of the ImplementingAssembly property. In this case, I'll limit my search to modules that I've installed from PowerShell Gallery.
PS C:\> Get-InstalledModule | foreach { Import-Module $_.Name } PS C:\> Get-Module | where ImplementingAssembly ModuleType Version Name ---------- ------- ---- Binary 3.3.27.0 AWSPowerShell Binary 1.0.0.0 CimCmdlets Binary 8.1.161... DynamicsCRM-Automation Binary 2.3.0.46 ISESteroids Script 1.7.0.0 PoshRSJob Binary 1.1.1 PowerForensics Binary 1.2.0 PSScriptAnalyzer
The PoshRSJob module (version 1.7.0.0) is a script module with a value for the ImplementingAssembly property. Let's get the names of the classes that it defines.
NOTE: PowerShell classes were added to the PoshRsJob module in version 1.7.0.0 and removed in version 1.7.2.4 to make the module work in pre-5.0 versions of PowerShell. How did I know? Release Notes
PS C:\> (Get-Module PoshRSJob).ImplementingAssembly.DefinedTypes | where IsPublic | Select Name Name ---- V2UsingVariable RSRunspacePool RSJob
To examine the members of the class, use the Using module statement to import the class into the current session. I'll remove the module, then re-import it with the Using statement. Now, the classes that it defines are available in my session.
PS C:\> Get-Module PoshRsJob | Remove-Module PS C:\> using module PoshRsJob PS C:\> [RSJob] IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False RSJob System.Object PS C:\> $rsJob = [RSJob]::New() PS C:\> $rsJob | gm TypeName: RSJob Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() Batch Property string Batch {get;set;} Command Property string Command {get;set;} Completed Property bool Completed {get;set;} Error Property System.Management.Automation.PSDataCollection[System.Management.Automation.ErrorReco Finished Property System.Threading.ManualResetEvent Finished {get;set;} Handle Property System.Object Handle {get;set;} HasMoreData Property bool HasMoreData {get;set;} ID Property int ID {get;set;} InnerJob Property powershell InnerJob {get;set;} InstanceID Property string InstanceID {get;set;} Name Property string Name {get;set;} Output Property System.Object Output {get;set;} Runspace Property System.Object Runspace {get;set;} RunspacePoolID Property string RunspacePoolID {get;set;} State Property System.Management.Automation.PSInvocationState State {get;set;} Debug ScriptProperty System.Object Debug {get=$this.InnerJob.Streams.Debug;} HasErrors ScriptProperty System.Object HasErrors {get=if ($psversiontable.psversion.major -ge 3){... Progress ScriptProperty System.Object Progress {get=$this.InnerJob.Streams.Progress;} Verbose ScriptProperty System.Object Verbose {get=$this.InnerJob.Streams.Verbose;} Warning ScriptProperty System.Object Warning {get=$this.InnerJob.Streams.Warning;}
The GAC and Location properties of the AssemblyBuilder object tell you where the classes are defined. For a script module, GAC is always False and Location is null. But they're populated for a binary module.
Location is the path and name of the file that defines classes.
PS C:\> (Get-Module PowerForensics).ImplementingAssembly GAC Version Location --- ------- -------- False v4.0.30319 C:\Program Files\WindowsPowerShell\Modules\PowerForensics\1.1.1\PowerForensics.dll
GAC tells you when the DLL that defines the classes is in the Global Assembly Cache, a storage location for class libraries
PS C:\> (Get-Module PSScheduledJob).ImplementingAssembly GAC Version Location --- ------- -------- True v4.0.30319 C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Micros... PS C:\> (Get-Module PSScheduledJob).ImplementingAssembly | Select Location Location -------- C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerShell.ScheduledJob\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.ScheduledJob.dll
To find the file in which a script module defines its classes, use the FullName or ScopeName properties, although there appears to be an encoding error.
So, if you're wondering about the PowerShell classes that are defined in modules, import the module and then use the ImplementingAssembly property.
June Blender is a technology evangelist at SAPIEN Technologies, Inc. and a Microsoft Cloud and Datacenter MVP. You can reach her at This email address is being protected from spambots. You need JavaScript enabled to view it. or follow her on Twitter at @juneb_get_help.
Special thanks to Bartek Bielawski (@bielawb) for his help with this solution.
For licensed customers, use the forum associated with your product in our Product Support Forums for Registered Customers.
For users of trial versions, please post in our Former and Future Customers - Questions forum.