在一般情況下,NuGet 套件會在編譯時自動從網路下載安裝,不需我們費心。但現實世界不如想像美好,有時你需要在無法上網的環境編譯專案,簡單解法是開個本地資料夾當成 NuGet 套件來源參考,手動下載預先放入 .nupkg 檔以實現離線安裝 NuGet 套件。

NuGet Gallery 每個套件首頁都有個 Download Package,點下去可下載格式如 dapper.2.1.24.nupkg 的套件檔,將其放入本機資料夾即可用於離線安裝。

如果你耐心破表,人工比對參照清單或遇到編譯有缺再去查網頁另存 .nupkg,日子其實也能過,但我是現代王藍田哪可能受得了?要我重複做個三次,肯定又會上演氣到摔滑鼠,用腳踩再咬破吐掉的戲碼(喂)。

所以,小工具來了,我寫了一支 Download-Nupkg.ps1,有三種用法:

# 指定套件名稱及版號
.\Download-Nupkg.ps1 Dapper 2.1.1
# 指定套件名稱,列舉版號,預設下載最新版
.\Download-Nupkg.ps1 Newtonsoft.Json
# 掃瞄 packages.config (.NET Framework) 或 .csproj (.NET 6+) 列出有參照的 NuGet 套件
# 比對本機資料夾是否已存在該 .nupkg ,若沒有才下載
.\Download-Nupkg.ps1 "X:\Github\WebApiWithAspNetMvcDemo\src\DemoWeb\packages.config"

操作展示

附上程式碼,有需要的同學請自取:(註:程式用 NuGet API 查詢套件可用版號及下載 .nupkg 檔)

param (
    [Parameter(Mandatory = $true)]
    [string]$pkgNameOrPkgConfig,
    [string]$version,
    [string]$saveFolder = '.'
)
Function GenNupkgPath($pkgName, $version) {
    return Join-Path $saveFolder "$pkgName.$version.nupkg"
}
Function DownloadNupkg {
    param (
        [Parameter(Mandatory = $true)]
        [string]$packageName, [string]$version
    )

    # https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource
    $lowerId = $packageName.ToLower()
    $url = "https://api.nuget.org/v3-flatcontainer/$lowerId/index.json"
    try {
        $json = (Invoke-WebRequest -Uri $url).Content
    }
    catch {
        Write-Host "Package not found - $packageName" -ForegroundColor Red
        return
    }
    $versions = ($json | ConvertFrom-Json).versions
    $latestVersion = $versions[-1]
    if ([string]::IsNullOrEmpty($version)) {
        $versionList = $versions -join ", "
        Write-Host "Versions: $versionList"
        $selVer = Read-Host "Select (default: $latestVersion)"
        if ([string]::IsNullOrEmpty($selVer)) {
            $selVer = $latestVersion
        }
        $version = $selVer
    }
    # Convert x.x.x.0 to x.x.x
    $verParts = $version.Split('.')
    if ($verParts.Length -eq 4 -and $verParts[-1] -eq '0') {
        $version = $verParts[0..2] -join '.'
    }
    if (!$versions.Contains($version)) {
        Write-Host "Failed to download $packageName $version" -ForegroundColor Red
        return
    }
    else {
        $lowerVersion = $version.ToLower()
        [string]$downloadUrl = "https://api.nuget.org/v3-flatcontainer/$lowerId/$lowerVersion/$lowerId.$lowerVersion.nupkg"
        $path = GenNupkgPath $packageName $version
        try {
            Invoke-WebRequest -Uri $downloadUrl -OutFile $path
            Write-Host "    $path saved" -ForegroundColor Gray
        }
        catch {
            Write-Host "Failed to download $downloadUrl" -ForegroundColor Red
        }
    }
}
$pkgList = @()
if ($pkgNameOrPkgConfig -match '(.config|.csproj)$') {
    try {
        [xml]$xml = Get-Content $pkgNameOrPkgConfig
        if ($xml.Project) {
            $xml.Project.ItemGroup | ForEach-Object {
                $_.PackageReference | ForEach-Object {
                    if (!(Test-Path (GenNupkgPath $_.Include $_.Version))) {
                        $pkgList += [PSCustomObject]@{
                            Name    = $_.Include
                            Version = $_.Version
                        }
                    }
                }
            }
        }
        elseif ($xml.packages) {
            $xml.packages.package | ForEach-Object {
                if (!(Test-Path (GenNupkgPath $_.id $_.version))) {
                    $pkgList += [PSCustomObject]@{
                        Name    = $_.id
                        Version = $_.version
                    }
                }
            }
        }
        else {
            Write-Host "Unknown XML schema" -ForegroundColor Red
            return
        }
        if ($pkgList.Count -gt 0) {
            Write-Host "Missing pakcages found:"
            $idx = 1
            $pkgList | ForEach-Object {
                Write-Host "$idx. $($_.Name) $($_.Version)"
                $idx++
            }
            $confirm = Read-Host "Download? (Y/n)"
            if (![string]::IsNullOrEmpty($confirm) -and $confirm -ine 'y') {
                Write-Host 'Abort' 
                return
            }
        }
    }
    catch {
        Write-Host "Failed to load $pkgNameOrPkgConfig - $_" -ForegroundColor Red
        return
    }
}
else {
    $pkgList += [PSCustomObject]@{
        Name    = $pkgNameOrPkgConfig
        Version = $version
    }
}
$pkgList | ForEach-Object {
    Write-Host "Downloading $($_.Name) $($_.Version)..." -ForegroundColor Yellow
    DownloadNupkg $_.Name $_.Version
}

My PowerShell tool to scan packages.config or .csproj to download .nupkg to local folder for offline install.


Comments

# by Jason

王藍田?我居然看不懂黑大的用典? 想不到不但技術跟不上,連中文造詣都跟不上! 趕緊搜尋惡補一下,繼續裝成看懂的樣子😂

Post a comment