在大型項目中,我們常常需要定期打包多個 .csproj
項目為 NuGet 包,并上傳到私有 NuGet 服務。這篇文章分享一份實戰腳本,支持以下自動化流程:
-
自動讀取、更新
.csproj
文件中的Version
、PackageOutputPath
等節點; -
自動構建并打包為
.nupkg
; -
自動上傳至私有 NuGet 源;
-
支持 SVN 更新與提交;
-
支持中文路徑,日志分離記錄成功與失敗信息。
? 功能概覽
功能點 | 說明 |
---|---|
自動更新版本號 | 根據日期自動生成版本號,并支持手動輸入 |
修改 | 自動注入 |
打包 NuGet | 使用 |
上傳 NuGet | 自動上傳所有打包成功的 |
SVN 支持 | 自動執行 SVN 更新并提交修改 |
日志清晰分離 |
|
📦 腳本內容
以下是完整的批處理腳本代碼,推薦將其保存為 auto_push_nuget.bat
:
@echo off
REM 切換到 UTF-8 編碼,支持中文路徑
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ===== Auto push nuget started =====REM ---------------- 配置區 ----------------
set "NUGET_SOURCE=你的私有nuget服務器"
set "API_KEY=私有nuget服務器的key,沒有填空"
set "SUCCESS_LOG=success_log.txt"
set "ERROR_LOG=error_log.txt"
set "SVN_COMMIT_LIST=svn_commit_list.txt"
set "TEMP_LOG=temp_ps_output.txt"
set "SVN_UPDATE_TEMP=svn_update_temp.txt"
set SUCCESS_COUNT=0
set FAIL_COUNT=0
REM -----------------------------------------
?? 腳本前置說明:
腳本自動將當前控制臺設置為 UTF-8,確保支持中文路徑和日志輸出;
所有日志均分離輸出到文件中,避免控制臺信息混亂。
🎯 項目路徑配置
修改以下部分,添加你要打包的 .csproj
路徑(相對路徑):
注意:索引數與COUNT一致
:: 定義項目路徑數組
set "PROJECT_PATHS[0]=Xxx.BI.WebSite.Core\Xxx.BI.WebSite.Core.csproj"
set "PROJECT_PATHS[1]=Xxx.BI.WebSite.RazorWeb\Xxx.BI.WebSite.RazorWeb.csproj"
set "PROJECT_COUNT=2"
根據你的項目實際路徑修改 PROJECT_PATHS
,添加你希望打包的工程列表。
?? 注意:這里的路徑請填寫 相對于腳本所在目錄的路徑,不要寫絕對路徑。如下圖
![]()
版本號格式為:
6{YY}.{MM}.{DD}.{X}
,其中 X 是遞增的補丁號。
例如:624.05.14.2
for /f "tokens=1-3 delims=/" %%a in ('powershell -command "Get-Date -Format 'yyyy/MM/dd'"') do (set YYYY=%%aset MM=%%bset DD=%%c
)
set YY=!YYYY:~2,2!
set /a M=1!MM! - 100
set /a D=1!DD! - 100
set VERSION_PREFIX=6!YY!.!M!.!D!
🔧 修改 .csproj 文件
使用 PowerShell 對 XML 進行修改,確保插入以下節點:
-
<Version>
:自動計算或用戶輸入; -
<Description>
:設置描述; -
<PackageOutputPath>
:統一輸出至..\..\..\nupkgs
; -
<GeneratePackageOnBuild>
:確保打包時包含所有內容。
$pg = $xml.Project.PropertyGroup | Where-Object { $_.Condition -eq $null -or $_.Condition -eq '' } | Select-Object -First 1;
if (-not $pg) { $pg = $xml.CreateElement('PropertyGroup'); $xml.Project.AppendChild($pg) | Out-Null };
# 后續注入節點邏輯...
🏗? 構建與打包
dotnet build "!CSProjPath!" -c Release
dotnet pack "!CSProjPath!" -c Release --output "%~dp0nupkgs"
腳本會在 nupkgs
文件夾生成對應 .nupkg
文件。
?? 自動上傳 NuGet 包
所有成功打包的路徑會記錄到 success_log.txt
,隨后逐個推送:
如果沒有API-KEY,請刪除 --api-key "%API_KEY%"
dotnet nuget push "!NUPKG_PATH!" --source "%NUGET_SOURCE%" --api-key "%API_KEY%"
🧾 SVN 自動化支持
-
支持腳本運行前執行
svn update
; -
成功處理的項目會記錄在
svn_commit_list.txt
; -
最后統一執行
svn commit
自動提交版本號更新:
svn commit "項目路徑" -m "Auto update Version to 624.05.14.1"
🧠?SVN 提交
每次成功修改 .csproj
后,記錄在列表文件 svn_commit_list.txt
中,最終一并執行 SVN 提交:
svn commit "項目路徑" -m "Auto update Version to 6xx.xx.xx.x"
📜 附錄:完整批處理腳本源碼
@echo off
REM 切換到 UTF-8 編碼,支持中文路徑
chcp 65001 >nul
setlocal EnableDelayedExpansion
echo ===== Auto push nuget started =====
REM ---------------- 配置區 ----------------
set "NUGET_SOURCE=你的私有nuget服務器"
set "API_KEY=私有nuget服務器key"
set "SUCCESS_LOG=success_log.txt"
set "ERROR_LOG=error_log.txt"
set "SVN_COMMIT_LIST=svn_commit_list.txt"
set "TEMP_LOG=temp_ps_output.txt"
set "SVN_UPDATE_TEMP=svn_update_temp.txt"::成功/失敗條數
set SUCCESS_COUNT=0
set FAIL_COUNT=0
REM 注意:建議檢查服務器是否支持 HTTPS,例如 https://www.baidu.com:8888
REM -----------------------------------------:: 清空日志和 SVN 提交列表
if exist "%SUCCESS_LOG%" del "%SUCCESS_LOG%"
if exist "%ERROR_LOG%" del "%ERROR_LOG%"
if exist "%SVN_COMMIT_LIST%" del "%SVN_COMMIT_LIST%"
if exist "%TEMP_LOG%" del "%TEMP_LOG%"
if exist "%SVN_UPDATE_TEMP%" del "%SVN_UPDATE_TEMP%":: 清理 SVN_DIR,確保無換行符或多余字符
set "SVN_DIR=%~dp0"
:: 去掉末尾反斜杠
if "!SVN_DIR:~-1!"=="\" set "SVN_DIR=!SVN_DIR:~0,-1!"
:: 規范化路徑
for %%P in ("!SVN_DIR!") do set "SVN_DIR=%%~fP"
echo [Debug] SVN directory: !SVN_DIR! >> "%SUCCESS_LOG%":: 執行 SVN 更新
echo [Info] Updating SVN working copy for directory: !SVN_DIR! >> "%SUCCESS_LOG%"
echo [Info] Updating SVN working copy...
svn update "!SVN_DIR!" > "%SVN_UPDATE_TEMP%" 2>&1
if errorlevel 1 (echo [Error] Failed to update SVN working copy. >> "%ERROR_LOG%"echo [Error] Failed to update SVN working copy. Error details:type "%SVN_UPDATE_TEMP%"echo [Error] Script will exit. >> "%ERROR_LOG%"echo [Error] Script will exit.del "%SVN_UPDATE_TEMP%"exit /b 1
) else (echo [Success] SVN working copy updated successfully. >> "%SUCCESS_LOG%"echo [Success] SVN working copy updated successfully.del "%SVN_UPDATE_TEMP%"
):: 獲取當前日期作為版本前綴(6 + 年后兩位 + 月 + 日)
for /f "tokens=1-3 delims=/" %%a in ('powershell -command "Get-Date -Format 'yyyy/MM/dd'"') do (set YYYY=%%aset MM=%%bset DD=%%c
)
set YY=!YYYY:~2,2!
set /a M=1!MM! - 100
set /a D=1!DD! - 100
set VERSION_PREFIX=6!YY!.!M!.!D!
echo [Debug] VERSION_PREFIX: !VERSION_PREFIX! >> "%SUCCESS_LOG%":: 定義項目路徑數組
::注意:PROJECT_PATHS[X]=實際條數(X),COUNT=實際條數
:: 這里的路徑規則,請錄入相對路徑,就是你的BAT所在文件夾以后的路徑
set "PROJECT_PATHS[0]=Xxx.BI.WebSite.Core\Xxx.BI.WebSite.Core.csproj"
set "PROJECT_PATHS[1]=Xxx.BI.WebSite.RazorWeb\Xxx.BI.WebSite.RazorWeb.csproj"
set "PROJECT_COUNT=2":: 循環處理數組中的相對路徑
for /l %%i in (0,1,%PROJECT_COUNT%-1) do (set "RELATIVE_PATH=!PROJECT_PATHS[%%i]!"if not "!RELATIVE_PATH!"=="" (echo [Info] Reading relative path: !RELATIVE_PATH! >> "%SUCCESS_LOG%"call :ProcessProject "!RELATIVE_PATH!")
)REM 統一 NuGet 包上傳
echo.
echo ===== Pushing NuGet packages =====
echo ===== Pushing NuGet packages ===== >> "%SUCCESS_LOG%"
echo ===== Pushing NuGet packages ===== >> "%ERROR_LOG%"
set "FOUND_NUPKG=0"
if exist "%SUCCESS_LOG%" (for /f "tokens=1,* delims=:" %%a in ('findstr /B "\[Success\] Pack succeeded:" "%SUCCESS_LOG%"') do (set "NUPKG_PATH=%%b"REM 去除首尾空格并規范化路徑for /f "tokens=*" %%c in ("!NUPKG_PATH!") do (set "NUPKG_PATH=%%~fc")if "!NUPKG_PATH!"=="" (echo [Warning] Skipping empty NUPKG_PATH >> "%ERROR_LOG%"echo [Warning] Skipping empty NUPKG_PATH) else (echo [Debug] Checking NuGet package: !NUPKG_PATH! >> "%SUCCESS_LOG%"if exist "!NUPKG_PATH!" (set "FOUND_NUPKG=1"echo Pushing: !NUPKG_PATH!echo Pushing: !NUPKG_PATH! >> "%SUCCESS_LOG%"dotnet nuget push "!NUPKG_PATH!" --source "%NUGET_SOURCE%" --api-key "%API_KEY%" >> "%SUCCESS_LOG%" 2>> "%ERROR_LOG%"if errorlevel 1 (set /a FAIL_COUNT+=1echo [Error] Push failed: !NUPKG_PATH! >> "%ERROR_LOG%"echo [Error] Push failed: !NUPKG_PATH!) else ( set /a SUCCESS_COUNT+=1echo [Success] Push succeeded: !NUPKG_PATH! >> "%SUCCESS_LOG%"echo [Success] Push succeeded: !NUPKG_PATH! )) else (set /a FAIL_COUNT+=1echo [Error] NuGet package not found during push: !NUPKG_PATH! >> "%ERROR_LOG%"echo [Error] NuGet package not found during push: !NUPKG_PATH!)))
)
if "!FOUND_NUPKG!"=="0" (echo [Warning] No successful NuGet packages found to push >> "%ERROR_LOG%"echo [Warning] No successful NuGet packages found to push
)REM 統一 SVN 提交
if exist "%SVN_COMMIT_LIST%" (echo.echo ===== Committing to SVN =====echo ===== Committing to SVN ===== >> "%SUCCESS_LOG%"echo ===== Committing to SVN ===== >> "%ERROR_LOG%"for /f "tokens=1,2 delims=;" %%a in (%SVN_COMMIT_LIST%) do (echo Committing: %%aecho Committing: %%a >> "%SUCCESS_LOG%"svn commit "%%a" -m "Auto update Version to %%b" >> "%SUCCESS_LOG%" 2>&1if errorlevel 1 (echo [Error] SVN commit failed: %%a >> "%ERROR_LOG%"echo [Error] SVN commit failed: %%a >> "%SUCCESS_LOG%"echo [Error] SVN commit failed: %%a) else (echo [Success] SVN commit succeeded: %%a >> "%SUCCESS_LOG%"echo [Success] SVN commit successed: %%a))del "%SVN_COMMIT_LIST%"
)echo. >> "%SUCCESS_LOG%"
echo ===== All tasks completed ===== >> "%SUCCESS_LOG%"
echo. >> "%ERROR_LOG%"
echo ===== All tasks completed ===== >> "%ERROR_LOG%"
echo.
echo ===== All tasks completed =====
echo Success log: %SUCCESS_LOG%
echo Error log: %ERROR_LOG%
echo Nuget push successed : %SUCCESS_COUNT%
echo Nuget push error: %FAIL_COUNT%
del "%ERROR_LOG%"
del "%SUCCESS_LOG%"
if exist "%SVN_UPDATE_TEMP%" del "%SVN_UPDATE_TEMP%"
pauseecho ===== Auto push nuget ended =====
endlocal
exit /b 0:ProcessProject
set "RELATIVE_PATH=%~1"
echo [Info] Processing relative path: %RELATIVE_PATH% >> "%SUCCESS_LOG%"REM 移除相對路徑的前導 \(如果存在)
set "CLEAN_PATH=%RELATIVE_PATH%"
if "!CLEAN_PATH:~0,1!"=="\" set "CLEAN_PATH=!CLEAN_PATH:~1!"
echo [Debug] CLEAN_PATH: !CLEAN_PATH! >> "%SUCCESS_LOG%"REM 使用 pushd 切換到腳本目錄
pushd "%~dp0"
echo [Debug] Current directory after pushd: %CD% >> "%SUCCESS_LOG%"REM 拼接完整路徑
set "CSProjPath=%CD%\!CLEAN_PATH!"
REM 規范化路徑
for %%P in ("!CSProjPath!") do set "CSProjPath=%%~fP"
echo [Info] Full .csproj path: !CSProjPath! >> "%SUCCESS_LOG%"REM 驗證 .csproj 文件存在
if not exist "!CSProjPath!" (echo [Warning] File not found: !CSProjPath! >> "%ERROR_LOG%"echo [Warning] File not found: !CSProjPath!popdexit /b 0
)REM 恢復原始目錄
popd
echo [Debug] Directory restored: %CD% >> "%SUCCESS_LOG%"echo.
echo ===== Processing: !CSProjPath! =====
echo ===== Processing: !CSProjPath! ===== >> "%SUCCESS_LOG%"REM —— 清空上次遺留的變量 ——
set "OLD_VERSION="
set "LAST_PART="REM —— 1) 讀取 csproj 中已有的 Version ——
powershell -NoProfile -Command ^"try { [xml]$x = Get-Content -Path '!CSProjPath!' -Encoding UTF8 -ErrorAction Stop; $version = $x.Project.PropertyGroup | Where-Object { $_.Version -and ($_.Condition -eq $null -or $_.Condition -eq '') } | Select-Object -ExpandProperty Version -First 1; if ($version) { $version.Trim() } else { '' } } catch { Write-Output 'ERROR: ' + $_.Exception.Message; exit 1 }" > "%TEMP_LOG%"
if errorlevel 1 (echo [Error] Failed to read Version from !CSProjPath! >> "%ERROR_LOG%"echo [Error] Failed to read Version from !CSProjPath!type "%TEMP_LOG%" >> "%ERROR_LOG%"popdexit /b 1
)
for /f "delims=" %%v in (%TEMP_LOG%) do (set "OLD_VERSION=%%v"
)
echo [Debug] OLD_VERSION: !OLD_VERSION! >> "%SUCCESS_LOG%"
del "%TEMP_LOG%"REM —— 2) 基于 OLD_VERSION 計算新的 LAST_PART ——
set "LAST_PART=1"
if defined OLD_VERSION (REM 檢查 OLD_VERSION 是否包含錯誤if not "!OLD_VERSION!"=="!OLD_VERSION:ERROR:=!" (echo [Warning] Invalid OLD_VERSION: !OLD_VERSION! >> "%ERROR_LOG%"echo [Warning] Invalid OLD_VERSION: !OLD_VERSION!set "OLD_VERSION=") else (REM 清理 OLD_VERSION 中的空格set "OLD_VERSION=!OLD_VERSION: =!"echo [Debug] Cleaned OLD_VERSION: !OLD_VERSION! >> "%SUCCESS_LOG%"for /f "tokens=1-4 delims=." %%a in ("!OLD_VERSION!") do (set "OLD_PREFIX=%%a.%%b.%%c"echo [Debug] OLD_PREFIX: !OLD_PREFIX!, VERSION_PREFIX: !VERSION_PREFIX! >> "%SUCCESS_LOG%"if "!OLD_PREFIX!"=="!VERSION_PREFIX!" (set /a LAST_PART=%%d + 1echo [Debug] LAST_PART: !LAST_PART! >> "%SUCCESS_LOG%")))
)echo [Info] version number generation in progress...
echo [Info] recommend version: !VERSION_PREFIX!.!LAST_PART! ,Enter use this version or input new version:
set /p USER_VER=if defined USER_VER (set "CUSTOM_VERSION=!USER_VER!"
)if defined CUSTOM_VERSION (set "NEW_VERSION=!CUSTOM_VERSION!"
) else (set "NEW_VERSION=!VERSION_PREFIX!.!LAST_PART!"
)echo [Info] NEW_VERSION = !NEW_VERSION! >> "%SUCCESS_LOG%"
echo [Info] NEW_VERSION = !NEW_VERSION!
echo [Info] version number generation completed...REM —— 3) 使用 PowerShell 注入并**覆蓋**所有必要節點 ——
echo [Info] modify the csproj file...
set NEW_VERSION=!NEW_VERSION!
powershell -NoProfile -Command ^"$path = '!CSProjPath!';" ^"try { [xml]$xml = Get-Content -Path $path -Encoding UTF8 -Raw -ErrorAction Stop; $pg = $xml.Project.PropertyGroup | Where-Object { $_.Condition -eq $null -or $_.Condition -eq '' } | Select-Object -First 1; if (-not $pg) { $pg = $xml.CreateElement('PropertyGroup'); $xml.Project.AppendChild($pg) | Out-Null }; $vn = $pg.SelectSingleNode('Version'); if (-not $vn) { $vn = $xml.CreateElement('Version'); $pg.AppendChild($vn) | Out-Null }; $vn.InnerText = $env:NEW_VERSION; $desc = $pg.SelectSingleNode('Description'); if (-not $desc) { $desc = $xml.CreateElement('Description'); $pg.AppendChild($desc) | Out-Null }; $desc.InnerText = 'Automatically generate nuget packages'; $gpb = $pg.SelectSingleNode('GeneratePackageOnBuild'); if (-not $gpb) { $gpb = $xml.CreateElement('GeneratePackageOnBuild'); $gpb.InnerText = 'true'; $pg.AppendChild($gpb) | Out-Null }; $pop = $pg.SelectSingleNode('PackageOutputPath'); if (-not $pop) { $pop = $xml.CreateElement('PackageOutputPath'); $pg.AppendChild($pop) | Out-Null }; $pop.InnerText = '..\\..\\..\\nupkgs'; $sw = New-Object System.IO.StreamWriter($path, $false, (New-Object System.Text.UTF8Encoding $true)); $xml.Save($sw); $sw.Close(); Write-Output 'PS_INJECTION_SUCCESS'; } catch { Write-Output 'ERROR: ' + $_.Exception.Message; exit 1 }" > "%TEMP_LOG%"findstr "PS_INJECTION_SUCCESS" "%TEMP_LOG%" >nul
if errorlevel 1 (echo [Error] XML injection failed: !CSProjPath! >> "%ERROR_LOG%"echo [Error] XML injection failed: !CSProjPath!type "%TEMP_LOG%" >> "%ERROR_LOG%"exit /b 1
) else (echo [Success] modify successed
)
echo [Info] modify the csproj file...REM —— 驗證 Version 節點 ——
powershell -NoProfile -Command ^"try { [xml]$x = Get-Content -Path '!CSProjPath!' -Encoding UTF8 -ErrorAction Stop; $version = $x.Project.PropertyGroup | Where-Object { $_.Version -and ($_.Condition -eq $null -or $_.Condition -eq '') } | Select-Object -ExpandProperty Version -First 1; if ($version) { $version.Trim() } else { '' } } catch { Write-Output 'ERROR: ' + $_.Exception.Message; exit 1 }" > "%TEMP_LOG%"
if errorlevel 1 (echo [Error] Failed to verify Version in !CSProjPath! >> "%ERROR_LOG%"echo [Error] Failed to verify Version in !CSProjPath!type "%TEMP_LOG%" >> "%ERROR_LOG%"exit /b 1
)
for /f "delims=" %%v in (%TEMP_LOG%) do (set "VERIFIED_VERSION=%%v"
)
echo [Debug] Version after injection: !VERIFIED_VERSION! >> "%SUCCESS_LOG%"
if not "!VERIFIED_VERSION!"=="!NEW_VERSION!" (echo [Error] Version node not updated to !NEW_VERSION!, found !VERIFIED_VERSION! >> "%ERROR_LOG%"echo [Error] Version node not updated to !NEW_VERSION!, found !VERIFIED_VERSION!exit /b 1
)REM —— 4) 構建 ——
echo [Info] building csproj...
dotnet build "!CSProjPath!" -c Release >> "%SUCCESS_LOG%" 2>> "%ERROR_LOG%"
if errorlevel 1 (echo [Error] Build failed: !CSProjPath! >> "%ERROR_LOG%"echo [Error] Build failed: !CSProjPath!exit /b 1
) else (echo [Success] build successed
)
echo [Info] end of csproj construction...REM —— 5) 打包 ——
echo [Info] package in csproj...
if not exist "%~dp0nupkgs" mkdir "%~dp0nupkgs"
dotnet pack "!CSProjPath!" -c Release --output "%~dp0nupkgs" -p:Description="Automatically generate nuget packages" >> "%SUCCESS_LOG%" 2>> "%ERROR_LOG%"
if errorlevel 1 (echo [Error] Pack failed: !CSProjPath! >> "%ERROR_LOG%"echo [Error] Pack failed: !CSProjPath!exit /b 1
) else (echo [Success] pack successed
)
REM 記錄成功打包的 .nupkg 文件
for %%P in ("%~dp0nupkgs\%~n1.!NEW_VERSION!.nupkg") do (if exist "%%P" (echo [Success] Pack succeeded: %%P >> "%SUCCESS_LOG%"echo [Success] Pack succeeded: %%P) else (echo [Error] Pack file not found: %%P >> "%ERROR_LOG%"echo [Error] Pack file not found: %%Pexit /b 1)
)
echo [Info] end of packaging csproj...REM —— 6) 記錄 SVN 提交 ——
echo !CSProjPath!;!NEW_VERSION!>> "%SVN_COMMIT_LIST%"del "%TEMP_LOG%"
exit /b 0
? 建議將腳本保存為 auto_push_nuget.bat
,每次發布時雙擊運行即可。
📁 目錄結構推薦
/你的解決方案根目錄
│
├─ auto_push_nuget.bat
├─ nupkgs/
├─ success_log.txt
├─ error_log.txt
├─ svn_commit_list.txt
📌 注意事項
-
建議手動驗證
.csproj
路徑是否正確; -
確保
svn
命令行工具已正確安裝; -
dotnet
SDK 和私有 NuGet 源地址需提前配置; -
每次運行腳本前,請使用最新版源碼(SVN update);
-
批處理腳本推薦使用 Windows PowerShell 5+ 和 Windows Terminal。
🎉 效果展示
成功輸出如下所示:
[Success] build successed
[Success] pack successed
Pushing: Xxx.BI.WebSite.Core.624.05.14.1.nupkg
[Success] Push succeeded: Xxx.BI.WebSite.Core.624.05.14.1.nupkg
Committing: Xxx.BI.WebSite.Core.csproj
[Success] SVN commit succeeded: Xxx.BI.WebSite.Core.csproj
🧩 最后
本腳本適合用于 CI/CD 之外的 本地開發輔助打包上傳工具。你可以結合 git
、Jenkins
、TeamCity
等系統,將其進一步整合為流水線任務。
如果你有類似自動化構建打包需求,歡迎參考本腳本并結合你項目的實際路徑進行修改和優化。
如需該腳本的 .bat
文件,可留言或私信獲取完整代碼包。
如果你覺得這篇文章對你有幫助,歡迎 👍 點贊 / ? 收藏 / 📝 留言!