I recently wrote this to a colleague: “Finally, if you’re going to peer into the module manifest file (.psd1), you might also check ‘CompatiblePSEditions.’ If it includes both the ‘Core’ and ‘Desktop’ values, then you might consider installing modules in both $env:USERPROFILE\Documents\WindowsPowerShell\Modules and $env:USERPROFILE\Documents\PowerShell\Modules.”
He’s using SCCM, Software Center, and a Windows PowerShell form to install modules. My thought was to install it for both PowerShell and Windows PowerShell, regardless of which they were using and in reference to the manifest file. This would ensure a module was already in place provided the user switched between PowerShell and Windows PowerShell or moved from one to another one day. Not a bad idea, I suppose. If the duplicated module is never used, it’s okay as the disk space used would likely never really be noticed. Maybe one day, PowerShell will include this option itself. You install a module in PowerShell and include some yet-to-be-determined switch parameter, and boom, it’s ready in both locations.
This got me thinking. How would one write the code to do the conditional logic here? While it wasn’t my project, I couldn’t help myself from determining how I might write it. And, when it was done — and this has happened before — I wasn’t sure what to do with it. Enter, this blog post; its new home, or final resting place.
I wrote this to check four different modules on my computer. They were: (1) AWS.Tools.Common, (2) AWSLambdaPSCore, (3) ISE, and (4) ExchangeOnlineManagement. Each of these four contained a different value in the psd1’s CompatiblePSEditions. In the same order as above, there were (1) Core and Desktop, (2) Only Core, (3) Only Desktop, and (4) Neither Core nor Desktop.
Here’s what I wrote; we’ll discuss it further below. It was saved into a file named ModuleDeploy.ps1.
# Both Core AND Desktop. $Path01 = "$env:USERPROFILE\Documents\PowerShell\Modules\AWS.Tools.Common\4.0.5.0\AWS.Tools.Common.psd1" # ONLY Core. $Path02 = "$env:USERPROFILE\Documents\PowerShell\Modules\AWSLambdaPSCore\2.0.0.0\AWSLambdaPSCore.psd1" # ONLY Desktop. $Path03 = "$env:SystemRoot\system32\WindowsPowerShell\v1.0\Modules\ISE\ISE.psd1" # Neither Core NOR Desktop. $Path04 = "$env:USERPROFILE\Documents\PowerShell\Modules\ExchangeOnlineManagement\1.0.1\ExchangeOnlineManagement.psd1" $Paths = $Path01,$Path02,$Path03,$Path04 Foreach ($Path in $Paths) { "`r`n||| Module: $(Split-Path -Path $Path -Leaf) |||" $Psd1File = Import-PowerShellDataFile -Path $Path Switch ($Psd1File.CompatiblePSEditions) { {$_ -contains 'Desktop'} { Write-Output -InputObject 'Copy to WindowsPowerShell directory.' } # End Condition. {$_ -contains 'Core'} { Write-Output -InputObject 'Copy to PowerShell directory.' } # End Condition. default { If ($PSVersionTable.PSEdition -eq 'Desktop' -or $null -eq $PSVersionTable.PSEdition) {'Copy to WindowsPowerShell directory (default).' } ElseIf ($PSVersionTable.PSEdition -eq 'Core') {'Copy to PowerShell directory (default).'} # End If-ElseIf. } # End Condition. } # End Switch. } # End Foreach.
Let’s quickly cover what’s happening in the above code. Then we’ll view the below results for the execution in both PowerShell and Windows PowerShell. Showing both versions may clarify the reason for the If statement inside the above final/default Switch condition. We’re already jumping ahead.
First, we created four different path variables for four specific module manifest files (.psd1 files). The first one is to a module that includes both “Core” and “Desktop” in CompatiblePSEdition. The second only has “Core,” the third only has “Desktop,” and the final one has neither/nothing. This was mentioned earlier. These four variables and their assigned values are combined into an array, which we then iterate over inside of a Foreach loop.
During each iteration, we run through a Switch statement that indicates where each module would be copied (if we were actually copying the modules). Based on the value(s) in CompatiblePSEdition for each of the files, the first module would be copied to both the PowerShell and WindowsPowerShell modules, the second, only to PowerShell modules, and the third, only to WindowsPowerShell modules. The final copy is dependent on whether or not we’re using PowerShell or Windows PowerShell. Take a look at the results.
PowerShell 7.0.1
[PS7.0.1] C:\> . "$env:TEMP\ModuleDeploy.ps1" ||| Module: AWS.Tools.Common.psd1 ||| Copy to PowerShell directory. Copy to WindowsPowerShell directory. ||| Module: AWSLambdaPSCore.psd1 ||| Copy to PowerShell directory. ||| Module: ISE.psd1 ||| Copy to WindowsPowerShell directory. ||| Module: ExchangeOnlineManagement.psd1 ||| Copy to PowerShell directory (default).
WindowsPowerShell 5.1
[PS5.1.18362.752] C:\> . "$env:TEMP\ModuleDeploy.ps1" ||| Module: AWS.Tools.Common.psd1 ||| Copy to PowerShell directory. Copy to WindowsPowerShell directory. ||| Module: AWSLambdaPSCore.psd1 ||| Copy to PowerShell directory. ||| Module: ISE.psd1 ||| Copy to WindowsPowerShell directory. ||| Module: ExchangeOnlineManagement.psd1 ||| Copy to WindowsPowerShell directory (default).
That’s it. Just some code that needed a home. I’m fully aware that there’s better ways this could be done, and that there would be some copy duplication if this were dropped into production the way it is. Additionally, if a directory didn’t yet exist, it might have to be created. Still, it needed a home (as is), and I didn’t think about much more than the conditional logic involved to determine where a module would need to be deployed.
Put your modules to Windows Powershell AllUsers scope location and Powershell 7 will be able to see and load them. No need to have 2 copies of module.
This also means that you need to manage module installation from within Windows Powershell.
I appreciate that you included this information. I’m not sure if I knew this or not, but things have slipped by me in the past, such as Get-ChildItem having a -Name switch parameter! What!? I somehow missed that until a day or two ago.
PowerShell absolutely does load modules from two of the WindowsPowerShell directories. This includes the one in /Program Files and the one in /Windows/System32. It didn’t pull back any from my local profile, however, so I have to assume this is why you wrote the “AllUsers” scope. Thank you for the comment!