powershell 从NuGet包加载程序集

ncecgwcz  于 2023-03-12  发布在  Shell
关注(0)|答案(2)|浏览(227)

有时在PowerShell脚本中,我需要使用Add-Type -AssemblyName访问特定的DLL。但是,我需要的DLL并不总是在计算机或GAC中。例如,我可能需要一个使用Dapper查询数据库的快速脚本。在这些情况下,我一直在复制DLL沿着ps1文件。我想知道这是否常见/一个好主意,以及是否有一个现有的扩展可以加载NuGet包,然后存储在全局或本地文件夹中,并自动调用Add-Type -AssemblyName
这很像分别在Node.js或Python中使用npmpip

更新

我做了一些研究,发现旧版本的PowerShell没有内置任何东西,我尝试使用nuget.exe从头开始编写一个,取得了一些进展

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

这将下载当前文件夹中“packages”文件夹下的一个给定的软件包/版本,沿着它的任何依赖项。然而,它看起来像是下载了每个framework版本,没有明显的方法来告诉你在给定的环境下使用哪个版本。
否则,您可以循环遍历结果并调用Add-Type:

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

我试着用restore命令和project.json文件来控制框架版本,但没有成功,这对我来说太过笨拙了。
我将查看@crownedjitter关于使用PowerShell 5的建议。

更新

使用@crownedjitter的建议,我最终能够用NuGet注册PackageManagement模块(参见下面的注解)。使用下面的命令,我能够重现上面的Nuget.exe命令所做的事情:

Install-Package Dapper -Destination packages

很明显,这要短得多。问题是它有同样的限制;它会关闭一个包的每个framework版本。如果这包括.NET core,它会关闭大量的.NET core framework!似乎没有一种方法可以指定一个目标framework(也就是.NET 4.5.1或更低版本)。

我想知道是否有一种方法可以根据PowerShell当前的$PSVersionTable.CLRVersion字段确定从哪个NuGet包文件夹加载DLL。

jgwigjjp

jgwigjjp1#

crownedjitter's helpful answer是一个很好的起点,Travis本人也在评论中提供了更多的提示,但让我尝试总结一下Windows PowerShell v5.1/PowerShell(核心)7.3.2

缺少的一个步骤(不那么简单)是加载任何可能已经安装的依赖项,因为the Dependencies property doesn't contain enough information,这似乎涉及到从Source目录的.nupkg文件中提取.nuspec文件,阅读<group>以获得适当的框架,并加载这些包的汇编。

以下方法可解决此问题,但请注意,首先需要下载并安装.NET SDK及其dotnet CLI

  • 为要向其中添加包的辅助项目创建文件夹,并对其进行更改;例如:
  • Set-Location (New-Item -Type Directory assemblies)
  • 在该文件夹中,创建一个虚拟库项目:
  • 对于 PowerShell(核心),默认情况下以最新安装的.NET(核心)SDK为目标:
  • dotnet new classlib
  • 对于 Windows PowerShell 或针对.NET 5+ * 操作系统特定 * 框架:
  • 要以仍然与.NET Framework兼容的最高.NET Standard为目标:
  • dotnet new classlib -f netstandard2.0
  • 要针对特定的.NET Framework版本 * 仅限 * 或针对特定于.NET 5+ * 操作系统 * 的Framework:
  • 您必须 * 手动 * 编辑生成的.csproj并更新<TargetFramework>元素;例如,为了以最新和最后版本4.8为目标,使用<TargetFramework>net48</TargetFramework>;要将.NET 6.0与特定于Windows的API结合使用,请使用

<TargetFramework>net6.0-windows</TargetFramework>

  • 以.NET Framework为目标可能需要首先下载developer pack
  • 添加对感兴趣的包的引用;例如:
  • dotnet add package Dapper
  • 要引用特定版本,请添加-v <version>
  • 发布虚拟项目,这会将所有必需的DLL(包括依赖项)复制到发布文件夹中:
  • dotnet publish -c Release
    重要信息-c参数的确切大小写(小写与大写)决定了相应输出文件夹的确切大小写;为了确保您的代码也能在区分大小写的文件系统上工作,特别是在Linux上, 请确保在引用输出二进制文件的文件路径中使用完全相同的大小写 *。
  • 测试是否可以加载包的主程序集;例如:
  • Add-Type -Path bin/Release/*/publish/Dapper.dll
  • 验证包的类型是否可以使用;例如:[Dapper.DbString]::new()

现在,您可以直接从辅助项目引用主DLL,也可以将所有bin/Release/*/publish/*.dll文件复制到您选择的文件夹并从那里引用它。
以下示例脚本显示了一个按需下载Terminal.Gui包并在相对于脚本位置的assemblies子文件夹中创建辅助项目的脚本。

$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

注意事项

  • 一些软件包依赖于 *native library *,dotnet publish将其放置在发布文件夹的runtimes子文件夹树中,位于特定于平台的子文件夹中,如runtimes\win-x64\native
  • Windows PowerShell 中,Add-Type -LiteralPath(及其底层的.NET API方法[System.Reflection.Assembly]::LoadFrom())* 确实 * 找到了适合平台的本机库,但奇怪的是,它 * 不 * 在 PowerShell(Core)7.2.0-preview.9 中起作用-至少在Microsoft.Data.Sqlite NuGet包的5.0.9版本中观察到了这一点。
    • 变通方法 * 是在runtimes子文件夹树中找到适合平台的本机库,然后将其 * 直接 * 复制到publish文件夹中。this answer中讨论的按需安装Add-NuGetType helper函数自动化了这一过程。

原始答案

  • 如前所述,PowerShell v5+(包括PowerShell Core)附带PackageManagement模块,它是一个 meta包管理器,通过 * 提供程序 * 提供对多个存储库的访问;这个模块的按需安装在v3和v4中 * 可能 * 是可能的(this download被标记为“March 2016 Preview”,这是我能找到的最新版本)。
  • Find-PackageProvider列出所有 * 可用 * 提供程序。
  • Get-PackageProvider列出 * 已安装 * 的。
  • 正是nuget提供程序支持通过Install-Package安装Nuget包,并且存在两个潜在障碍
  • 可能未安装nuget提供程序。
  • 它可能是使用错误的API URL安装的,从而阻止Find-Package返回结果。
    测试是否安装了nuget提供程序
# If this fails, the provider isn't installed
Get-PackageProvider nuget

如果 * 已 * 安装:验证包源URI是否正确:

  • 打开 * 提升的 * PowerShell会话。
  • 运行Get-PackageSource
  • 如果发现Nugettest源代码,请将其删除:
  • x1米39英寸
  • 如果源nuget.orgLocation列显示https://api.nuget.org/v3/index.json(或ttps://www.nuget.org/api/v2以外的内容),请更新它:
  • Set-PackageSource nuget.org -NewLocation https://www.nuget.org/api/v2 -Trusted
    *警告:这可能会破坏在Visual Studio中浏览NuGet包的功能:参见https://github.com/PowerShell/PowerShellGet/issues/107
    如果 * 未 * 安装:从头开始安装提供程序
  • 打开 * 提升的 * PowerShell会话。
  • 运行以下命令:
Install-PackageProvider nuget
Register-PackageSource -ProviderName nuget -name nuget.org -Location https://www.nuget.org/api/v2 -Trusted

完成上述步骤后,NuGet软件包的发现(例如Find-Package Dapper)和安装(例如Install-Package Dapper)应会成功。

默认情况下,Install-Package安装在AllUsers作用域中,这需要提升,但您可以选择仅使用-Scope CurrentUser在当前用户的上下文中安装。

*使用 * 下载的NuGet软件包

注:请参阅GitHub issue #6724,它建议通过扩展Add-Type来简化在PowerShell中使用NuGet包,这将消除对所有后续步骤的需要,而这些步骤在PowerShell Core 7.3.2中仍然需要。

  • 如问题中所示,您需要使用Add-Type -Path <assembly-file-path>将软件包的程序集手动加载到PowerShell会话中;但是,在.NET Core时代,软件包可能具有用于不同.NET环境的DLL,因此您不能总是盲目加载 * 软件包文件夹中的 * 所有 * *.dll文件
  • 要发现下载的软件包的文件系统位置,请查询Get-Package返回的相关对象的.Source属性:
(Get-Package Dapper).Source
  • 查看包中所有DLL的完整路径,请运行以下命令:
(Get-ChildItem -Filter *.dll -Recurse (Split-Path (Get-Package Dapper).Source)).FullName
  • 查看完整的DLL路径应该会告诉您哪些DLL适合您的环境加载;以Dapper封装为例:
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
  • 但是,如果***.NET Standard* DLL在 * 所有 * .NET平台**上运行,则可以通过编程方式查找(最新的)此类DLL并加载它们:
(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 }
  • 上面的代码查找 * 最高可用 * .NET标准版本DLL;如果你想瞄准一个 * 特定的版本 *,这个命令会变得更容易;例如,对于.NET标准2.0
Get-ChildItem -Recurse -Filter *.dll -LiteralPath (Join-Path (Split-Path (Get-Package Dapper).Source) lib/netstandard2.0) |
    ForEach-Object { Add-Type -LiteralPath $_.FullName }
42fyovps

42fyovps2#

您是否正在使用Powershell 5?因为如果您正在使用,它有一个包管理模块:

它似乎是开源的:https://github.com/OneGet

相关问题