Using Windows PowerShell Modules in PowerShell 7

With the advent of PowerShell Core and PowerShell 7, not all modules have been updated to be compatible with core engine updates or cross-platform accessibility. Instead of needing to switch between multiple versions of PowerShell depending on the tasks at hand, PowerShell 7 has added compatibility layers for existing Windows PowerShell compatible modules.

Of course, with any compatibility layer, there are always situations that will not work properly or unexpected behaviors. In this article, we explore how the Windows Compatibility layer works within PowerShell 7 and what you need to be aware of.

There are still many modules that have not been updated to work with PowerShell 7. This can be due to many different reasons, but generally, it’s due to one of the following.

  • .NET Core incompatibility
  • Windows PowerShell core engine dependencies
  • Windows-specific API’s not available in PowerShell 7

Notably, with the change to .NET Core 3.1, there are many breaking changes that .NET Framework (4.x) has and may prevent modules from working correctly with .NET Core and therefore PowerShell 7. Additionally, there are core engine changes and Windows-specific APIs that a module may need to update to work properly with PowerShell 7.

One big note here is that these compatibility layers are Windows-specific, and do not apply to the cross-platform nature and ability of PowerShell 7. That is not to say that some modules won’t function if they utilize remoting to operate on a remote Windows system, but in this article, we are discussing locally loaded modules needing compatibility.

How Windows Compatibility Works in PowerShell 7

With all that discussion how does the Windows Compatibility layer work? The modules are not loaded into the current PowerShell 7 runtime. In the background, Windows PowerShell is running a version of the imported module. Any data sent to and from the module is then serialized and exposed to the PowerShell 7 session. The actual process follows the steps shown below:

  1. Upon importing the first module, a remote session named WinPSCompatSession is created to Windows PowerShell 5.1.
  2. The module is loaded via implicit remoting via a proxy module from the users $env:Temp folder.
  3. Cmdlet parameters are serialized and only the final result is returned.
  4. After the final module utilizing WinPSCompatSession is removed, destroy the session.

Implicit Remoting

In case you may not remember, there are two types of remoting within PowerShell. Implicit and Explicit remoting. Explicit remoting is when you create a session and then consume that session intentionally to run remote commands. Implicit remoting is the act of running a module that then creates a proxy function to send the commands to an automatically created remote session and return the results.

Proxy Modules

That brings us to our next point, what is a proxy module? A proxy module is a script function that takes command input on a local computer and sends that over to a secondary session, local or remote, to run a command. This allows the local PowerShell session to utilize commands that it may not have installed.

PowerShell 7 Compatibility Configuration

There are a few ways to configure how the Windows Capability layer works in PowerShell 7. In PowerShell 7 this is configured via the powershell.config.json file. This file can live in one of two different locations.

  • All Users Configuration – The $PSHOME location is defined as the same directory as the executing System.Management.Automation.dll assembly. For example, on a typical PowerShell 7 install, this is located here: C:\Program Files\PowerShell\7
  • Current User Configuration – The easiest way to determine this location is by running the command, Split-Path $PROFILE.CurrentUserCurrentHost. Typically this is located in the user profile here: C:\Users\username\PowerShell

Once you locate the configuration file to modify there are a few commands that we can use to customize how exactly Windows PowerShell modules are loaded.

WindowsPowerShellCompatibilityModuleDenyList

By modifying the WindowsPowerShellCompatibilityModuleDenyList setting, you can make sure that certain modules are excluded from being able to be run using the compatibility layer. Below are the default modules excluded from loading.

"WindowsPowerShellCompatibilityModuleDenyList":  [
   "PSScheduledJob","BestPractices","UpdateServices"
]

DisableImplicitWinCompat

If you want to disable the ability to implicity load Windows PowerShell modules that are not present in your PowerShell 7 loading, you can use the DisableImplicitWinCompat setting. If set to true, this command will force the explicit loading of modules.

Clobbering of Native PowerShell 7 Modules

With explicit loading, there is the possibility of overwriting native PowerShell 7 modules with those of Windows PowerShell. Though not fully released yet, in PowerShell 7.1 this behavior has been modified to disallow clobbering the following core modules:

  • Microsoft.PowerShell.ConsoleHost
  • Microsoft.PowerShell.Diagnostics
  • Microsoft.PowerShell.Host
  • Microsoft.PowerShell.Management
  • Microsoft.PowerShell.Security
  • Microsoft.PowerShell.Utility
  • Microsoft.WSMan.Management

To avoid clobbering additional modules, you can also add them to the PowerShell 7 config setting, WindowsPowerShellCompatibilityNoClobberModuleList. The example is seen below to exclude the AzureAD module from being clobbered. Keep in mind that this only works in PowerShell 7.

"WindowsPowerShellCompatibilityNoClobberModuleList": ['AzureAD']

Examples

There are two examples of loading that we are going to take a look at here. Implicit loading and explicit loading.

Explicit Loading

A common example of explicit loading is that of the AzureAD module. Due to certain issues with loading the module in PowerShell 7, it is sometimes necessary to load this module via Windows PowerShell in the background. To do this, simply use the -UseWindowsPowerShell switch when loading.

Import-Module -Name 'AzureAD' -UseWindowsPowerShell

Implicit Loading

To take advantage of implicit remoting, you can simply try loading the ServerManager module. As you can see in the image below, not only is the module automatically loaded using the compatibility layer, PowerShell 7 displays a warning regarding the serialization of results.

Import-Module -Name 'ServerManager'
Get-Module -Name 'ServerManager'
Untitled 98

Conclusion

With all the changes introduced by PowerShell 7, modules naturally broke, but with the introduction of a compatibility layer, many of the issues with co-existing versions of PowerShell are alleviated. The utility and power of the Windows PowerShell Compatibility Layer continue to evolve over time and soon will cover nearly every use case. This is until such time that all modules are converted to PowerShell 7 native modules.