Working directly with .NET is important because as a PowerShell developer you do not want to be limited to the things that have been turned into neat little commands. Being able to directly use these types opens the possibility of using third-party assemblies, such as those on nuget.org:
This article is a small section from the book Mastering Powershell Scripting, Fourth Edition by Chris Dent. This extensively revised edition helps you navigate through PowerShell’s capabilities and includes new chapters on debugging and troubleshooting and creating GUIs (online chapter).
Mastering PowerShell Scripting: Automate and manage your environment using PowerShell 7.1, 4th Edition
$44.19
in stock
6 used from $63.73
Amazon.com
It is important to understand that .NET is vast; it is not possible to cover everything about .NET in a single article. This article aims to show how .NET may be used within PowerShell based on the Microsoft Docs references:
The goal is therefore not to learn everything about .NET, but to learn enough know-how to learn more.
Following are some of the crucial topics that you should be well-versed in to be able to increase your flexibility in PowerShell with .NET
- Assemblies
- Types
- Enumerations
- Classes
- Namespaces
- The using keyword
- Type accelerators
- Members
- Fluent interfaces
- Reflection in PowerShell
In this article, we will just cover the first six topics till using keywords. The book, however, covers the entire spectrum of concepts along with a lot more to make you proficient in leveraging PowerShell.
Assemblies are the starting point; they contain the types that can be used in PowerShell.
Assemblies
An assembly is a collection of types and any other supporting resources. .NET objects are implemented within assemblies. An assembly may be static (based on a file) or dynamic (created in memory).
The assembly type load locations can be seen by exploring the Assembly
property of the type. For example, the String type is loaded from System.Private.CoreLib.dll
in PowerShell 7:
PS> [System.String].Assembly.Location
C:\Program Files\PowerShell\7\System.Private.CoreLib.dll
In PowerShell 7, the assemblies that are loaded by default or those that can be loaded by name are in the $PSHome
directory.
You can view the list of currently loaded assemblies in a PowerShell session using the following statement:
[System.AppDomain]::CurrentDomain.GetAssemblies()
The list can be quite extensive and can grow as different modules (which might depend on other .NET types) are loaded. The first few lines are shown here:
GAC Version Location
--- ------- --------
False v4.0.30319 C:\Program Files\PowerShell\7\System.Private.CoreLib.dll
False v4.0.30319 C:\Program Files\PowerShell\7\pwsh.dll
False v4.0.30319 C:\Program Files\PowerShell\7\System.Runtime.dll
False v4.0.30319 C:\Program Files\PowerShell\7\Microsoft.PowerShell.Co...
False v4.0.30319 C:\Program Files\PowerShell\7\System.Management.Autom...
False v4.0.30319 C:\Program Files\PowerShell\7\System.Threading.Thread...
False v4.0.30319 C:\Program Files\PowerShell\7\System.Runtime.InteropS...
False v4.0.30319 C:\Program Files\PowerShell\7\System.Threading.dll
You can use the ClassExplorer
module from the PowerShell Gallery to simplify the previous command:
Install-Module ClassExplorer
Get-Assembly
Assemblies can be explicitly loaded with the Add-Type
command. PowerShell 7 includes the System.Windows.Forms.dll
file in the $PSHome
folder but will only load it if told to. The DLL is used to write some graphical user interfaces. You can therefore load it using the name alone (instead of a full path to the DLL):
Add-Type -AssemblyName System.Windows.Forms
Once an assembly, and the types it contains, has been loaded into a session it cannot be unloaded without completely restarting the PowerShell session. This might affect an upgrade to an existing module based on a DLL; PowerShell cannot unload the DLL and load a newer version. When a DLL is in use by an application, it is locked; attempting to delete the DLL file would fail.
Much of PowerShell itself is implemented in the System.Management.Automation
DLL. You can view details of the DLL using the following statement:
[System.Management.Automation.PowerShell].Assembly
In this statement, the PowerShell
type is used to get information about the System.Management.Automation
assembly.
Any other type in the same assembly may be used to get the same Assembly property. The PowerShell type could be replaced with any other type implemented as part of PowerShell itself:
[System.Management.Automation.PSCredential].Assembly
[System.Management.Automation.PSObject].Assembly
In Windows PowerShell, assemblies are often loaded from the Global Assembly Cache (GAC). PowerShell 7 (and other .NET Core applications) cannot use the GAC, which is why the GAC property in each of the assemblies in use by PowerShell 7 is False.
About the Global Assembly Cache
In Windows PowerShell, most types exist in DLL files stored in %SystemRoot%\Assembly
. This folder stores what is known as the Global Assembly Cache (GAC). The DLL files registered here can be used by any .NET Framework application on the computer. Each DLL may be used by name, rather than an application needing to know the exact path to a DLL.
You can use the Gac
module, in the PowerShell Gallery, to list assemblies. The versions of an assembly in the GAC will vary depending on the installed versions of the .NET Framework (and any other installed components, such as Software Development Kits or SDKs):
PS> Install-Module Gac -Scope CurrentUser
PS> Get-GacAssembly System.Windows.Forms
Name Version Culture PublicKeyToken PrArch
---- ------- ------- -------------- ------
System.Windows.Forms 2.0.0.0 b77a5c561934e089 MSIL
System.Windows.Forms 1.0.5000.0 b77a5c561934e089 None
System.Windows.Forms 4.0.0.0 b77a5c561934e089 MSIL
If a specific version is required, you can use the full name of the assembly as it uniquely identifies the assembly:
Add-Type -AssemblyName 'System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
You can use the Gac
module to show the FullName
value used in the previous code. The values displayed depend on the installed versions of the .NET Framework:
PS> Get-GacAssembly System.Windows.Forms | Select-Object FullName
FullName
--------
System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
An assembly typically contains several different types.
Types
A type is used to represent the generalized functionality of an object. This description is vague, but a .NET type can be used to describe anything; it is hard to be more specific. To use this book as an example, it could have several types, including the following:
- PowerShellBook
- TextBook
- Book
Each of these types describes how an object can behave. The type does not describe how this book came to be, or whether it will do anything (on its own) to help create one.
In PowerShell, types are written between square brackets. The [System.AppDomain]
and [System.Management.Automation.PowerShell]
statements, used when discussing previous assemblies, are types.
The type of an object can be revealed by the Get-Member
command (the output below is truncated):
PS> 1 | Get-Member
TypeName: System.Int32
Name MemberType Definition
---- ---------- ----------
CompareTo Method int CompareTo(System.Object value), int ...
Equals Method bool Equals(System.Object obj), bool Equ...
GetHashCode Method int GetHashCode()
You can also use the GetType method, for example, by using the method on a variable:
PS> $variable = 1
PS> $variable.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
You can create types using several different keywords, including enum, struct, interface
, and class
.
Enumerations
An enumeration is a specialized type that is used to express a list of constants. Enumerations are used throughout .NET and PowerShell.
PowerShell itself makes use of enumerations for many purposes. For example, the possible values for the $VerbosePreference variable are described in an enumeration:
PS> $VerbosePreference.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True ActionPreference System.Enum
Notice that the BaseType
is System.Enum
, indicating that this type is an enumeration.
The possible values for an enumeration can be listed in several different ways. The most convenient of these is to use the GetEnumValues()
method on the enumeration type:
PS> $VerbosePreference.GetType().GetEnumValues()
SilentlyContinue
Stop
Continue
Inquire
Ignore
Suspend
Break
Enumerations are relatively simple types. They contain a list of constants that you can use in your code. A class is more complex.
Classes
A class is a set of instructions that dictates how a specific instance of an object behaves, including how it can be created and what it can do. A class is, in a sense, a recipe.
In the case of this book, a class might include details of authoring, editorial processes, and publication steps. These steps are, hopefully, invisible to anyone reading this book; they are part of the internal implementation of the class. Following these steps will produce an instance of the PowerShellBook
object.
Once a class has been compiled, it is used as a type. The members of the class are used to interact with the object.
Classes (and types) are arranged and categorized into namespaces.
Namespaces
A namespace is used to organize types into a hierarchy, grouping types with related functionality together. A namespace can be considered like a folder in a file system.
PowerShell is for the most part implemented in the System.Management.Automation
namespace. This namespace has associated documentation:
Similarly, types used to work with the filesystem are grouped together in the System.IO
namespace:
For the following given type name, the namespace is everything before the final label. The namespace value is accessible as a property of the type:
PS> [System.IO.File].Namespace
System.IO
In PowerShell, the System namespace is implicit. The System.AppDomain
type was used to show which assemblies PowerShell is currently using. This can be shortened to:
[AppDomain]::CurrentDomain.GetAssemblies()
The same applies to types with longer names, such as System.Management.Automation.PowerShell
, which can be shortened to:
[Management.Automation.PowerShell].Assembly
PowerShell automatically searches the System namespace for these types. The using keyword can be used to look up types in longer namespaces.
The using keyword
The using
keyword simplifies the use of namespaces and can be used to load assemblies or PowerShell modules. The using
keyword was introduced with PowerShell 5.0.
You can use the using
keyword in a script, a module, or the console. In a script, the using
keyword can only be preceded by comments.
The using module
statement is used to access PowerShell classes created within a PowerShell module. In the context of working with .NET, namespaces and assemblies are of interest.
Summary
Delving into .NET significantly increases the flexibility of PowerShell over using built-in commands and operators. .NET is made up of hundreds of classes and enumerations, many of which can be easily used in PowerShell.
.NET types are arranged in namespaces, grouping types with similar purposes together. For example, the System.Data.SqlClient namespace contains types for connecting to and querying Microsoft SQL Server instances.
The using keyword, introduced with PowerShell 5.1, allows types to be used by name only, instead of the full name that includes the namespace. Learn more from the book Mastering PowerShell Scripting, Fourth Edition by Chris Dent.
About the Author
Chris Dent is an automation specialist with deep expertise in the PowerShell language. Chris is often found answering questions about PowerShell in both the UK and virtual PowerShell user groups. Chris has been developing in PowerShell since 2007 and has released several modules over the years.