You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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\"
               '''
           }
       }
    }
}

原因分析

  1. 仅停止IIS站点不彻底:Stop-IISSite只是暂停站点的HTTP请求处理,对应的应用程序池进程(w3wp.exe)仍在后台运行,会锁定部署目录中的.dll等文件,导致复制失败。手动操作时你可能无意识等待了更久,或进程已自然释放,但Jenkins流水线连续执行,没有等待进程完全退出。
  2. 站点状态检查逻辑有漏洞:你用appcmd list site仅验证站点状态,但未检查应用程序池的运行状态——即使站点显示已停止,应用池进程可能还在占用文件。
  3. Windows环境下scp的兼容性问题:scp是跨平台工具,在Windows系统中处理文件锁的机制不如原生复制命令(如robocopy、xcopy)稳定,容易触发文件占用错误。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 11:45:04