How to Push a NuGet Package to a Feed from a Batch Script
After creating a NuGet package, the next step is publishing it to a NuGet feed where consumers can discover and install it. Whether pushing to the public NuGet.org gallery, a private Azure Artifacts feed, or a self-hosted NuGet server, automating the push process through Batch Script ensures consistent, secure, and repeatable package distribution as part of your CI/CD pipeline.
In this guide, we will explore how to push NuGet packages to various feed types from a Batch Script, covering authentication, error handling, and production-grade publishing workflows.
Understanding NuGet Feeds
| Feed Type | URL | Use Case |
|---|---|---|
| NuGet.org | https://api.nuget.org/v3/index.json | Public open-source packages |
| Azure Artifacts | https://pkgs.dev.azure.com/{org}/_packaging/{feed}/nuget/v3/index.json | Private organizational packages |
| GitHub Packages | https://nuget.pkg.github.com/{owner}/index.json | Repository-scoped packages |
| Self-hosted | Varies (BaGet, NuGet.Server, ProGet) | Full control, on-premises |
Method 1: Pushing to NuGet.org
@echo off
setlocal
set "package=artifacts\packages\MyLibrary.1.0.0.nupkg"
set "source=https://api.nuget.org/v3/index.json"
:: API key should be set as environment variable
if not defined NUGET_API_KEY (
echo [ERROR] NUGET_API_KEY environment variable not set.
echo Get your key from: https://www.nuget.org/account/apikeys
pause
exit /b 1
)
:: Verify package exists
if not exist "%package%" (
echo [ERROR] Package not found: %package%
pause
exit /b 1
)
echo Pushing %package% to NuGet.org...
dotnet nuget push "%package%" --api-key "%NUGET_API_KEY%" --source "%source%"
if %errorlevel%==0 (
echo.
echo [SUCCESS] Package published to NuGet.org.
echo It may take a few minutes to appear in search results.
) else (
echo.
echo [ERROR] Push failed.
exit /b 1
)
pause
Never hardcode API keys in script files. Use environment variables, CI/CD secrets, or credential managers. API keys in source control are a serious security risk.
Method 2: Pushing to a Private Feed
@echo off
setlocal
set "package=artifacts\packages\MyLibrary.1.0.0.nupkg"
set "feed_name=MyCompanyFeed"
set "feed_url=https://pkgs.dev.azure.com/mycompany/_packaging/MyFeed/nuget/v3/index.json"
:: Use PAT (Personal Access Token) for authentication
if not defined NUGET_PAT (
echo [ERROR] NUGET_PAT environment variable not set.
pause
exit /b 1
)
if not exist "%package%" (
echo [ERROR] Package not found: %package%
pause
exit /b 1
)
echo Pushing to private feed: %feed_name%
echo Package: %package%
echo.
dotnet nuget push "%package%" --api-key az --source "%feed_url%"
if %errorlevel%==0 (
echo [SUCCESS] Package pushed to %feed_name%.
) else (
echo [ERROR] Push failed. Check credentials and feed URL.
exit /b 1
)
pause
Adding a Feed Source (One-Time Setup)
@echo off
setlocal
set "feed_name=MyCompanyFeed"
set "feed_url=https://pkgs.dev.azure.com/mycompany/_packaging/MyFeed/nuget/v3/index.json"
if not defined NUGET_PAT (
echo [ERROR] NUGET_PAT environment variable not set.
pause
exit /b 1
)
echo Adding NuGet source: %feed_name%
dotnet nuget add source "%feed_url%" --name "%feed_name%" --username "user" --password "%NUGET_PAT%" --store-password-in-clear-text
if %errorlevel%==0 (
echo [OK] Source added.
) else (
echo [ERROR] Failed to add source.
)
pause
Method 3: Push All Packages in a Directory
When a build produces multiple packages:
@echo off
setlocal enabledelayedexpansion
set "package_dir=artifacts\packages"
set "source=https://api.nuget.org/v3/index.json"
if not defined NUGET_API_KEY (
echo [ERROR] NUGET_API_KEY not set.
pause
exit /b 1
)
if not exist "%package_dir%\" (
echo [ERROR] Package directory not found: %package_dir%
pause
exit /b 1
)
echo =============================================
echo PUSH ALL PACKAGES
echo =============================================
echo.
set "pushed=0"
set "failed=0"
set "total=0"
for %%P in ("%package_dir%\*.nupkg") do (
set /a total+=1
echo Pushing %%~nxP...
dotnet nuget push "%%P" --api-key "%NUGET_API_KEY%" --source "%source%" --skip-duplicate
if !errorlevel!==0 (
echo [OK]
set /a pushed+=1
) else (
echo [FAIL]
set /a failed+=1
)
)
if !total!==0 (
echo [WARNING] No .nupkg files found in %package_dir%.
pause
exit /b 1
)
echo.
echo Pushed: !pushed! Failed: !failed!
if !failed! gtr 0 exit /b 1
pause
The --skip-duplicate Flag
The --skip-duplicate flag prevents errors when pushing a version that already exists on the feed. This is useful in retry scenarios and when re-running pipelines.
Method 4: Full Publish Pipeline
Build, test, pack, and push in a single workflow:
@echo off
setlocal enabledelayedexpansion
:: Configuration
set "project=MyLibrary"
set "test_project=MyLibrary.Tests"
set "config=Release"
set "output=artifacts\packages"
set "source=https://api.nuget.org/v3/index.json"
:: Validate API key
if not defined NUGET_API_KEY (
echo [ERROR] Set NUGET_API_KEY before running.
exit /b 1
)
:: Version
set "version=1.0.0"
if exist VERSION (
set /p "version=" < VERSION
)
echo =============================================
echo NUGET PUBLISH PIPELINE
echo %project% v!version!
echo =============================================
echo.
:: Stage 1: Build
echo [1/4] Building...
dotnet build "%project%" -c %config% --verbosity quiet
if !errorlevel! neq 0 (
echo [FAIL] Build failed.
exit /b 1
)
echo OK.
:: Stage 2: Test
echo [2/4] Testing...
if exist "%test_project%" (
dotnet test "%test_project%" -c %config% --no-build --verbosity quiet
if !errorlevel! neq 0 (
echo [FAIL] Tests failed.
exit /b 1
)
echo OK.
) else (
echo No test project found (%test_project%^). Skipping.
)
:: Stage 3: Pack
echo [3/4] Packing...
if exist "%output%" rmdir /s /q "%output%"
mkdir "%output%"
dotnet pack "%project%" -c %config% --no-build -o "%output%" /p:PackageVersion=!version!
if !errorlevel! neq 0 (
echo [FAIL] Pack failed.
exit /b 1
)
echo OK.
:: Stage 4: Push
echo [4/4] Pushing to feed...
set "push_failed=0"
for %%P in ("%output%\*.nupkg") do (
echo Pushing %%~nxP...
dotnet nuget push "%%P" --api-key "%NUGET_API_KEY%" --source "%source%" --skip-duplicate
if !errorlevel! neq 0 set "push_failed=1"
)
if !push_failed!==1 (
echo [ERROR] One or more pushes failed.
exit /b 1
)
echo.
echo =============================================
echo PUBLISHED: %project% v!version!
echo =============================================
pause
Method 5: Push with Symbols Package
For debugging support, push symbols alongside the main package:
@echo off
setlocal enabledelayedexpansion
set "project=MyLibrary"
set "config=Release"
set "output=artifacts\packages"
set "source=https://api.nuget.org/v3/index.json"
if not defined NUGET_API_KEY (
echo [ERROR] NUGET_API_KEY not set.
exit /b 1
)
echo Packing with symbols...
if exist "%output%" rmdir /s /q "%output%"
mkdir "%output%"
:: Pack with symbols
dotnet pack "%project%" -c %config% -o "%output%" ^
/p:IncludeSymbols=true ^
/p:SymbolPackageFormat=snupkg
if !errorlevel! neq 0 (
echo [ERROR] Pack failed.
exit /b 1
)
echo.
echo Pushing package and symbols...
:: Push the main .nupkg (the .snupkg is pushed automatically by NuGet.org)
set "push_count=0"
for %%P in ("%output%\*.nupkg") do (
echo %%~xP | findstr /i "snupkg" >nul
if !errorlevel! neq 0 (
echo %%~nxP
dotnet nuget push "%%P" --api-key "%NUGET_API_KEY%" --source "%source%" --skip-duplicate
if !errorlevel! neq 0 (
echo [FAIL]
exit /b 1
)
set /a push_count+=1
)
)
if !push_count!==0 (
echo [WARNING] No .nupkg files found to push.
exit /b 1
)
echo.
echo [DONE] Package and symbols pushed.
pause
Listing Packages on a Feed
Verify your package was published:
@echo off
set "package_id=MyLibrary"
echo Searching NuGet.org for %package_id%...
echo.
dotnet nuget search "%package_id%" --source "https://api.nuget.org/v3/index.json" --take 5
pause
Deleting/Unlisting a Package
@echo off
setlocal
set "package_id=MyLibrary"
set "version=1.0.0"
set "source=https://api.nuget.org/v3/index.json"
if not defined NUGET_API_KEY (
echo [ERROR] NUGET_API_KEY not set.
pause
exit /b 1
)
echo Unlisting %package_id% v%version% from NuGet.org...
echo (Note: NuGet.org does not support true deletion, only unlisting^)
dotnet nuget delete "%package_id%" "%version%" --source "%source%" --api-key "%NUGET_API_KEY%" --non-interactive
if %errorlevel%==0 (
echo [OK] Package unlisted.
) else (
echo [ERROR] Unlist failed.
)
pause
Common Mistakes
The Wrong Way: Hardcoding API Keys
:: WRONG - API key exposed in source code
dotnet nuget push MyLib.nupkg --api-key abc123-secret-key --source nuget.org
Output Concern:
API keys in scripts can be committed to source control, exposing them to anyone with repository access. Use environment variables (%NUGET_API_KEY%), CI/CD secret variables, or credential managers instead.
The Wrong Way: Pushing Without Testing
:: WRONG - Publishes untested code
dotnet pack MyLib -c Release -o packages
dotnet nuget push packages\MyLib.1.0.0.nupkg --api-key %KEY% --source nuget.org
:: Package may contain broken code
Once published, a NuGet package version cannot be overwritten (only unlisted). Always run tests before publishing to ensure the package works correctly.
The Wrong Way: Pushing Duplicate Versions
:: FAILS - Version already exists on the feed
dotnet nuget push MyLib.1.0.0.nupkg --source nuget.org
:: HTTP 409 Conflict
NuGet feeds enforce unique versions. Use --skip-duplicate to gracefully handle re-pushes, or bump the version before pushing.
Best Practices
- Never hardcode API keys: Use environment variables or CI/CD secrets.
- Test before pushing: Published versions are permanent (cannot be overwritten).
- Use
--skip-duplicate: Prevents errors when re-running pipelines. - Push symbols: Enable source debugging for consumers by including
.snupkgfiles. - Verify after pushing: Search the feed to confirm the package is available.
Conclusion
Pushing NuGet packages to a feed from a Batch Script is handled by the dotnet nuget push command with an API key for authentication. By securing credentials through environment variables, using --skip-duplicate for idempotent pushes, and integrating the push step into a full build-test-pack-push pipeline, teams achieve automated, reliable package distribution. Whether publishing open-source libraries to NuGet.org or internal packages to private feeds, the pattern remains the same: verify the code, create the package, and push it securely.