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 / PowerShell [Core] 7.1:
Update: The original answer below contains some useful general pointers, plus a link to the feature suggestion on GitHub to integrate NuGet packages with Add-Type
, but the Install-Package
based method it shows ultimately falls short, because it doesn't account for dependencies of a package, as
BACON points out:
The one (not-so-trivial) step that's missing is loading any dependencies that may have been installed, too. Because the Dependencies property doesn't contain enough information, it seems that would involve extracting the .nuspec
file from the .nupkg
file in the Source directory, reading the <group>
for the appropriate framework, and loading those packages' assemblies.
The following approach remedies this, but note that it first requires download and installation of the .NET SDK with its dotnet
CLI:
Create a folder for an auxiliary project to which the package will be added and change to it; e.g.:
Set-Location (New-Item -Type Directory assemblies)
In that folder, create a dummy library project:
Add a reference to the package of interest; e.g.:
Publish the dummy project, which copies all required DLLs, including dependencies, into the publish folder:
dotnet publish -c Release
- Important: The exact case (lower- vs. uppercase) of the
-c
argument determines the exact case of the corresponding output folder; to make sure your code works on case-sensitive filesystems too, notably on Linux, make sure that you use the exact same case in the file paths referring to the output binaries.
Test that the package's main assembly can be loaded; e.g.:
Now you can either reference the main DLL directly from the auxiliary project, or you can copy all bin/Release/*/publish/*.dll
files to a folder of your choice and reference it from there.
The following sample script shows a script that downloads the Terminal.Gui package on demand, and creates the auxiliary project in an assemblies
subfolder relative to the script's location.
$packageName = 'Terminal.Gui'
$assembly = "$packageName.dll"
# Set to @() to get the latest stable version.
$packageVersionArgs = '-v', '1.0.0-pre.4'
$projectFolder = 'assemblies' # Subfolder for the aux. project
$assemblyPath = "$PSScriptRoot/$projectFolder/bin/Release/*/publish/$assembly"
$literalAssemblyPath = Convert-Path -ErrorAction Ignore $assemblyPath
if ($literalAssemblyPath) {
Write-Verbose -vb "Package '$packageName' already installed. Loading main assembly: $literalAssemblyPath"
Add-Type -ErrorAction Stop -LiteralPath $literalAssemblyPath
}
else {
Write-Verbose -vb "Installing package '$packageName'..."
$null = Get-Command -ErrorAction Stop -CommandType Application dotnet
Push-Location (New-Item -ErrorAction Stop -Type Directory "$PSScriptRoot/$projectFolder")
$null = dotnet new classlib
$null = dotnet add package $packageName @packageVersionArgs
$null = dotnet publish -c Release
Pop-Location
Write-Verbose -vb "Loading main assembly: $assemblyPath"
Add-Type -ErrorAction Stop -Path $assemblyPath
}
# Instantiate a type from the package to verify that it was loaded.
"Listing property names of a [Terminal.Gui.Button] instance:"
[Terminal.Gui.Button]::new().psobject.Properties.Name
Caveat:
Some packages have dependencies on native libraries, which dotnet publish
places in the runtimes
subfolder tree of the publish folder, in platform-specific subfolders such as runtimeswin-x64
ative
.
In Windows PowerShell, Add-Type -LiteralPath
(and its underlying .NET API method, [System.Reflection.Assembly]::LoadFrom()
) does find the platform-appropriate native library, but, curiously, it does not work as of PowerShell (Core) 7.2.0-preview.9 - at least as observed with version 5.0.9 of the Microsoft.Data.Sqlite
NuGet package.
The workaround is to find the platform-appropriate native library in the runtimes
subfolder tree and copy it directly into the publish folder. The install-on-demand Add-NuGetType
helper function, discussed in this answer, automates this process.
ORIGINAL ANSWER
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:
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 in 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:
Note: See this suggestion on GitHub for making the use of NuGet packages in PowerShell easier by extending Add-Type
, which would obviate the need for all subsequent steps, which are still needed as of PowerShell Core 6.2.0.
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 always 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 FilesPackageManagementNuGetPackagesDapper.1.50.4lib
et451Dapper.dll
C:Program FilesPackageManagementNuGetPackagesDapper.1.50.4lib
etstandard1.3Dapper.dll
C:Program FilesPackageManagementNuGetPackagesDapper.1.50.4lib
etstandard2.0Dapper.dll
Given that .NET Standard DLLs run on all .NET platforms, however, you can programmatically look for the (latest) such DLLs and load them:
(Get-Item (Join-Path (Split-Path (Get-Package Dapper).Source) lib/netstandard*) |
Sort-Object { [version] ($_.Name -replace '^netstandard') })[-1] |
Get-ChildItem -Filter *.dll -Recurse |
ForEach-Object { Add-Type -LiteralPath $_.FullName }
The above looks for the highest available .NET Standard version DLLs; if you want to target a specific version, the command becomes easier; e.g., for .NET Standard 2.0
:
Get-ChildItem -Recurse -Filter *.dll -LiteralPath (Join-Path (Split-Path (Get-Package Dapper).Source) lib/netstandard2.0) |
ForEach-Object { Ad