User Rating: 5 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Active
 

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.

image001 

 

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.

 image003

 

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.

image005

 

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.

 

If you have questions about our products, please post in our support forum.
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 Trial Software Questions forum.
Copyright © 2017 SAPIEN Technologies, Inc.