RamblingCookieMonster / PSDepend

PowerShell Dependency Handler
MIT License
282 stars 75 forks source link

fix(Import-PSDependModule): Sanitize version string used for Import-Module #140

Open fourpastmidnight opened 1 year ago

fourpastmidnight commented 1 year ago

This commit fixes one particular issue out of two identified issues.

The first identified issue is that when initially "getting" a dependency, it does not seem to be imported. (I may be mistaken on this point, but read on.) The second issue, and the one this commit addresses, is that upon a subsequent run of PSDepend, the now "installed" module is attempted to be imported, and this fails in the case of modules whose PSDepend specification's version string includes a pre-release version tag.

For example, the following spec:

@{
    'PSResourceGet' = @{
        Name = 'Micrsooft.PowerShell.PSResourceGet'
        Version = 'latest'
        Parameters = @{
            AllowPreRelease = $true
        }
    }
}

The first time you run PSDepend, everything "works" fine. The reason I say that an "install" is performed but not an import is because there are no errors when running this the first time. But using this same configuration, running PSDepend a subsequent time results in the following error:

Checking for and installing the latest versions of the build system dependencies...
WARNING: Access to the path 'D:\src\git\MyProject\build\deps\Microsoft.PowerShell.PSResourceGet\0.5.24' is denied.
Save-Package: Unable to save the module 'Microsoft.PowerShell.PSResourceGet'.
Import-Module: C:\Users\me\Documents\PowerShell\Modules\PSDepend\0.3.8\Private\Import-PSDependModule.ps1:21
Line |
  21 |              Import-Module @importParams
     |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | The specified module 'D:\src\git\MyProject\build\deps\Microsoft.PowerShell.PSResourceGet' was not loaded because no valid module
     | file was found in any module directory.

The issue here is that now that the module is "installed", the version string, as specified in the PSDepend spec, is passed directly to Import-Module via Import-PSDependModule. It is necessary to sanitize the version string to remove any pre-release tags since Import-Module doesn't know anything about pre-release versions. The RquiredVersion parameter is a System.Version from the .NET BCL and is a Microsoft-proprietary version number format, not a SemVer string.

This commit fixes this subsequent "import issue".

Fixes #132

fourpastmidnight commented 1 year ago

One could argue that this change should be hoisted up to PSDepend\PSDependScripts\PSGalleryModule.ps1. However, one counter-argument to that argument is that potentially any PSDependScript passing data to Import-PSDependModule could be susceptible to not sanitizing the version string prior to invoking Import-PSdependModule, resulting in the same or a similar error; and so I thought it made sense to put this change there.

johlju commented 1 year ago

If you would have both version 2.0.0 and 2.0.0-preview1 and you want to import 2.0.0-preview1 the suggested change would wrongly import 2.0.0. Ignore that - actually thought that Import-Module in PS7 actually supported that, but no. 🙂

The first error that is reported is Save-Package: Unable to save the module 'Microsoft.PowerShell.PSResourceGet'.. This is probably due to that the PSResourceGet is imported into the session, and it will load assemblies which will make it impossible to remove the folder until the session is closed that has those assemblies loaded.

fourpastmidnight commented 1 year ago

@johlju, Yes, you are correct, but that's because PowerShell has no idea about pre-release versions. The -RequiredVersion parameter of Import-Module is a System.Version, which is a Microsoft-proprietary version number format that does not follow semver. There is no way to specify a pre-release version when importing a module (for Desktop PowerShell—I need to check on PowerShell Core).

If you install a prerelease version, let's say MyModule, version 2.0.0-preview1, here's the directory structure you will find of the installed module:

C:\Users\<me>\Documents\WindowsPowerShell\Modules
 └─ MyModule
     └─ 2.0.0
         └─ <Module files and folders>

Notice the folder for the version--there is no pre-release tag there.

johlju commented 1 year ago

Yes, correct. The preview release string is in the module manifest.

fourpastmidnight commented 1 year ago

Exactly. So, because PSDepend was passing the full SemVer version string as the -RequiredVersion parameter to Import-Module, this was breaking the import of pre-release modules.

Looking at PowerShell Core, I don't see that that has changed. One thing I want to research once more, is it possible to specify a pre-release version string in a full "module declaration" when importing? But IIRC, I don't believe there is.

fourpastmidnight commented 1 year ago

Per your other comment regarding Save-Package, I got this, too. And PSDepend cannot help with this. You are correct. Some modules have binary dependencies. For example, if you write a PowerShell script and use the Add-Type -AssemblyName System.Messaging, then those types are forever in your session. There's no way to "unload" the assembly. This has implications for modules that are imported by PSDepend that have such external DLL dependencies. A subsequent dependency installation will fail if those DLLs have been loaded into your session already. The only thing you can do is close your session and start a new one.