Jenkins部署IIS上.NET 8.0 API时遇文件占用错误的解决咨询
问题
我有一个托管在Microsoft Windows Server 2016系统的IIS服务器上的.NET 8.0 API。运行Jenkins任务时,构建阶段后通过bat命令scp -r G:/Source/* "G:/Destination/"部署文件,会抛出错误:The process cannot access the file because it is being used by another process.,但手动复制文件到部署目录却能成功。请问这是什么原因,该如何解决?
我的Jenkins流水线
pipeline { agent { label 'windows02' } stages { stage('delete') { steps { bat 'rmdir /s /q | echo y' } } stage('clone') { steps { git branch: 'UAT', credentialsId: 'JENKINS_TOKEN_GIT', url: 'https://gitlink.com' } } stage('Stop_website') { steps { bat ''' C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe Stop-IISSite -Name \"website\" -Confirm:$false ''' } } stage('build') { steps { bat 'msbuild PROJECT.sln /t:restore /t:Rebuild /p:outdir=\"G:/Source/\" /p:Platform=\"Any CPU\"' } } stage ('Check website status') { steps { script { def commandOutput = bat(script: 'C:\\Windows\\System32\\inetsrv\\appcmd.exe list site website', returnStdout: true).trim() def searchString = 'SITE \"website\" (id:22,bindings:http/110.456.741.1.2:50060:,state:Stopped)' env.CONDITION_MET = commandOutput.contains(searchString) ? 'true' : 'false' if (commandOutput.contains(searchString)) { echo \"website successfully stopped \" } else { error \"website not stopped\" } } } } stage('Deploy') { steps { bat 'scp -r G:/Source/* \"G:/Destination/\"' } } stage('Start_website') { steps { bat ''' C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe Start-IISSite -Name \"website\" ''' } } } }
原因分析
- 仅停止IIS站点不彻底:
Stop-IISSite只是暂停站点的HTTP请求处理,对应的应用程序池进程(w3wp.exe)仍在后台运行,会锁定部署目录中的.dll等文件,导致复制失败。手动操作时你可能无意识等待了更久,或进程已自然释放,但Jenkins流水线连续执行,没有等待进程完全退出。 - 站点状态检查逻辑有漏洞:你用
appcmd list site仅验证站点状态,但未检查应用程序池的运行状态——即使站点显示已停止,应用池进程可能还在占用文件。 - Windows环境下scp的兼容性问题:scp是跨平台工具,在Windows系统中处理文件锁的机制不如原生复制命令(如
robocopy、xcopy)稳定,容易触发文件占用错误。 - delete阶段命令无效:
rmdir /s /q | echo y没有指定目标目录,无法清理部署目录的旧文件,旧文件可能被残留进程占用。
解决方法
1. 停止站点+回收应用程序池+等待进程释放
修改Stop_website阶段,不仅停止站点,还要停止并回收应用程序池,同时添加等待时间确保进程完全退出:
C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe Stop-IISSite -Name \"website\" -Confirm:$false; Stop-WebAppPool -Name \"你的应用程序池名称\"; Start-Sleep -Seconds 15
注意:替换
你的应用程序池名称为实际的IIS应用程序池名称,等待时间可根据应用大小调整。
2. 完善状态检查逻辑,同时验证应用池状态
更新Check website status阶段,同时检查站点和应用程序池的状态:
script { def siteOutput = bat(script: 'C:\\Windows\\System32\\inetsrv\\appcmd.exe list site website', returnStdout: true).trim() def poolOutput = bat(script: 'C:\\Windows\\System32\\inetsrv\\appcmd.exe list apppool \"你的应用程序池名称\"', returnStdout: true).trim() def siteStopped = siteOutput.contains('state:Stopped') def poolStopped = poolOutput.contains('state:Stopped') if (siteStopped && poolStopped) { echo \"网站和应用程序池已成功停止\" } else { error \"网站或应用程序池未停止,部署终止\" } }
3. 替换scp为Windows原生复制命令
用robocopy(推荐,更稳定)替代scp,它支持重试、批量复制,能更好处理文件锁问题:
robocopy G:/Source/ G:/Destination/ /E /COPYALL /R:5 /W:3
参数说明:
/E:复制所有子目录(包括空目录)/COPYALL:复制所有文件属性(权限、时间戳等)/R:5:复制失败时重试5次/W:3:重试间隔3秒
如果需要更简单的命令,也可以用xcopy:
xcopy G:/Source/* G:/Destination/ /E /H /Y
参数说明:
/E:复制子目录和空目录/H:复制隐藏和系统文件/Y:覆盖文件时不提示
4. 修复delete阶段的清理命令
修改delete阶段,明确清理部署目录的旧文件:
rmdir /s /q G:/Destination/ || echo y
这样能确保部署前目标目录是空的,避免旧文件被占用。
5. 排查其他占用进程(可选)
如果以上方法仍无效,可添加一个阶段检查部署目录的文件占用情况,使用Sysinternals的handle.exe工具:
handle.exe G:/Destination/
如果输出显示有进程占用,可在流水线中添加命令杀死对应PID:
taskkill /F /PID 进程ID
内容的提问来源于stack exchange,提问作者Ghanshyam Baravaliya
相关产品推荐
相关产品推荐

