5

I have a PowerShell module that targets both Windows PowerShell 5.1 and PowerShell Core 6.0. On the Windows PowerShell (Desktop edition) side, it has an additional dependency on .NET Framework 4.7.1 in order to work properly.

When I originally authored the module, I thought it was sufficient to use the DotNetFrameworkVersion entry in the module manifest thinking that PowerShell would enforce that minimum.

DotNetFrameworkVersion key

Specifies the minimum version of the .NET Framework that is required by the module.

I have since learned there is no enforcement and I'm not even sure what the field is used by. In any case, I'm trying to reduce the support issues that get filed when people try using the module without the minimum .NET Framework version installed.

In a recent version, I added some code in the module's PSM1 file that would check the .NET Framework version when the module is loaded and throw an error if the minimum version wasn't found.

This seems to work great, but only if you manually import the module with Import-Module. If you just try to run one of the module functions and let PowerShell auto-loading do the import, the import error is suppressed and you instead get a relatively generic error such as:

Get-Blah : The 'Get-Blah' command was found in the module 'MyModule', but the module could not be loaded. For more information, run 'Import-Module MyModule'.

I want to believe users would read the message, do what it says by importing the module manually, and then read/understand the real error message. But I've already had a user submit an issue to the contrary.

So now I'm wondering. Is there a better way to go about this? Do I move my version check code out of the PSM1 and into each public function? Is there something simpler I'm missing?

4
  • From the documentation: This prerequisite is valid for the PowerShell Desktop edition only. Commented Jul 19, 2018 at 16:42
  • Yes, Desktop edition is where the problem is. Commented Jul 19, 2018 at 16:59
  • You say yourself that this is a user's reading comprehension problem. You are never going to find an adequate technical solution to your users' reading comprehension problems. The design of PowerShell modules is such that full error checking is only performed when you run Import-Module. There's no way to change that without Microsoft doing it. I suggest you make it clear in your module documentation that the .Net Framework has a minimum required version, and then in your documentation examples always include the Import-Module command. Commented Jul 19, 2018 at 19:38
  • 1
    The readme in the docs definitely specify the required version already. I've also got a separate FAQ in the wiki that I need to add this to. Adding an explicit Import-Module to the examples is a good idea too. Commented Jul 19, 2018 at 19:45

2 Answers 2

1

I too just ran into this, and found it frustrating. I read the documentation linked above to where Microsoft documents how to get the .NetFramework client installed version using C# and PowerShell code. I wrote this script, which you can execute on another system:

$Url = 'https://gist.githubusercontent.com/ChrisLynch
HPE/e2f276eadd5ec96c2c2e5b5835a444eb/raw/05a2d1c98839d3c253a66fb6469c840fec9ea496/Get-InstalledDotNetFramework.ps1'
iex (([System.Net.WebClient]::new()).DownloadString($Url))

This is the solution I came up with that is in the header of my PSM1:

if ($PSVersionTable.PSVersion -match '5.1')
{

    $ReleaseKey = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\' |  Get-ItemPropertyValue -Name Release

    if ($ReleaseKey -lt 461808)
    {
        [System.String]$Exception                                  = 'InvalidOperationException'
        [System.String]$ErrorId                                    = 'UnableToLoadModuleMissingDependancy'
        [System.Object]$TargetObject                               = 'Import-Module ModuleName'
        [System.Management.Automation.ErrorCategory]$ErrorCategory = 'ResourceUnavailable'
        [System.String]$Message                                    = 'The library is unable to load due to this sytem missing the required .NetFramework 4.7.2 client.  Please visit https://go.microsoft.com/fwlink/?LinkId=863265 to download the .NetFramework 4.7.2 Offline Installer.'

        throw [Management.Automation.ErrorRecord]::new((New-Object $Exception $Message), $ErrorID, $ErrorCategory, $TargetObject)

    }   

}

So when a client attempts to import the module with say an older .NetFramework installed, the following exception is thrown and the module fails to load:

Import-Module: The library is unable to load due to this sytem missing the required .NetFramework 4.7.2 client.  Please visit https://go.microsoft.com/fwlink/?LinkId=863265 to download the
.NetFramework 4.7.2 Offline Installer.
At line:1 char:1
+ ipmo hponeview.500
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (Import-Module ModuleName:String) [Import-Module], InvalidOperationException
    + FullyQualifiedErrorId : UnableToLoadModuleMissingDependancy,Microsoft.PowerShell.Commands.ImportModuleCommand
Sign up to request clarification or add additional context in comments.

1 Comment

Yes. As I mentioned in my question, the biggest problem with this approach is module auto-importing (i.e. Start PowerShell and immediately run a function from the module). The custom error message that would inform the user of the problem is overridden with a generic "module could not be loaded" error.
0

It's not quite what you're looking for, but the closest thing you can do as you found is to put a version check in the psm1 that gets evaluated when Import-Module is called. An easy way to check the current version of .NET that is available to Powershell is to check the $PSVersionTable variable:

PS C:\> $PSVersionTable.CLRVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      30319  42000

You can either check the Major, Minor, Build, and Revision fields separately, or check the full string-based version with .ToString()

PS C:\> $PSVersionTable.CLRVersion.ToString()
4.0.30319.42000

EDIT

For versions of .NET 4.5 and higher it is best to check the .NET Release DWORD in the registry rather than rely on the CLR version. Technically you can still map CLR versions to .NET versions using the Revision field, but there isn't much documentation on the subject.

$netFrameworkRelease = ( Get-ItemProperty -Path HKLM:\Software\Microsoft\NET Framework Setup\NDP\v4\Full ).Release

You can then map the release number to the .NET Framework version as outlined here.

3 Comments

Unfortunately, the CLRVersion is not the same as the .NET version. I did end up doing a check on module load, but I'm doing a version lookup in the registry based on this article. learn.microsoft.com/en-us/dotnet/framework/migration-guide/…
Oops, my bad. I'll modify my answer
Actually, we are both right. You used to be able to use the CLRVersion. But I can only find exact CLR versions matching a .NET version up to and including .NET 4.0. It appears the currently supported way to check the .NET version in use by Powershell is to check the Release DWORD.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.