PowerShell / PSResourceGet

PSResourceGet is the package manager for PowerShell
https://www.powershellgallery.com/packages/Microsoft.PowerShell.PSResourceGet
MIT License
483 stars 92 forks source link

Install-PSResource installs Script module to lowercase directory #1446

Open rickandersen-forte opened 10 months ago

rickandersen-forte commented 10 months ago

Prerequisites

Steps to reproduce

Install a PSM1/PSD1 module with a mixed-case name (e.g., MyModule) using Install-PSResource.

Expected behavior

The module should install to C:\...\Modules\MyModule\ and be find-able as a PowerShell module.

Actual behavior

When I install a module with a mixed-case name (e.g., MyModule) using Install-Module, it installs perfectly. When I use Install-PSResource, it installs successfully, but installs it in C:\...\Modules\mymodule\ (i.e. lowercase) instead of C:\...\Modules\MyModule\. This doesn't cause any problem for PS under Windows, but on case-sensitive *nix platforms, the module can't be found and, therefore, can't be loaded (without manually renaming the install directory).

Error details

No response

Environment data

Name              : Microsoft.PowerShell.PSResourceGet
Path              : C:\Users\rick\Documents\PowerShell\Modules\Microsoft.PowerShell.PSResourceGet\1.0.0\Microsoft.PowerShell.PSResourceGet.dll
Description       : PowerShell module with commands for discovering, installing, updating and publishing the PowerShell artifacts like Modules, Scripts, and DSC Resources.
ModuleType        : Binary
Version           : 1.0.0
PreRelease        :
NestedModules     : {Microsoft.PowerShell.PSResourceGet}
ExportedFunctions : Import-PSGetRepository
ExportedCmdlets   : {Find-PSResource, Get-InstalledPSResource, Get-PSResourceRepository, Get-PSScriptFileInfo…}
ExportedVariables :
ExportedAliases   : {fdres, Get-PSResource, isres, pbres…}

Name                           Value
----                           -----
PSVersion                      7.3.8
PSEdition                      Core
GitCommitId                    7.3.8
OS                             Microsoft Windows 10.0.22621
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

LaurentDardenne commented 10 months ago

This is an issue referenced, but not documented as a known issue. Everyone discovers it. There is an implicit rule which is not controlled and remains the responsibility of the user. Just like the file names specified in a module manifest.

The case of the package name can influence the installation, see the platyPS module for example.

See too : https://github.com/PowerShell/PowerShellGetv2/issues/472#issuecomment-495676400

rickandersen-forte commented 10 months ago

Thanks, @LaurentDardenne, although I'm not sure this is part of that overall issue; I'm not convinced it's that straightforward. When I install my module to PSGallery, with the mixed-case names, everything works fine. The module is installed into a mixed-case directory and all is well. But when I publish to the GitHub Packages, which (apparently) requires that I use the newly-released PowerShellGet v3 (PSResourceGet), it installs into a lowercase directory and the problem arises. PSResourceGet (more specifically, Install-PSResource) also works on PSGallery, so I'm starting to suspect the problem is with GitHub's environment.

I've been assuming GitHub is using standard hosting software, but I don't know if that's the case. Maybe I need to flag this as a bug for GitHub? Any ideas?

LaurentDardenne commented 10 months ago

But when I publish to the GitHub Packages, which (apparently) requires that I use the newly-released PowerShellGet v3 (PSResourceGet), it installs into a lowercase directory and the problem arises.

This may be another problem...

Here is a case where this occurs : https://cloudsmith.io/~psmodulecache/repos/test/packages/detail/nuget/wrongnamingrule/1.0.0/#files

rickandersen-forte commented 10 months ago

In my case, the module and package have the same name (i.e., "MyModule"), but the module is installed in the lowercase directory ("mymodule") when installed from GitHub Packages. If the directory and PSD1 file were named differently, I'd understand the issue, but they are the same.

I wish I could have you install it to see if you see the same behavior and/or see if I made a mistake, but I'm not allowed to expose it yet. I am trying to find a way to download the nupkg file directly so I can see if it was generated correctly by Publish-PSResource (via the following command).

Publish-PSResource -Path "${{ github.workspace }}\AnyChain\AnyChain.psd1" -Repository $repoName -ApiKey ${{ github.token }} -DestinationPath "${{ github.workspace }}" -Verbose
SydneyhSmith commented 10 months ago

Thanks for opening this issue @rickandersen-forte PowerShell is, by default, case-insensitive even on non-Windows platform, to confirm are you running into an issue with your use case because of this or are you worried that you might in the future?

LaurentDardenne commented 10 months ago

May be a problem similar to this

rickandersen-forte commented 10 months ago

Thanks, @SydneyhSmith. I am already running into this case, but it's not a significant issue (yet?) because we are in development and I don't think it's being used by anyone in an affected environment (e.g., Debian or Ubuntu). I'm hoping to get it fixed before it does, but since this is an internal tool, worst case scenario is that I tell users to either rename the directory or create a properly named symbolic link to the lowercase-named directory.

As @LaurentDardenne pointed out, it appears to be the same as issue 1402 (so far as I can tell).

rickandersen-forte commented 10 months ago

In case it helps (e.g., I hope I'm doing something wrong in publication), below is the GitHub Action that I use to publish. Notice that the Publish using nuget.exe section at the end is disabled; that was a test to see if using nuget worked better, but it had the same problem.

Also relevant, the module directory and PSD1 file are both named AnyChain.

# This is a basic workflow to help you get started with Actions

name: Publish AnyChain PowerShell module

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the "main" branch
  #push:
  #  branches: [ "main" ]
  #pull_request:
  #  branches: [ "main" ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow packages and publishes the AnyChain module to the GitHub package repository
  publish-anychain-package:

    # The type of runner that the job will run on
    runs-on: windows-latest

    # Enable required repo permissions for this workflow
    permissions: 
      contents: read
      packages: write

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out this repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v3
      - uses: nuget/setup-nuget@v1
        with: 
          nuget-version: 'latest'
          nuget-api-key: ${{ github.token }}

      # Import new PSResourceGet v3
      - name: Import new PSResourceGet modules
        if: ${{ true }}
        run: |
          # Mark PSGallery as trusted to allow subsequent installations w/o prompts
          Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -Verbose

          # Install secret management modules
          Install-Module Microsoft.PowerShell.SecretManagement,Microsoft.PowerShell.SecretStore 

          # Install PowerShellGet 3.0 (Microsoft.PowerShell.PSResourceGet)
          Install-Module -Name Microsoft.PowerShell.PSResourceGet -AllowClobber -Force

          # OPTIONAL?: Install PowerShellGet 3.x module (provides backward compatibility for PowerShellGet 2.0 functionality)
          Install-Module -Name PowerShellGet -AllowPrerelease -Force

          # OPTIONAL?: Register PowerShellGet (v2) repos in the PSResourceGet (v3) resources repos
          Import-PSGetRepository

      # Runs a set of commands using the runners shell
      - name: Publish the PowerShell module to GitHub package repository
        if: ${{ true }}
        run: |

          # Temporary repo name
          $repoName = "MyPackages"

          # Construct a PSCredential from the GitHub token
          $pass = '${{ github.token }}' | ConvertTo-SecureString -AsPlainText -Force 
          $cred = [PSCredential]::new( '${{ github.actor }}', $pass )

          # Register the NuGet repository to which the module will be published 
          Register-PSResourceRepository -Name $repoName -Priority 10 -Trusted -Uri 'https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json' -CredentialInfo $credInfo -Verbose

          # Publish the AnyChain module to the NuGet repository
          # We're having a problem in which the nupkg file is lowercase, which causes problems on case-sensitive Unix platforms.
          Publish-PSResource -Path "${{ github.workspace }}\AnyChain\AnyChain.psd1" -Repository $repoName -ApiKey ${{ github.token }} -DestinationPath "${{ github.workspace }}" -Verbose

      # Publish using nuget.exe
      - name: Publish module using nuget.exe
        if: ${{ false }}
        run: |
          $ver = (Import-PowerShellDataFile -Path "${{ github.workspace }}\AnyChain\AnyChain.psd1").ModuleVersion
          nuget pack "${{ github.workspace }}\AnyChain.nuspec" -BasePath "${{ github.workspace }}\AnyChain\" -Version $ver
          nuget sources add -Name "GitHubRepo" -Source 'https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json' -username "rickandersen-forte" -password "${{ github.token }}"
          nuget push "${{ github.workspace }}\AnyChain.$ver.nupkg" -Source GitHubRepo
rickandersen-forte commented 10 months ago

I was able to download the raw nupkg file from the GitHub Packages and it appears to be virtually identical to that from PSGallery (e.g., different Id and psmdcp Target file in .rels, "NuGet" vs "NuGet.Packaging" in .psmdcp files). Both also specify the nupkg name as lowercase ("anychain.2.5.8.4.nupkg") and mixed-case within the encapsulated files (e.g., AnyChain.nuspec's element).

Yet, when I install from PSGallery using Install-PSResource, it installs to ".../Modules/AnyChain", and when I install from GitHub using Install-PSResource, it installs to ".../Modules/anychain". GitHub uses ApiVersion v3 while PSGallery uses v2. I can't but suspect the problem is in the v3 repository "provider".

By chance, is there a v3 interface to the PSGallery yet that I can test against?

rickandersen-forte commented 10 months ago

I just noticed that Find-PSResource also returns a lowercase name for the GitHub Packages repo, but a mixed-case for the PSGallery repo. It seems like that might be relevant.

rickandersen-forte commented 10 months ago

Through experiments, it appears that the case of the nupkg filename and the name of the enclosed nuspec file (though not the <id> within the nuspec file) must match exactly for the resource to be available via Find-PSResource or for Install-PSResource to work correctly.

When installed from the GitHub repo, each part of the URL is converted to lowercase, as per the standard (https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource). This results in the downloaded nupkg filename and local directory name being lowercase, which is then moved into the final destination (keeping the same lowercase name), even though the package itself uses mixed case.

Since the URL must use lowercase (e.g., https://nuget.pkg.github.com/<organization>/anychain/2.5.8.4/anychain.2.5.8.4.nupkg), but the package itself may be mixed-case, as specified in the nuspec file (e.g., <id>AnyChain</id>), it seems that two things must change in the PSResourceGet implementation to allow a workable installation under case-sensitive file systems:

  1. Publish-PSResource must generate a lowercase nuspec filename (e.g., anychain.nuspec) that contains a mixed-case id tag (e.g., <id>AnyChain</id>). Since this file is embedded in the nupkg file, the server can't be expected to convert it to lowercase for us. Right now, the file is created with the same case as the internal package name, which only works if the package name is always lowercase (a very bad restriction).
  2. Install-PSResource must download the package to a directory whose name matches the mixed-case id tag from the <id> tag within the nupkg file, not the lowercase package name; or at least convert it to that name before the installation completes. This allows the installed directory to match the case of the actual module so PowerShell can find the module in a mixed-case-sensitive environment.

Or, I'm completely lost and don't know what I'm talking about. Definitely possible. If so, please let me know.

LaurentDardenne commented 10 months ago

But, I'm completely lost

I don't think so, it's consistent with the behavior we see.

Since this file is embedded in the nupkg file, the server can't be expected to convert it to lowercase for us.

I don't know if it's related to this problem, but when downloading from the Cloudsmith site the file name 'anychain.2.5.8.4.nupkg' is renamed to 'AnyChain.2.5.8.4.nupkg' ( id=AnyChain ) :

image

Which is not the case for the package I mentioned previously:

image

The difference is I created the .nuspec file and the package manually.

For this package the directory name is in lower case: image

Which is not the case for your package.

rickandersen-forte commented 10 months ago

If my understanding is correct, the behavior you're seeing makes sense. I believe the name of the downloaded nupkg file should be the same as the <id> from the nuspec file, not the value as specified in the URL. I'm not sure if that's necessary, but it is if the client-side installer uses the file name instead of the <id> value for the target directory. I suspect the safest would be to do both in case there are clients that don't follow that rule. (Q: Is Install-PSResource the only relevant client in this context?)

@SydneyhSmith: Any thoughts or suggestions? I'm not asking for a commitment, but just wondering if this sounds like we're on the right track? And thinking maybe I should rename my modules to be all lowercase if it looks like this fix (or any fix) isn't possible in the foreseeable future.

corkupine commented 2 months ago

Is there any update on when we should expect this issue to be addressed? We got bit by this too. Also - there is another open issue which I believe is the same and perhaps should be merged. Thanks!