PowerShell 7, like Windows PowerShell, is a .NET application—an application built on top of and leveraging the .NET Framework, particularly the base class libraries.
Microsoft built Windows PowerShell using the full .NET Framework employing a design in which cmdlets are a thin and intelligent layer above the .NET Framework. Cmdlets rely on the .NET Framework to do all the heavy lifting. Windows PowerShell 5.1 leverages the Microsoft .NET Framework version 4.5.2.
PowerShell 7 is a complete and open source reimplementation of Windows PowerShell based on the open source .NET Core Framework using .NET Core 3.1. This has been a huge reengineering job and one that has thrown up a few challenges.
In PowerShell, commands are contained within modules. To run any command, PowerShell must first load a module that contains that command. PowerShell can only load modules that contain cmdlets and other binary artifacts if their developer has enabled this.
The PowerShell team reimplemented most of the commands contained in the core PowerShell convert to modules and ensured that the Active Directory module works with PowerShell 7.
Some Microsoft product teams and other external teams have not yet ported their modules for you to use natively in PowerShell 7. To get around this issue, the PowerShell team developed a compatibility solution that enables most commands previously developed for Windows PowerShell to work in PowerShell 7.
The compatibility solution enables you to use PowerShell 7 to manage a wide range of features in Windows Server, as demonstrated throughout this book. That does leave, however, a small set of features, modules, and commands that you are not able to use with PowerShell 7. For those, there are work‐arounds.
This chapter looks at the following topics:
In this chapter, you use one host,
DC1
. This is a Windows Server 2019 Datacenter Edition host.
Figure 2.1 shows the system you use in this chapter.
Figure 2.1: System used in this chapter
Before looking at compatibility with Windows PowerShell, you should understand PowerShell modules. Modules are fundamental to PowerShell, and understanding how they work is important when dealing with compatibility.
A PowerShell module is a package of commands. A module contains members, which can include cmdlets, providers, functions, variables, and aliases. Modules are the means that developers use to package and distribute PowerShell commands. You import a module into PowerShell to use the members of the module.
There are four main module types in PowerShell. Each one is used differently and solves different problems.
.PSM1
extension and contain function definitions. When you import a script module, the functions defined in the
.PSM1
files are imported into the current PowerShell session..PSD1
extension that contains details about a module. This information includes metadata (author, copyright) and instructions on what the module contains and how PowerShell should load it.Import‐Module
and specifying the DLL. To simplify loading of a binary module, you use a module manifest to help PowerShell load the needed members such as related help files.Any module can have additional members, although PowerShell needs to have a manifest to enable it to load those members. For example, you could include some XML for formatting or include a help file for the module.
Script modules were an easy way to distribute functions and replaced the practice of dot sourcing of
.PS1
files, which was a common approach many users took with PowerShell version 1.
A manifest module is one that has a PowerShell module manifest file. This is a text file that contains the module details in the form of a hash table. With a module manifest, a developer can repackage a snap‐in into a binary module. You can create a module manifest using
New‐ModuleManifest
, as described at docs.microsoft.com/powershell/module/microsoft.powershell.core/new-modulemanifest.
You use dynamic modules to create a module on the fly based on the script block. As such, these are intended to be short‐lived, and you cannot look at them using
Get‐Module
. Dynamic modules do not require manifests.
To demonstrate the use of modules, you can create a simple module on
DC1
, as follows:
# 1. Create a simple script module—MyModule1
$MyModulePath = "C:\Users\$env:USERNAME\Documents\PowerShell\Modules\MyModule1"
$MyModule = @"
# MyModule1.PSM1
Function Get-HelloWorld {
"Hello World from My Module"
}
"@
New-Item -Path $MyModulePath -ItemType Directory -Force | Out-Null
$MyModule | Out-File -FilePath $MyModulePath\MyModule1.PSM1
Get-Module—Name MyModule1 -ListAvailable
You can see the output from these commands in Figure 2.2.
Figure 2.2: Creating a new module
These commands create a new module,
MyModule1
, which is a simple script module created as a
.PSM1
file. You can see in the output that this module exports a single command, the
Get‐HelloWorld
function.
There are three ways PowerShell imports a module into the current PowerShell session. First, you can use the
Import‐Module
cmdlet to import a module explicitly. Second, you can use the module autoload feature, introduced with PowerShell version 2. Finally, if you use
Get‐Command
to discover the commands within a module, PowerShell ensures the module is imported and then returns the requested commands.
Using
Import‐Module
allows you to load any module from any location. You can load just a single
.PSM1
file, a single .NET assembly, or a rich multimember module with the help of a module manifest.
With module autoload, when you use any command that is in a module you have not yet imported, PowerShell loads the module and then runs the command. This also loads any members of the module, such as a help file. You can turn this off by setting the PowerShell environment variable
$PSModuleAutoloadingPreference
to
none
.
You can use
Import‐Module
to import the
MyModule1
module you just created.
# 2. Import the MyModule1 module
Import-Module -Name MyModule1 -Verbose
You can see the output from these commands in Figure 2.3.
Figure 2.3: Importing the
MyModule1
module
You could also use module autoload by just specifying the
Get‐HelloWorld
function.
A module manifest in PowerShell is a file with a
.PSD1
extension that contains a hash table of values. These values, which are held in a PowerShell hash table, include module metadata, such as the author, plus other information to enable PowerShell to load the module.
The article at docs.microsoft.com/powershell/scripting/developer/module/how-to-write-a-powershell-module-manifest provides more information about manifests and describes how to write one.
You can add a manifest to the
MyModule1
module as follows:
# 3. Create and Test a new module manifest
$NMMFHT = @{
Path = "$MyModulePath\MyModule1.PSD1"
Author = "Thomas Lee"
CompanyName = 'PS Partnership'
Rootmodule = 'MyModule1.psm1'
}
New-ModuleManifest @NMMFHT
Get-Module -Name MyModule1 -List
# remove and re-import
Get-Module -Name MyModule1 | Remove-Module
Import-Module -Name MyModule1 -Verbose
Get-HelloWorld
These commands first create a new module manifest and review the module after you create the manifest. Then you import the module explicitly and finally run the
Get‐HelloWorld
function, as you can see in Figure 2.4.
Figure 2.4: Creating and using a manifest
You can view the hash table by viewing
MyModule1.PSD1
, or you can use your favorite code editor to update it as necessary.
In general, a module is a folder that contains one or more members. The simplest module is a
.PSM1
file with function definitions. In this case, you have a folder, perhaps called
MyModule
. Below this folder you have a file,
MyModule.PSM1
. PowerShell requires the folder name and the
.PSM1
filename to be the same; otherwise,
Import‐Module
does not import the module.
You can convert a script module into a manifest module by adding a
.PSD1
file to the module folder. In this case, the
.PSD1
file must have the same name as the module folder. In such a case, the manifest contains the filename of the
.PSM1
file. The filenames can be different but should be the same to avoid confusion.
After you have created
MyModule1
, you can view the files contained in the module using Windows Explorer. In Figure 2.5, you can see the module, with both the
.PSM1
and
.PSD1
files.
Figure 2.5: Viewing files in
MyModule1
PowerShell supports module versioning, which allows you to have multiple versions of a module on a host. You use
Import‐Module
to load a specific version or let it take the default of loading the latest version. For example, you could have both versions 1.0.0 and 1.1.0 of
MyModule
. By default,
Import‐Module
(and module autoload) would load the latest version, which is 1.1.0. You can specify the earlier module as needed.
You can create and use a module with multiple versions with the following code:
# 4. Create MyModule2 with 2 versions
# Create Module folders
$MyModule2Path =
"$env:USERPROFILE\Documents\PowerShell\Modules\MyModule2"
$MyModule2V1Path = "$MyModule2Path\1.0.0"
$MyModule2V2Path = "$MyModule2Path\2.0.0"
New-Item -Path $MyModule2Path -ItemType Directory -Force | Out-Null
New-Item -Path $MyModule2Path -Name '1.0.0' -ItemType Directory -Force |
Out-Null
New-Item -Path $MyModule2Path -Name '2.0.0' -ItemType Directory -Force |
Out-Null
# Create MyModule2V1.PSM1
$MyModule2V1 = @"
Function Get-HelloWorld2 {
"Hello World from MyModule2 (V1)"
}
"@
$MyModule2V1 | Out-File -Path "$MyModule2V1Path\MyModule2.PSM1"
# Create MyModule2V2.PSM1
$MyModule2V2 = @"
Function Get-HelloWorld2 {
"Hello World from MyModule2 (V2)"
}
"@
$MyModule2V2 | Out-File -Path "$MyModule2V2Path\MyModule2.PSM1"
# Create manifests for both versions of this module
$NMMFHV1HT = @{
Path = "$MyModule2V1Path\MyModule2.PSD1"
Author = "Thomas Lee"
CompanyName = 'PS Partnership'
Rootmodule = 'MyModule2.psm1'
}
New-ModuleManifest @NMMFHV1HT -ModuleVersion '1.0.0'
$NMMFHV2HT = @{
Path = "$MyModule2V2Path\MyModule2.PSD1"
Author = "Thomas Lee"
CompanyName = 'PS Partnership'
Rootmodule = 'MyModule2.psm1'
}
New-ModuleManifest @NMMFHV2HT -ModuleVersion '2.0.0'
These commands create the module folders and create the module itself.
In the previous section, you created a new module (
MyModule2
) with two versions. You can use the functions within the different versions of a module as follows:
# 5. Use MyModule2
# Discover, import and use MyModule2
Get-Module MyModule2 -ListAvailable
Import-Module -Name MyModule2 -Verbose -RequiredVersion '1.0.0'
Get-HelloWorld2
# Re-import MyModule2—by default the highest version
Import-Module -Name MyModule2 -Force -Verbose
# Use V2 Function
Get-HelloWorld2
You can see the results of these commands in Figure 2.6.
Figure 2.6: Using
MyModule2
In this code snippet, you use
Get‐Module
to discover
MyModule2
. You then load version 1.0.0 of
MyModule2
. If you use the
‐Verbose
switch, PowerShell displays the process of importing your module. Verbose output like this can be useful in debugging more complex modules. With version 1 of
MyModule2
loaded, you run the first version of the
Get‐HelloWorld2
function, showing the expected version 1 output. You then remove the old module explicitly and reimport the
MyModule2
module and use the function. You can see that because you did not specify a version, PowerShell loads the highest version of the function, which then returns the expected version 2 output.
You can have a simple module with just a
.PSM1
file. However, you must use a module manifest if you want to support multiple versions of the same module. In that case, the folder containing a given version must match the corresponding version number contained in the manifest. You can see the structure of
MyModule2
in the filestore in Figure 2.7.
Figure 2.7: Viewing
MyModule2
With module autoload, PowerShell loads the module that contains the command you are using before running the command. PowerShell uses the environment variable
PSModulePath
to hold a comma‐separated value list of paths in the Windows filesystem. PowerShell can discover the commands in any module. You can view this variable by typing
$Env:PSModulePath
.
In “Module Naming,” you created
MyModule2
, with two versions of the module. To demonstrate autoload, you can do the following:
# 6. Demonstrate autoload of MyModule2
Get-Module MyModule* | Remove-Module -Verbose
Get-HelloWorld2
In these commands you first remove all the
MyModule
modules and then, with no modules loaded, use a command in the module
MyModule2
, namely,
Get‐HelloWorld2
, provided for test purposes like this. You can see the output in Figure 2.8.
You can modify the module path environment variable(s) externally to PowerShell by using the
sysdm.cpl
applet. If you do so, the next time you enter PowerShell, you should see the updated environment variable value.
Windows supports two environment variables (of the same name)—one for the user and one for the system. PowerShell adds the values contained in the user and system Windows environment variables into the value
$Env:PSModulePath
. You can define both the system‐ and user‐level
PSModulePath
variables using
sysdm.cpl
. In most cases, the module autoload path is sufficient.
Figure 2.8: Using module autoload
Third‐party publishers ship installers that install their module(s) into a module‐specific location by extending the
PSModulePath
variable.
PowerShell does not persist any changes you make to the value of
$Env:PSModulePath
inside a PowerShell session. You can make any changes to your profile scripts if necessary.
When PowerShell uses autoloading, it has a large number of modules to search to discover which module has the requested command. To improve performance, PowerShell maintains an internal module analysis cache of all the modules on your system and the commands they contain. By default, PowerShell stores this cache at
$Env:LOCALAPPDATA\Microsoft\Windows\PowerShell\ModuleAnalysisCache
. You can view the cache file using
Get‐ChildItem
.
# 7. View Module Analysis Cache
$CF = "$Env:LOCALAPPDATA\Microsoft\Windows\PowerShell\"+
'ModuleAnalysisCache'
Get-ChildItem -Path $CF
Figure 2.9 shows the output and the file containing the cache.
Figure 2.9: Viewing the module analysis cache
At the start of each session, PowerShell spawns a low‐priority background thread that discovers the modules and the commands and updates the cache file accordingly. To learn more about the module analysis cache, see:
In most cases, your module analysis cache remains constant, since in most cases, you rarely change the modules on your system. Should you want, you can change the location of the cache file. There is really little reason to do so, however, and it could just introduce more troubleshooting challenges.
Windows PowerShell was first launched in 2006 and has been a built‐in component in Windows for more than a decade. The first two releases of what is now PowerShell 7 were known as PowerShell Core, with the emphasis on Core as in .NET Core. The development team released two versions (6.0 and 6.1). As part of the planning for the third major version of PowerShell, Microsoft decided to rename the product PowerShell 7, dropping the Core moniker. This book focuses on PowerShell 7.
With the first version PowerShell Core, it became clear that some Windows PowerShell modules did not work in the version of PowerShell based on .NET Core. An early solution to this issue was to use explicit remoting. You could create a PowerShell remoting session to your host using a PowerShell 5.1 remoting endpoint and then run your commands in that remoting session. That method works, but it means you have to manage the process and so is not ideal.
Implicit remoting is a feature of PowerShell that lets you use commands that are not available in locally installed modules. Exchange Server uses implicit remoting, and this is a handy solution cross‐platform. It could be used, for example, if you want to run AD commands from a Mac or Linux computer.
In PowerShell 7, whenever
Import‐Module
attempts to import any module, it looks to see whether that module is compatible with PowerShell 7. If the module is compatible, then
Import‐Module
loads it into PowerShell 7, but if not, then PowerShell loads the module in compatibility mode.
When
Import‐Module
imports a module in compatibility mode, it creates a PowerShell remoting session to the local host using a Windows PowerShell remoting endpoint, named
WinPSCompatSession
. PowerShell then imports the module into the remote session running Windows PowerShell and finally uses
Import‐PSSession
to import the commands into the current PowerShell 7 session. This creates functions that duplicate the names of the commands imported from the remote session. Those functions use implicit remoting to run the actual cmdlet logic in the remote session.
You can view the use of the compatibility solution as follows:
# 8. Import Server Manager Module on DC1 and use it
Get-Module ServerManager -ListAvailable
Import-Module ServerManager
Get-Module ServerManager | Format-Table -AutoSize -Wrap
Get-WindowsFeature -Name Hyper-V | Format-Table -AutoSize
$CS = Get-PSSession -Name WinPSCompatSession
Invoke-Command -Session $CS -ScriptBlock {
Get-WindowsFeature -Name Hyper-V | Format-Table -AutoSize
}
You can see the output from these commands in Figure 2.10.
Figure 2.10: Loading the ServerManager module
In the output, you can see that when you first use
Get‐Module
to find the ServerManager module, you see no output, which is expected because you have not yet loaded the module. You can then manually import the ServerManager module and review it. As you can see, this is a script module. Finally, you use a command to view a windows feature. The first time you attempt to view the Hyper‐V feature, you can see that the normal
DisplayName
field is missing from the output. This is because
Import‐Module
has not also imported the module's display XML into the PowerShell 7 session. As you can see, you can get the output by using the compatibility remoting session and having PowerShell do the formatting in that remote session.
If your script uses
Import‐Module
to enable you to access commands from multiple modules,
Import‐Module
checks to see whether
WinPSCompatSession
exists. If so, PowerShell uses the existing session to autoload additional modules. This means you only ever have one compatibility remoting session. You can read more about implicit remoting and
Import‐PSSession
at
docs.microsoft.com/powershell/module/microsoft.powershell.utility/import-pssession.
During the development of PowerShell 7, it became clear that a small number of modules would never work either natively or via the compatibility solution. If you did attempt to use them in the compatibility session, commands in the modules failed. The failure usually resulted in error messages that were unclear and not actionable. This was not a good user experience, particularly to new users.
To avoid that bad experience, PowerShell 7 has a list of modules that
Import‐Module
does not load (natively or via the compatibility solution), by default. You can override this logic by using
Import‐Module
with the
‐SkipEditionCheck
parameter, although that is unlikely to be successful.
In the PowerShell installation folder, you can find PowerShell's configuration file
$PSHOME\PowerShell.config.json
. You can view the contents of this file, as follows:
# 8. View JSON Configuration File on DC1
Get-Content -Path $PSHOME\powershell.config.json
You can see the output of this command in Figure 2.11.
Figure 2.11: Viewing the module deny list
This configuration file, which you can change as needed, contains a list of modules that
Import‐Module
does not load. These are based on module names, so if the product team updates these modules so that they become usable, you can change the file. Also, if and when those denied modules do change and you can use them in PowerShell 7, PowerShell may update the configuration file at the next PowerShell update.
Thanks to the great work by a combination of the PowerShell 7 product team and the PowerShell community, the majority of Windows PowerShell commands function properly in PowerShell 7. This means your Windows PowerShell scripts should run just fine in PowerShell 7, as this book amply demonstrates.
The compatibility solution does impose minor limitations caused by the serialization of data between the PowerShell 7 session and the compatibility remoting session. When you transfer data via remoting, PowerShell serializes the data into XML, transports the data, and then deserializes it at the other end. When a command receives commands from the remote session, that data is deserialized. This means that there are no object methods (aside from a few default ones that all objects have) returned. This is why, for example, the UpdateServices module does not work—it relies on object methods instead of cmdlets. Also, PowerShell changes the object type name to reflect the serialization.
Despite those limitations, almost all the modules supported by the compatibility solution work. (That is, the commands function and do their jobs.) That means you should be able to run Windows PowerShell scripts in PowerShell 7 successfully—as this book more than adequately demonstrates. But there are some features, modules, and commands PowerShell 7 does not support either natively or via the compatibility solution.
Despite a lot of hard work by the PowerShell team and others, there remains a small set of PowerShell 5.1 and earlier features, modules, and cmdlets that simply do not work with PowerShell 7, with or without the compatibility solution. These include the following:
‐ComputerName
parameter on some cmdletsAdd‐Computer
,
Checkpoint‐Computer
,
Remove‐Computer
, and
Restore‐Computer
commands from the
Microsoft.PowerShell.Management
modulePowerShell workflows were based on the Windows Workflow Framework component of the full NET Framework. The .NET Core team did not choose to implement the necessary components to support workflows in .NET Core. The workflow feature was not heavily used, and the PowerShell team decided not to carry it forward. If you are using workflows, you can either continue to use them by using Windows PowerShell or look into alternatives.
One use of workflows was to improve performance using the Workflow component's built‐in parallelism. With the implementation of
Foreach‐Object‐Parallel
, you can get the needed parallelism (and improve script run times) without using Windows PowerShell workflows.
It is not likely that workflows are going to be implemented with PowerShell 7. See the release notes at docs.microsoft.com/en-us/powershell/scripting/whats-new/breaking-changes-ps6?view=powershell-6.
PowerShell 7 does not support Windows PowerShell snap‐ins. In Windows PowerShell V1, you used snap‐ins to hold commands, but this approach lacked flexibility. With snap‐ins, you had to use a compiled language such as C# to write your command—you could not write your commands using PowerShell. The developer also needed to create an installer program (although there is a default installation program included with .NET). Also, the installer stored details of the module in a protected area of the registry, meaning you needed administrative permissions to install a module.
The module feature, which was added in Windows PowerShell 2, in effect replaced the snap‐in. Snap‐ins continue to be supported in Windows PowerShell. In some cases, you may be able to convert a snap‐in into a module by using a manifest. For other snap‐ins, you may need to ask your internal developer or external vendor to update their product (or seek alternative solutions that support PowerShell 7).
The WMI cmdlets are not supported in PowerShell 7. You can, and should, use the CIM cmdlets. The CIM cmdlets are lighter weight and provide improved usability and reduced network bandwidth. Although the article is old, you can read more about the CIM cmdlets at devblogs.microsoft.com/powershell/introduction-to-cim-cmdlets
/
.
Some Windows PowerShell commands contain the
‐ComputerName
parameter. For example, you can specify a value of that parameter to
Get‐Service
to have the cmdlet get services on the specified machine. In PowerShell 7, the following commands do not support the
‐ComputerName
parameter:
Clear‐EventLog
Get‐Process
Get‐Service
Limit‐EventLog
New‐EventLog
Remove‐Computer
Remove‐EventLog
Set‐Service
Test‐EventLog
Show‐EventLog
The
‐ComputerName
parameter is still used in PowerShell 7, just not to indicate that the cmdlet does remote processing internally, and it does not leverage PowerShell remoting.
PowerShell 7 does not implement the full Windows DSC feature. PowerShell 7 does implement
Invoke‐DSCResource
to invoke a DSC resource, but there is no local configuration manager, no push servers, or any of the other great features you used with DSC in Windows PowerShell.
The WSUS feature includes a module that is not compatible natively with PowerShell 7 and does not function acceptably in a compatibility session. The design of the
UpdateServices
module makes use of object methods, instead of the more usual approach of using cmdlets. Since methods are removed via serialization, you cannot use this module within the compatibility solution. If you want to manage WSUS, you must use Windows PowerShell.
Also, it is unlikely that there is an easy fix for the overall architecture of this module, since it relies on Simple Object Access Protocol (SOAP) for communications with the WSUS server. The .NET Core team has not implemented SOAP and appears to have no plans to do so. The long‐term solution is for the WSUS team to redesign their client‐server implementations to use Representational State Transfer (REST) or other supported protocols. At the time of writing, no plans have been announced.
The Best Practices module also does not work either natively or via the compatibility solution. The Best Practices team needs to redevelop the module, and feature, to make use of .NET Core.
The WebAdministration module, part of the IIS Management tools, includes a PowerShell provider. When you load this module in Windows PowerShell, Windows PowerShell loads the provider and creates an
IIS:
drive you use when you administer IIS. While the commands in both the
IISAdministration
and
WebAdministration
modules more or less work, any command that uses the provider would fail.
The Microsoft.PowerShell.Management module in PowerShell does not contain the
Add‐Computer
,
Checkpoint‐Computer
,
Remove‐Computer
, or
Restore‐Computer
cmdlets. You can use these commands by creating a Windows PowerShell remote session and invoking the commands in that remoting session.
The default work‐around for any Windows PowerShell compatibility issue is simply to not use PowerShell 7 until there is a solution to your specific issues. Microsoft offers full support for Windows PowerShell 5.1 for the foreseeable future, so there is little risk in continuing to use Windows PowerShell.
What might be more effective is a hybrid strategy that combines running PowerShell 7 natively where you can or via the compatibility solution. Then use Windows PowerShell as needed until you can migrate fully to PowerShell 7.
For the few Windows PowerShell features not in PowerShell 7, there is no easy work‐around aside from continuing to use Windows PowerShell.
That is made more complex by the cross‐platform nature of PowerShell. It is harder to add features to PowerShell where .NET and the OS itself do not provide the necessary supporting features.
The modules and providers that do not work natively in PowerShell or via the compatibility list also have no easy solution. The code that would need to be updated to support PowerShell 7 natively is proprietary. The Microsoft product teams currently would need to make those changes, and for some teams, particularly the WSUS team, that could be a lot of otherwise unplanned work.
Some Microsoft product teams, for example the Active Directory team, were able to ensure the Active Directory module works with PowerShell 7, although you do need the latest version of that module. As Chapter 3, ”Managing Active Directory,” demonstrates, this means you can manage your AD database within PowerShell 7. But at the same time, the AD Deployment module, which you need to deploy AD in your environment, works only via the compatibility mechanism. So, you can install new forests, new domains, and new domain controllers via the compatibility solution.
For commands that formerly used the
‐ComputerName
parameter, you can use
Invoke‐Command
to run the command remotely and return the results. This book makes extensive use of this feature to run commands on different computers.
In summary, PowerShell 7 has done a good job implementing backward compatibility with Windows PowerShell. Almost all of the features of Windows PowerShell are available to you in PowerShell.7. The compatibility solution, which uses implicit remoting, extends the set of commands available to you, although there are some minor issues in some cases. Finally, there are a few things that do not work in PowerShell 7, and for those you have other options.