User Rating: 5 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Active
 

Tested On: Windows PowerShell 5.0, 5.1, 6.0.0.9, 6.0.0.10, PowerShell (Mac OS X) 6.0.0.9

Beginning in Windows PowerShell 5.0, you can create PowerShell classes in scripts and modules. To create different versions of a class, it's best to place the class in a module and use the versioning features of the module to create versions of your class.

But, to import the classes defined in a module into your session, you can't use Import-Module or even #Requires -Module. Instead, use the Using statement, introduced in PowerShell 5.0, which imports the module, including its classes, into the caller's session. The Using statement is not yet documented, so this is a bit confusing. Let's examine it.

 

Import-Module Does Not Import Classes

Let's start with the standard module import command, Import-Module. With the advent of module auto-loading in PowerShell 3.0, Import-Module almost became obsolete. But, the introduction of side-by-side versioning in PowerShell 5.0 resurrected Import-Module and, in fact, made it your new best friend, because Import-Module has four version parameters that let you specify the version you want: ModuleVersion (minimum version), MaximumVersion, RequiredVersion, and FullyQualifiedName, which takes a ModuleSpecification object.

Import-Module would be a splendid tool to use to import classes. But, Import-Module does not import PowerShell classes defined in the module.

In this test module, ImportTester, I've created an ImportTester class and a Test-Function function. I've explicitly exported the function, but Export-ModuleMember doesn't have a Class parameter to export classes.

importtester psm1

 

The ImportTester module has a module manifest, ImportTester.psd1. The value of the FunctionsToExport key is the Test-Function function. But, module manifests don't have a ClassesToExport key. In fact, there are no class-related keys in the module manifest.

 importtester psd1

 

I can use Import-Module to import the ImportTester module and explicitly import version 1.0.0.0. The module is imported and its function is exported, but its ImportTester class is not exported.

ImportModuleDoesNotWork

 

#Requires Does Not Import Classes

The #Requires statement is also really useful. Its value can be a module name string or a ModuleSpecification object that can include a version number. But, sadly, although #Requires imports the module, it doesn't import classes defined in the module.

In this test script, I'll use a #Requires statement with a ModuleSpecification object and run the script in the console. The #Requires statement imports the correct version of the module, including the Test-Function function, but it doesn't import the ImportTester class.

RequiresDoesNotImportClasses

 

Using Module Statement Imports Classes

The Using statement, introduced in Windows PowerShell 5.0, has two uses. It imports assemblies (using namespace) and it imports modules (using module), including their classes. The assembly-importing feature is a convenience; the module-importing feature is a requirement, because there's no other way to import classes from script modules.

The Using statement has a module parameter that takes a module name string or a ModuleSpecification object.

Syntax:

Using namespace <namespace>
Using module <string>
Using module <Microsoft.PowerShell.Commands.ModuleSpecification>

 

Unlike #Requires, you can use a Using module statement in a script or interactively in the console, so let's use it to import the ImportTester module.

UsingModuleString

 

The import succeeds. The Using module statement imported the module, including its Test-Function function. And, unlike Import-Module and #Requires, the Using module statement imported the ImportTester class.

UsingImportsClassesMethod

 

You can also create a version-specific Using module statement with a ModuleSpecification object. Again, it imports the ImportTester class.

UsingModuleSpec

 

Rules (and Gotchas) for the Using Module Statement

Here are the rules for the Using Module statement:

  • Using module statement can be used in a script or module, or run interactively in the console.
  • In a script or module, the Using module statement must be the first statement. No uncommented statement can precede it, including parameters.
  • Its value can be a string (module name or path to module file) or a ModuleSpecification hashtable.
  • Using module imports classes from the root module (ModuleToProcess) of a script module or binary module. The Using statement does not (consistently) import classes defined in nested modules or classes defined in scripts that are dot-sourced into the module. (These classes are, of course, available in module scope without the Using statement.) (Thanks to Brandon Olin for the info about scripts.)
  • Using module statement cannot include any variables. Its values must be static.

    PS C:\> $moduleName = 'ImportTester'
    PS C:\> Using module $moduleName
    At line:1 char:1
    + Using module $moduleName + ~~~~~~~~~~~~~~~~~~~~~~~~
    '$moduleName' is not a valid value for using name.     + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
        + FullyQualifiedErrorId : InvalidValueForUsingItemName

    To use variable values in a Using module statement, create a script block that includes the Using module statement and dot-source it into the script. (Thanks to Bartek Bielawski for this solution.)

    Param (
    	[parameter(Mandatory)]
    	[string]
    	$ModuleName
    )
    
    $scriptBody = "using module $ModuleName"
    $script = [ScriptBlock]::Create($scriptBody)
    . $script
    
    ...
  • Using module statement can include a path to a module that is not in a .psd1 file, but the path must not include variables. Instead, use a relative path...

    PS C:\> Using module $home\Documents\ClassTester\1.0.0.0\ClassTester.psd1
    At line:1 char:1
    + Using module $home\Documents\ClassTester\1.0.0.0\ClassTester.psd1
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    '$home\Documents\ClassTester\1.0.0.0\ClassTester.psd1' is not a valid value for using name.
        + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
        + FullyQualifiedErrorId : InvalidValueForUsingItemName
    PS C:\> cd $home 
    PS C:\Users\User01> Using module .\Documents\ClassTester\1.0.0.0\ClassTester.psd1 
    PS C:\Users\User01>

Using namespace imports namespaces

Just for completeness, I want to mention the namespace parameter of Using, which imports the namespace from a .NET assembly into the current session.

Here it is in action. I want to create a ModuleSpecification object from a hash table. This isn't required for the FullyQualifiedName parameter, or for the #Requires or the Using statement. Those will take a hash table and convert it to a ModuleSpecification object for you. But, sometimes, you want to create a ModuleSpecification object explicitly.

To do so, by default, you need to type its full .NET name, Microsoft.PowerShell.Commands.ModuleSpecification, with its namespace, Microsoft.PowerShell.Commands.

usingNamespace1

 

To make it easier to type, you can use the Using namespace statement to import the namespace. Then, you don't need to type the full name.

UsingNamespace2

But, this works only when the assembly that contains the namespace is available. That is, importing the namespace and using the type name works only when typing the fully-qualified name with the namespace already works.

This isn't the same as the Add-Type cmdlet, which adds a class to the session that is NOT available, even if you type its full name.

For example, I want to use System.Windows.Forms.MessageBox. But, because the type isn't available, Using namespace won't help.

UsingNamespace3

 

In this case, you need Add-Type.

usingNamespaceAddType

 

Which modules export classes?

If you're defining classes in a module and you want users to be able to use them, be sure to write about them in your help topics. Get-Help does not support PowerShell classes, but you can write about the classes that you define in an About topic, like this one.

Help is really critical. The Using statement isn't documented and it isn't (yet) well known, so users are quite likely to use Import-Module or #Requires, or rely on module auto-loading, and then be frustrated because the class isn't imported. Or, more likely, they won't even know that the class exists until they hit a feature that requires it.

To find the classes in a module, you can use the ImplementingAssembly property of imported modules, but it's not well-known either. There are no class-related parameters of Export-ModuleMember, Find-Module, or Get-InstalledModule, and no class-related keys in the module manifest. If you manage to learn about the class and use a Using statement to import the class, there's no help for classes other than help for the script or module in which the class is defined.

So, for now, read the help. And use the Using statement.

 

June Blender is a technology evangelist at SAPIEN Technologies, Inc. and a Microsoft Cloud & 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 Trevor Sullivan, Microsoft Cloud & DataCenter MVP, for his video, Learn about the Using Statement, which introduced everyone to the Using statement.

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 Former and Future Customers - Questions forum.
Copyright © 2024 SAPIEN Technologies, Inc.