Loading Assemblies from NuGet Packages

2019-02-13 16:17发布

问题:

Sometimes in my PowerShell scripts, I need access to a specific DLL, using Add-Type -AssemblyName. However, the DLLs I need aren't always on the machine or in the GAC. For instance, I might want a quick script that uses Dapper to query a database. In these cases, I have been literally copying the DLLs along with the ps1 file. I was wondering if this was common/a good idea and whether there was an existing extension that would load up NuGet packages, store then in a global or local folder and call Add-Type -AssemblyName automatically.

It'd be a lot like using npm or pip in Node.js or Python, respectively.

Update

I did some research and there's nothing built-in to older versions of PowerShell. I made some progress trying to write one from scratch using the nuget.exe

&"$(Get-Location)/nuget.exe" install $packageName -Version $version -OutputDirectory "$(Get-Location)/packages" -NoCache -NoInteractive

This will download a given package/version under a "packages" folder in the current folder, along with any of its dependencies. However, it looks like it downloads every framework version, with no obvious way to tell which one to use for your given environment.

Otherwise, you could just loop through the results and call Add-Type:

Get-ChildItem .\packages\ -Recurse -Filter "*.dll" | % {
    try
    {
        Add-Type -Path $_.FullName
    }
    catch [System.Exception]
    {
    }
}

I tried using the restore command using a project.json file to see if I could control the framework version with no luck. This is just too hacky for me.

I'll check out @crownedjitter's suggestion of using PowerShell 5.

Update

Using @crownedjitter's suggestion, I was able to eventually register the PackageManagement module with NuGet (see comments below). With the following command, I was able to reproduce what the Nuget.exe command above was doing:

Install-Package Dapper -Destination packages

Obviously, this is a lot shorter. Problem is it has the same limitation; it brings down every framework version of a package. If this includes .NET core, it brings down a good deal of the .NET core framework with it! There doesn't appear to be a way to specify a target framework (a.k.a, .NET 4.5.1 or below).

I am wondering if there is a way to determine which NuGet package folder(s) to load the DLLs from based on PowerShell's current $PSVersionTable.CLRVersion field.

回答1:

See this suggestion on GitHub for making the use of NuGet packages in PowerShell easier, by extending Add-Type.

crownedjitter's helpful answer is a good starting point, and Travis himself has provided additional pointers in comments, but let me try to summarize as of Windows PowerShell v5.1:

  • As stated, PowerShell v5+ - including PowerShell Core - comes with the PackageManagement module that is a meta package manager providing access to multiple repositories via providers; on-demand installation of this module is may be possible in v3 and v4 (this download is labeled "March 2016 Preview", and it is the most recent I could find).

    • Find-PackageProvider lists all available providers.
    • Get-PackageProvider lists installed ones.
  • It is the nuget provider that enables installation of Nuget packages via Install-Package, and there are two potential hurdles:

    • The nuget provider may not be installed.

    • It may be installed with an incorrect API URL that prevents Find-Package from returning results.

Test if the nuget provider is installed:

# If this fails, the provider isn't installed
Get-PackageProvider nuget

If it is installed: Verify that the package source URI is correct:

  • Open an elevated PowerShell session.
  • Run Get-PackageSource:
    • If you find a Nugettest source, remove it:
      • Unregister-PackageSource Nugettest
    • If the Location column for source nuget.org shows https://api.nuget.org/v3/index.json (or something other than ttps://www.nuget.org/api/v2), update it:
      • Set-PackageSource nuget.org -NewLocation https://www.nuget.org/api/v2 -Trusted
      • Caveat: This may break the ability to browse NuGet packages in Visual Studio: see https://github.com/PowerShell/PowerShellGet/issues/107

If it is not installed: Install the provider from scratch:

  • Open an elevated PowerShell session.
  • Run the following commands:

    Install-PackageProvider nuget
    Register-PackageSource -ProviderName nuget -name nuget.org -Location https://www.nuget.org/api/v2 -Trusted
    

After completing the above steps, discovery (e.g., Find-Package Dapper) and installation (e.g., Install-Package Dapper) of NuGet packages should succeed.

By default, Install-Package installs to the AllUsers scope, which requires elevation, but you can opt into installing in the context of the current user only with -Scope CurrentUser.


Using a downloaded NuGet package:

  • As demonstrated in the question, you need to manually load the package's assemblies into your PowerShell session with Add-Type -Path <assembly-file-path>; however, in the era of .NET Core, packages may have DLLs for different .NET environments, so you cannot blindly load all *.dll files in the package folder.

    • In order to discover the file-system location of a downloaded package, query the .Source property of the relevant object returned by Get-Package:

      (Get-Package Dapper).Source
      
    • To see the full paths of all DLLs inside the package, run the following:

      (Get-ChildItem -Filter *.dll -Recurse (Split-Path (get-package dapper).Source)).FullName
      
  • Looking at the full DLL paths should tell you which DLL(s) are the right ones to load for your environment; using the example of the Dapper package:

    C:\Program Files\PackageManagement\NuGet\Packages\Dapper.1.50.4\lib\net451\Dapper.dll
    C:\Program Files\PackageManagement\NuGet\Packages\Dapper.1.50.4\lib\netstandard1.3\Dapper.dll
    C:\Program Files\PackageManagement\NuGet\Packages\Dapper.1.50.4\lib\netstandard2.0\Dapper.dll
    

As far as I know, there is no easier way to do this as of Windows PowerShell v5.1 / PowerShell Core v6.0.2 - do tells us if there is.



回答2:

Are you using Powershell 5? Because if you are, it has a package management module:

It appears to be open source: https://github.com/OneGet