在大型项目中,我们常常需要定期打包多个 .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=%%a
set MM=%%b
set 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=%%a
set MM=%%b
set 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+=1
echo [Error] Push failed: !NUPKG_PATH! >> "%ERROR_LOG%"
echo [Error] Push failed: !NUPKG_PATH!
) else (
set /a SUCCESS_COUNT+=1
echo [Success] Push succeeded: !NUPKG_PATH! >> "%SUCCESS_LOG%"
echo [Success] Push succeeded: !NUPKG_PATH!
)
) else (
set /a FAIL_COUNT+=1
echo [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: %%a
echo Committing: %%a >> "%SUCCESS_LOG%"
svn commit "%%a" -m "Auto update Version to %%b" >> "%SUCCESS_LOG%" 2>&1
if 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%"
pause
echo ===== 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!
popd
exit /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%"
popd
exit /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 + 1
echo [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: %%P
exit /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
文件,可留言或私信获取完整代码包。
如果你觉得这篇文章对你有帮助,欢迎 👍 点赞 / ⭐ 收藏 / 📝 留言!