Skip to main content

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 TypeURLUse Case
NuGet.orghttps://api.nuget.org/v3/index.jsonPublic open-source packages
Azure Artifactshttps://pkgs.dev.azure.com/{org}/_packaging/{feed}/nuget/v3/index.jsonPrivate organizational packages
GitHub Packageshttps://nuget.pkg.github.com/{owner}/index.jsonRepository-scoped packages
Self-hostedVaries (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
warning

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

  1. Never hardcode API keys: Use environment variables or CI/CD secrets.
  2. Test before pushing: Published versions are permanent (cannot be overwritten).
  3. Use --skip-duplicate: Prevents errors when re-running pipelines.
  4. Push symbols: Enable source debugging for consumers by including .snupkg files.
  5. 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.