﻿# 如何正確設定 Azure Artifacts 的 NuGet 套件來源 (Feeds)

如果你在 [Azure Artifacts](https://azure.microsoft.com/id-id/services/devops/artifacts/) 建立一個 NuGet 來源，由於所有存取 Azure Artifacts 上的 NuGet 套件都需要經過身分認證，如果你用的是 Visual Studio 就沒啥問題，更新到 Visual Studio 最新版就能搞定所有問題。但如果你用 Visual Studio Code 或 .NET CLI 進行 NuGet 操作，例如透過 `nuget restore` 或 `dotnet add package` 等命令，就會開始出現問題。這篇文章將說明如何正確設定，讓日後的 NuGet 操作都不再出問題。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/aecbe4c5a8a6c7193b9b-51228414-6b253c00-1993-11e9-98cc-c536eb117fa1.png)

### 重現問題

我會發現這個問題是因為我在用 Visual Studio Code 寫 .NET Core 應用程式，很多時候我都習慣透過 [dotnet add package](https://learn.microsoft.com/zh-tw/dotnet/core/tools/dotnet-add-package) 來新增套件，而這次新增的套件是放在 [Azure Artifacts](https://azure.microsoft.com/id-id/services/devops/artifacts/) 提供的 NuGet feeds 裡。

接著我先在專案根目錄 (方案目錄) 建立一個 `NuGet.config` 設定檔

```bash
dotnet new nuget
```

然後在命令提示字元執行以下命令，加入 Azure Artifacts 的 NuGet 來源網址 (Feeds)

```bash
nuget sources add -Name {YourOrg} -Source https://{YourOrg}.pkgs.visualstudio.com/_packaging/{YourProject}/nuget/v3/index.json
```

> 如果要使用 `NuGet` 命令列工具，你可以透過 [Chocolatey](https://chocolatey.org/) 安裝：`choco install nuget.commandline`

然後很簡單的加入一個 `Microsoft.Extensions.Logging` 套件，問題就發生了！

```bash
dotnet add package Microsoft.Extensions.Logging
```

這時畫面上就會顯示以下錯誤，很明顯是權限不足造成 ( `401 (Unauthorized)` )：

```
error: 無法從遠端來源 '{URL}' 擷取 'Microsoft.Extensions.Logging.Debug' 相關資訊。
error:   Response status code does not indicate success: 401 (Unauthorized).
```

### 使用 dotnet 存取 Azure Artifacts 套件來源(Feeds)

如果要使用 `.NET CLI` 命令列工具 (`dotnet`)，你必須下載 [Azure Artifacts Credential Provider](https://github.com/Microsoft/artifacts-credprovider) 才能順利通過 Azure Artifacts 的身分驗證。

以下是 PowerShell 自動安裝命令，安裝過程會建立 `%USERPROFILE%\.nuget\plugins` 目錄，並將支援 `.NET CLI` 的 **認證提供者** (Credential Providers) 安裝進去：

```ps
Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1'))
```

另一種安裝方式為：

```ps
wget https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1 -OutFile installcredprovider.ps1
.\installcredprovider.ps1
```

如果要更新現有版本，可以執行以下命令：

```ps
.\installcredprovider.ps1 -Force
```

這套工具還有一個 `netfx` 版本，官方建議不要手動安裝，因為這個版本會隨著 Visual Studio 自動更新，你安裝了之後，Visual Studio 就不會自動更新了。所以如果你要在 CI 伺服器上安裝，而該電腦又沒有安裝 Visual Studio 的話，可以執行以下命令：

```ps
.\installcredprovider.ps1 -AddNetfx
```

如果是 Linux 平台，可以執行以下命令，過程中也會完全自動安裝 **認證提供者** 到 `$HOME/.nuget/plugins/` 目錄下：

```bash
curl -s -L https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh | bash
```

在安裝好 **認證提供者** 之後，通常要先執行一次身分認證，之後才可以免輸入密碼。你可以執行一次 `dotnet restore --interactive` 命令，這裡的 `--interactive` 參數會讓你在執行的時候提示你如何通過身分驗證，也唯有這樣才能正確使用。

```bash
dotnet restore --interactive
```

第一次身分驗證通過後，身分認證就會記憶起來，預設會儲存在 [Session Token Cache Locations](https://github.com/Microsoft/artifacts-credprovider#session-token-cache-locations) 中，路徑如下：

-   Windows (cmd): `%LOCALAPPDATA%\MicrosoftCredentialProvider`
-   Windows (PS): `$env:UserProfile\AppData\Local\MicrosoftCredentialProvider`
-   Linux/MAC: `$HOME/.local/share/MicrosoftCredentialProvider/`

如果你日後想忘記密碼，可以到上述目錄刪除 `SessionTokenCache.dat` 檔案即可。

### 使用 NuGet 存取 Azure Artifacts 套件來源(Feeds)

原則上 `NuGet` 命令列工具會用以下順序檢查是否有合法的認證資訊(Credentials)：

1.  找到 `%AppData%\NuGet\NuGet.Config` 檔案，裡面會有個 [<packageSourceCredentials>](https://learn.microsoft.com/zh-tw/nuget/reference/nuget-config-file#packagesourcecredentials) 可以看見加密過的認證資訊。
    
    你可以透過 `nuget sources add` 或 `nuget sources update` 命令，來更新 Feeds 的預設登入帳號密碼。範例如下：
    
    ```bash
    nuget sources add -Name Duotify -Source https://{YourOrg}.pkgs.visualstudio.com/_packaging/{YourProject}/nuget/v3/index.json -UserName optional -PassWord [PAT]
    ```
    
    > 請注意：PAT (Personal Access Tokens) 可以到 `https://dev.azure.com/{YourOrg}/_usersSettings/tokens` 建立
    
2.  透過 NuGet 外掛 (plug-in) 的 **認證提供者** (Credential Providers) 進行身分認證
    
    參見 [Use NuGet with Azure DevOps Services feeds](https://learn.microsoft.com/en-us/azure/devops/artifacts/nuget/nuget-exe?view=vsts&tabs=new-nav)
    
    你在 Azure Artifacts 網頁介面中，會看到一個 **Connect to feeds** 功能，點下去之後會提供一個 `Download NuGet + Credential Provider` 按鈕 (如下圖示)。
    
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f132e606fcd3abd798ac-2019-01-15_00-31-42.jpg)
    
    點擊該按鈕會下載一個 `CredentialProviderBundle.zip` 壓縮檔，請將裡面的 `NuGet.exe` 與 `CredentialProvider.VSS.exe` 執行檔複製到你的專案內，直接跟 `NuGet.config` 設定檔放一起即可。
    
    > 注意：這個 `CredentialProviderBundle.zip` 壓縮檔，可以直接透過以下網址結構取得，不用登入也可以獲取。
    > 
    > ```
    > https://pkgs.dev.azure.com/{YourOrg}/_apis/public/nuget/client/CredentialProviderBundle.zip
    > ```
    > 
    > 記得要將 `{YourOrg}` 換成你公司的 Azure DevOps 組織名稱，例如 `miniasp.visualstudio.com` 就會是 `miniasp` 這個名字。
    
    接著你就可以嘗試透過 `nuget restore` 或 `nuget list` 命令，測試是不是能夠連到 Azure Artifacts feeds 進行驗證，這個 `CredentialProvider.VSS.exe` 工具會自動被啟動，跳出 Azure 的登入視窗，成功登入之後就會自動記憶密碼，下次就不用再輸入密碼驗證。
    
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/5fe20be8eafef4347e38-image_3177.png)
    
    > 注意：你只要成功登入一遍，就再也不能換身分，目前我還找不到怎樣才能忘記之前透過認證快取暫存的登入資料。我已經到 Stack Overflow 的[這裡](https://stackoverflow.com/questions/54187616/how-to-clear-credential-cache-by-credentialprovider-vss-exe)發問，過幾天可以看看有沒有解答。
    
    > 透過 `CredentialProvider.VSS.exe` 工具進行登入時，開啟的視窗是用萬惡的 Internet Explorer 瀏覽器，如果你的電腦安裝的是 Windows Server 的話，很有可能會因為 IE 的預設安全性過高，導致完全無法執行 JavaScript，連帶的也會讓你無法透過 `CredentialProvider.VSS.exe` 登入成功！如果你真的遇到這種狀況，可能就要換其他方式通過認證。不過，我個人花了不少時間實驗解決方案，發現這問題只要安裝 `netfx` 版本的 [Azure Artifacts Credential Provider](https://github.com/Microsoft/artifacts-credprovider) 就能解決問題，這個版本提供更完善的登入機制。
    
    不過，我個人通常不喜歡把 NuGet 放進專案中，而是習慣透過 [Chocolatey](https://chocolatey.org/) 直接安裝 [NuGet.exe](https://chocolatey.org/packages/nuget.commandline) 與 [CredentialProvider.VSS.exe](https://chocolatey.org/packages/nuget-credentialprovider-vss) 套件，所有設定都會自動配置完成：
    
    ```bash
    choco install nuget.commandline nuget-credentialprovider-vss -y
    ```
    
    當然，你也可以手動將 `CredentialProvider.VSS.exe` 複製到以下路徑 (這是 NuGet 認證提供者外掛的預設全域路徑)，未來在**這台電腦上**所有的 `NuGet.exe` 都會自動找到這個 **認證提供者** (Credential Providers)。但還是透過上述的 `choco install nuget-credentialprovider-vss` 命令安裝比較快！
    
    -   Windows (cmd): `%LOCALAPPDATA%\NuGet\CredentialProviders`
    -   Windows (PS): `$env:UserProfile\AppData\Local\NuGet\CredentialProviders`
    
    除了全域設定外，你也可以透過設定 `NUGET_CREDENTIALPROVIDERS_PATH` 環境變數，來指定 **認證提供者** (Credential Providers) 外掛所在路徑，這個設定很適合用在需要 CI/CD 的執行環境使用。
    
3.  直接從命令列提示你輸入帳號密碼
    
    如果從 `%AppData%\NuGet\NuGet.Config` 檔案，或是從 NuGet 外掛 (plug-in) 的 **認證提供者** (Credential Providers) 都找不到認證資訊，預設就會有互動介面幫你跟 Azure Artifacts feeds 進行驗證，缺點是每次都要你輸入帳號密碼進行驗證。
    

> 請注意：上述 3 點僅適用於 `NuGet.exe` 命令，並不適用於 `dotnet restore` 命令！

以下有兩個重要連結可以參考：

-   [Use NuGet with Azure DevOps Services feeds](https://learn.microsoft.com/en-us/azure/devops/artifacts/nuget/nuget-exe?view=vsts&tabs=new-nav)
-   [Authenticating feeds with nuget.exe credential providers](https://learn.microsoft.com/en-us/nuget/reference/extensibility/nuget-exe-credential-providers)

### 使用 MSBuild 還原 Azure Artifacts 套件

在以上動作都設定好之後，若要透過 MSBuild 執行 NuGet 套件還原工作，第一次使用要先指定 `/p:nugetInteractive=true` 屬性來執行，之後就會自動通過驗證。

```bash
msbuild /t:restore /p:nugetInteractive=true
```

### 透過 CI/CD 自動化建置部署的注意事項

透過 CI/CD 通常意味著必須在無互動模式下自動化執行，此時上述步驟就不適用，因為透過上述步驟連到 Azure Artifacts 的 NuGet 來源至少需要一次手動輸入帳號密碼。

這裡有兩種不同的設定環境：

1.  如果你是在 [Azure Pipelines](https://azure.microsoft.com/zh-tw/services/devops/pipelines/) 持續建置 (CI) 平台中執行
    
    在 Azure Pipelines 環境中連到 Azure Artifacts 是不太需要設定的，預設就會用當下的使用者身分進行驗證。
    
2.  如果你是在 **非 Azure 環境** 執行 (如 [Jenkins CI](https://jenkins.io/)、[GitLab CI](https://about.gitlab.com/product/continuous-integration/) 或 [Drone](https://drone.io/) 等)
    
    使用 `NuGet.exe` 的做套件還原的話，請參考上述 NuGet 部分的「第一種」認證方式，透過 `nuget sources` 命令時，特別加上 `-ConfigFile` 指定 CI 過程中的 `NuGet.config` 設定檔即可。預設如我沒有指定 `-ConfigFile` 參數的話，會自動去抓 `%AppData%\NuGet\NuGet.config` 這個檔案。
    
    使用 `dotnet` 工具 (.NET CLI) 的話，就可以直接透過設定 [環境變數](https://github.com/Microsoft/artifacts-credprovider/blob/master/README.md#environment-variables) 的方式達成，相對簡單很多！其設定步驟如下：
    
    1.  安裝 [Azure Artifacts Credential Provider](https://github.com/Microsoft/artifacts-credprovider) (本文上方有說明)
        
    2.  再到 `https://dev.azure.com/{YourOrg}/_usersSettings/tokens` 建立一個 [Personal Access Tokens](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=vsts) (PAT) 金鑰
        
        建立 PAT 時請記得設定適當的授權，盡量不要給 `Full access` 這麼大的權限！
        
    3.  最後設定幾個必要的 [環境變數](https://github.com/Microsoft/artifacts-credprovider/blob/master/README.md#environment-variables) 即可
        
        請參考以下 JSON 內容，將 Feeds 網址放入 endpoint 屬性，將產生好的 PAT 放入 password 屬性，最後將這份 JSON 字串設定給 `VSS_NUGET_EXTERNAL_FEED_ENDPOINTS` 環境變數，之後的 CI/CD 在執行 `dotnet restore` 的過程就會順利通過身分驗證，完全不需要互動介面操作。
        
        ```json
        {"endpointCredentials": [{"endpoint":"https://{YourOrg}.pkgs.visualstudio.com/_packaging/{YourProject}/nuget/v3/index.json", "password":"[PAT]"}]}
        ```
        

### 如何替團隊設定共用的 NuGet 環境設定 (加入版控)

[Set up a multi-developer NuGet environment | Microsoft Docs](https://learn.microsoft.com/en-us/azure/devops/artifacts/nuget/bootstrap-nuget?view=vsts)

1.  初始化 Git 本地儲存庫 (Optional)
    
    若你開發的專案是 .NET 相關的話，建議你可以建立一個預設的 `.gitignore` 檔案。你可以嘗試使用 PowerShell 執行以下命令，下載 `.gitignore` 的初始內容。
    
    ```ps
    Invoke-WebRequest https://www.gitignore.io/api/visualstudio -OutFile .gitignore
    ```
    
    接著初始化 Git 本地儲存庫 (非必要步驟)：
    
    ```ps
    git init
    git add .
    git commit -m "Initial commit"
    ```
    
2.  初始化團隊 NuGet 設定
    
    ```ps
    Invoke-WebRequest https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile nuget.exe
    .\nuget.exe install -OutputDirectory packages Microsoft.VisualStudio.Services.NuGet.Bootstrap
    .\packages\Microsoft.VisualStudio.Services.NuGet.Bootstrap.*\tools\Bootstrap.ps1
    rm .\nuget.exe
    echo ".tools/" | Out-File -Encoding ascii -Append -FilePath .\.gitignore
    ```
    
3.  調整 `nuget.config` 內容 (主要是 `<packageSources>` 套件來源設定)
    
    這套 `Microsoft.VisualStudio.Services.NuGet.Bootstrap` 工具預設會將 `nuget.org` 這個套件來源註解掉，因為一般來說企業內部只會開放允許的 NuGet 套件在企業內使用，不會開放所有 `nuget.org` 上面的公開套件。如果你需要開啟的話，請記得取消該行註解。
    
4.  將 `packages` 與 `.tools` 資料夾加入 `.gitignore` (Git) 或 `.tfignore` (TFS) 忽略版控清單中，然後將新增與變更的檔案全部加入版控。
    

經過上述設定後，之後無論誰要加入專案團隊，第一次使用該專案時，只要先執行一次 `.\init.ps` 或 `init.cmd` 即可初始化完整的 NuGet 相關設定，最新版的 NuGet 也會在執行過程中自動下載安裝。

這個 `init` 命令大概作了以下這些事：

1.  自動下載最新的 **NuGet.exe** 與 **CredentialProvider.VSS.exe** 工具，並置於 `.\.tools` 目錄下。
2.  自動將 `.\.tools\nuget.exe` 加入目前工作階段的 `PATH` 環境變數，方便你執行 `nuget` 命令。

### 相關連結

-   Azure DevOps
    -   [Consume NuGet packages in Visual Studio | Microsoft Docs](https://learn.microsoft.com/en-us/azure/devops/artifacts/nuget/consume?view=vsts&tabs=new-nav)
    -   [Use NuGet with Azure DevOps Services feeds](https://learn.microsoft.com/en-us/azure/devops/artifacts/nuget/nuget-exe?view=vsts&tabs=new-nav)
    -   [Use dotnet with Azure Artifacts feeds](https://learn.microsoft.com/en-us/azure/devops/artifacts/nuget/dotnet-exe?view=vsts)
    -   [Authenticate with personal access tokens - Azure DevOps | Microsoft Docs](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=vsts)
    -   [Set up a multi-developer NuGet environment | Microsoft Docs](https://learn.microsoft.com/en-us/azure/devops/artifacts/nuget/bootstrap-nuget?view=vsts)
    -   [Azure Artifacts Credential Provider](https://github.com/Microsoft/artifacts-credprovider)
-   NuGet
    -   [Authenticating feeds with nuget.exe credential providers | Microsoft Docs](https://learn.microsoft.com/en-us/nuget/reference/extensibility/nuget-exe-credential-providers)
    -   [NuGet Cross Plat Credential Plugin](https://github.com/NuGet/Home/wiki/NuGet-Cross-Plat-Credential-Plugin#plugin-installation-and-discovery)
    -   [nuget.config reference](https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file)
-   Stack Overflow
    -   [docker - Azure Artifacts gives unauthorized when trying to build Dockerfile - Stack Overflow](https://stackoverflow.com/questions/53419491/azure-artifacts-gives-unauthorized-when-trying-to-build-dockerfile/53510966#53510966)
