carloscastilla - Fotolia
Windows Compatibility module expands PowerShell Core reach
The PowerShell team closed the cmdlet coverage gap with a compatibility module release for PowerShell Core, but its use requires some caution.
The Windows Compatibility module gives you access to Windows PowerShell functionality that is not natively available in PowerShell Core. However, using this feature will require some attention to detail.
When Microsoft debuted Windows PowerShell in 2006, the 1.0 version had 137 cmdlets in the base install. Each subsequent release up to version 5.1 in 2016 added to the number of cmdlets, which grew into the thousands when you counted the additions from Microsoft's networking and storage teams, Windows Server roles and features, the PowerShell Gallery, and other third parties.
The first PowerShell Core release, 6.0, saw a significant reduction in functionality with slightly more than 400 compatible cmdlets compared to approximately 1,500 in the base installation of Windows PowerShell 5.1. Windows administrators who tried to work around this limitation found they could not run a majority of the Windows PowerShell cmdlets due to underlying .NET Framework differences between Windows PowerShell and PowerShell Core.
Why some cmdlets won't work in PowerShell Core
A Windows PowerShell module will run under PowerShell Core if one of the following conditions is met:
- it's a script-based module that doesn't use commands that are not present in PowerShell Core;
- it's a cmdlet definition XML base module -- for instance, NetAdapter -- that utilizes the Common Information Model to perform its tasks; or
- the module has been recompiled to work with .NET Core.
PowerShell Core 6.1 added the PSEdition property, shown in Figure 1, to the ModuleInfoGrouping objects returned by the following command:
Get-Module -ListAvailable
Figure 1 shows the PSEdition property returning Core, Desk or both for PowerShell Core 6 modules and Windows PowerShell 5.1 modules. The Core value implies it works on PowerShell Core and the Desk value indicates it runs on Windows PowerShell.
PowerShell Core 6.1 and later automatically add the Windows PowerShell modules folder -- C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules -- to the module path.
A close look at Figure 1 shows the ThreadJob module in PowerShell Core, among others, has the Desk label. This is because Get-Module doesn't test whether the module will run on PowerShell Core; it relies on an entry in the module manifest (.psd1 file).
CompatiblePSEditions = @("Core")
CompatiblePSEditions = @('Desktop', 'Core')
If the CompatiblePSEditions entry isn't present, then Get-Module reports the module with an entry of Desk.
A further twist is that Get-Module will ignore modules from non-PowerShell Core folders that don't report a PSEdition that includes the value Core. Figure 1 shows the BitLocker module has a PSEdition of Core,Desk, but the BITStransfer module doesn't appear in the list. You need to use the SkipEditionCheck parameter to see modules that are not PowerShell Core-compatible.
If you try to import a module that isn't supported by PowerShell Core, then you'll get an error:
Import-Module bitstransfer
Import-Module : Module 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\bitstransfer\bitstransfer.psd1' does not support current PowerShell edition 'Core'. Its supported editions are 'Desktop'. Use 'Import-Module -SkipEditionCheck' to ignore the compatibility of this module.
If you use the SkipEditionCheck parameter, another error occurs:
Import-Module bitstransfer -SkipEditionCheck
Import-Module : Could not load type 'System.Management.Automation.PSSnapIn' from assembly 'System.Management.Automation, Version=6.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
If you want to use PowerShell Core with a module that has not been recompiled, then this gap creates a problem.
Enter the Windows Compatibility module
In late 2018, the PowerShell team released the Windows Compatibility module to get Windows PowerShell modules working in PowerShell Core. There is some irony that a technology originally developed on, and for, Windows now needs a compatibility pack, but it gets the job done.
You'll find the Windows Compatibility module in the PowerShell Gallery. The module's description is worth quoting: "This module provides compatibility utilities that allow PowerShell Core sessions to invoke commands that are only available in Windows PowerShell. These utilities help you to discover available modules, import those modules through proxies and then use the module commands much as if they were native to PowerShell Core."
The module creates a PowerShell remoting session from your PowerShell Core session to a Windows PowerShell remoting endpoint. You can then import modules into the remoting session. This creates proxy functions of the cmdlets you can call locally in your PowerShell Core session that will utilize the Windows PowerShell cmdlet through the remoting endpoint. This is effectively implicit remoting from PowerShell Core to Windows PowerShell with some commands to help you manage the session.
Installing the Windows Compatibility module
You install the Windows Compatibility module directly from the PowerShell Gallery.
Install-Module -Name WindowsCompatibility
You'll see a message about the gallery being untrusted -- it's a public gallery, so this is correct -- as shown in Figure 2.
Answer Y to the prompt and the installation will proceed.
When using the Scope parameter with Install-Module, the following rules apply:
- AllUsers installs the module to $env:ProgramFiles\PowerShell\Modules;
- CurrentUser installs the module to $home\Documents\PowerShell\Modules;
- Without a defined scope for an elevated PowerShell session, the scope will default to AllUsers. In non-elevated PowerShell sessions in PowerShellGet versions 2.0.0 and above, the scope is CurrentUser. For non-elevated PowerShell sessions in PowerShellGet versions 1.6.7 and earlier, the scope is undefined, and Install-Module will fail.
PowerShell 6.2.0 uses PowerShellGet 2.1.2, and PowerShell 6.1.3 uses version 1.6.7. You will see different behavior depending on the PowerShell version, so always use the Scope parameter to dictate the module location.
Using the Windows Compatibility module
The Windows Compatibility module contains the following functions: Add-WindowsPSModulePath (also aliased as Add-WinPSModulePath), Add-WinFunction, Compare-WinModule, Copy-WinModule, Get-WinModule, Import-WinModule, Initialize-WinSession and Invoke-WinCommand.
The Add-WindowsPSModulePath cmdlet appends the Windows PowerShell PSModulePath to your existing PowerShell Core PSModulePath.
PowerShell 6.1 and later already add the standard PowerShell modules to the PSModulePath, so you'll only need to use this function if you need other parts of the Windows PowerShell module path. You're better off appending paths to your PowerShell Core module path as you need them rather than doing a blanket add of all the Windows PowerShell paths.
The commands in the Windows Compatibility module rely on enabling PowerShell remoting, which uses the Windows Remote Management (WinRM) service. This is the case by default on Windows Server 2012 and later, but not on client versions of Windows or earlier Windows Server versions. If remoting isn't enabled, you'll see a message that starts:
Get-WinModule
New-PSSession : [localhost] Connecting to remote server localhost failed with the following error message : The client cannot connect to the destination specified in the request.
To set up remoting, run Enable-PSRemoting from an elevated Windows PowerShell session. You might have a problem, especially on client workstations, if any of your network connections have a NetworkCategory of public. Check with Get-NetConnectionProfile and set it to private with Set-NetConnectionProfile.
Get-WinModule returns a list of the available modules from the compatibility session. The cmdlet returns modules from C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules and C:\Program Files\WindowsPowerShell\Modules, though modules already in PowerShell Core, such as PackageManagement and PowerShellGet, aren't shown from the latter.
Speed takes a dip at the expense of expanded functionality
You may find that Get-WinModule or any of the other functions in the Windows Compatibility module will run slower due to the remoting session. Try using Get-Module -ListAvailability across a Windows PowerShell remoting session to a remote computer to compare.
Another cause for the sluggishness is that Get-WinModule calls Initialize-WinSession -- as do other functions in the module -- to create the remoting session. This session is persistent until you remove it or close PowerShell, so its creation adds to the overhead. The remoting session is visible to the PSSession cmdlets and will be called something like wincompat-localhost-<user>, and it will connect to the standard Microsoft.PowerShell endpoint. You can create sessions to remote computers if required using the Windows Compatibility module functions.
Compare-WinModule is another way to investigate the modules available through the compatibility session. The cmdlet help message states it will "Compare the set of modules for this version of PowerShell against those available in the compatibility session."
Figure 3 shows how Compare-WinModule displays the modules in the compatibility session that aren't present in the PowerShell Core session, even if it's just a version difference. Be careful with this cmdlet's results because the information can be quite subtle.
Copy-WinModule adds functionality from Windows PowerShell into PowerShell Core by copying modules from the compatibility session that are directly usable in PowerShell Core. By default, these modules are copied to the $Home/Documents/PowerShell/Modules folder, but you can override this with the Destination parameter. If the module already exists in the destination, then it will not be copied.
Be wary of using Copy-WinModule. I'd rather leave the module and access it through the compatibility session. If the module works with PowerShell Core, you can access it directly in the PowerShell Core session without the need to copy.
Working with the Active Directory module
To import the Active Directory Windows PowerShell module into your PowerShell Core session, use the following command:
Import-WinModule activedirectory
When you investigate the module in your PowerShell Core session, you'll find a set of functions, one per cmdlet, in the module:
Get-Command -Module ActiveDirectory
To dig into the functions, enter this command:
Get-ChildItem -path Function:\Get-ADUser | Format-List *
The output shows the function is a proxy function that performs implicit remoting back to the Windows PowerShell session. The proxy function is available only in the PowerShell Core session.
You use the proxy function the same way as the cmdlet shown in Figure 4.
The only thing that reveals it's not the original cmdlet is the RunSpaceId property on the returned object.
While this is a clever way to access Windows PowerShell functionality with PowerShell Core, you need to be aware of a few things. First, working through an implicit remoting session returns inert -- or deserialized -- objects, which might not be an issue most times but it needs to be noted.
Next, you can't run GUI tools through the remoting session, standard to all remoting sessions. Also, because this uses WinRM, it only works from Windows to Windows due to the current WinRM limitations on Mac and Linux. You must also use PowerShell Core 6.1 or later.
The Active Directory module in Windows 10 1809, Windows Server 1809 and Windows Server 2019 or later will run directly in PowerShell Core, but if you try to access the ProtectedFromAccidentalDeletion property, you'll get an error. If you use the module through the Windows Compatibility module, there will be no error because access goes through Windows PowerShell.
The last command in the module, Invoke-WinCommand, runs a scriptblock over the compatibility remoting session. It is effectively the Invoke-Command but constrained to use the compatibility session.
After connecting to a Windows PowerShell session, run the following command to return the version information:
Invoke-WinCommand -ScriptBlock {$PSVersionTable}
When you close the PowerShell Core session, you lose the proxy functions and the remoting session. It may be available as a disconnected session.