任务计划程序运行PowerShell脚本Start-Sleep未执行问题
问题描述
我有一个名为XLStoXLSX.ps1的PowerShell脚本,通过批处理(.bat)文件在Windows任务计划程序中调用,调用命令如下:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File "E:\Conversion XLS to XLSX\XLStoXLSX.ps1" 1>E:\Conversion XLS to XLSX\logs\Logs.log 2>&1
该脚本在任务计划程序外手动运行时可正常工作,XLStoXLSX.ps1的完整内容如下:
$folderpath = "E:\One drive\HAJJAJ\SFTP\data\02_Hour\" $ConversionFolder = "E:\One drive\HAJJAJ\Conversion_Space\02_Hour\" $ProductionFolder = "E:\One drive\HAJJAJ\SFTP\data\02_Hour\" Add-Type -AssemblyName Microsoft.Office.Interop.Excel $xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlOpenXMLWorkbook write-host $xlFixedFormat $excel = New-Object -ComObject excel.application $excel.visible = $true $filetype ="*xls" Get-ChildItem -Path $folderpath -Include $filetype -recurse | ForEach-Object ` { $path = ($_.fullname).substring(0, ($_.FullName).lastindexOf(".")) $pathBackup = ($_.fullname).substring(0, ($_.FullName).lastindexOf("\")) $FileNameToMove = $path.Replace($folderpath,$ConversionFolder) $FileNameBackUp = $pathBackup.Replace($folderpath,$ConversionFolder)+"\Done\" $ProductionFileNameToMove = $path.Replace($folderpath,$ProductionFolder) $ProductionFileNameBackUp = $pathBackup.Replace($folderpath,$ProductionFolder)+"\Done\" "Converting $path" $FileNameBackUp += $(get-date -f yyyyMMddHHmmss) + ($_.Name -replace '\.xls', '.xlsx') $ProductionFileNameBackUp += $(get-date -f yyyyMMddHHmmss) + ($_.Name -replace '\.xls', '.xlsx') $workbook = $excel.workbooks.open($_.fullname) if($_.length/1MB -gt 50MB) { Start-Sleep -Seconds 400 }else { Start-Sleep -Seconds 20 } $FileNameToMove += ".xlsx" $ProductionFileNameToMove += ".xlsx" #moving file to backup file on Conversion_Space before Conversion Move-Item -Path $FileNameToMove -Destination $FileNameBackUp $workbook.saveas($FileNameToMove, $xlFixedFormat) $workbook.close() if($_.length/1MB -gt 50MB) { Start-Sleep -Seconds 50 }else { Start-Sleep -Seconds 20 } $oldFolder = $path.substring(0, $path.lastIndexOf("\")) write-host $oldFolder if(-not (test-path $oldFolder)) { new-item $oldFolder -type directory } move-item $_.fullname $oldFolder #moving file to backup file on Conversion_Space before Conversion Move-Item -Path $ProductionFileNameToMove -Destination $ProductionFileNameBackUp Move-Item -Path $FileNameToMove -Destination $ProductionFileNameToMove #moving the file to Production } $excel.Quit() $excel = $null [gc]::collect() [gc]::WaitForPendingFinalizers()
脚本可正常启动执行,但以下代码段未按预期运行:
if($_.length/1MB -gt 50MB) { Start-Sleep -Seconds 400 }else { Start-Sleep -Seconds 20 }
需要解决该问题,同时寻找更优的实现方式,为Excel文件打开加载预留充足等待时间。
问题根因
固定时长等待逻辑本身可靠性极低,再加任务计划程序的运行环境和手动运行的交互环境不一致,直接导致逻辑失效:
- 任务计划程序如果配置为「不管用户是否登录都运行」,会启动非交互式会话,不支持创建带GUI的Excel窗口,你设置的
$excel.visible = $true会导致Excel进程挂起,后续所有逻辑都会乱序。 - 按文件大小判断等待时长完全没有依据:带大量公式、数据透视表、外部链接的几MB小文件,加载耗时可能比纯数据的100MB文件还久,固定等20秒容易等不够导致后续操作报错,固定等400秒又会浪费大量时间。
- 脚本本身存在逻辑顺序错误:你在执行
SaveAs生成xlsx文件之前,就尝试移动还不存在的目标xlsx文件,手动运行时可能因为残留文件没触发报错,任务计划程序下运行目录干净就会直接抛错中断。
解决方案
第一步:修复基础运行环境配置
- 打开任务计划程序,找到对应任务,在「常规」选项卡勾选仅当用户登录时运行,Office Interop不支持非交互式无人值守运行,这个配置不改其他调整都没用。
- 修改Excel COM对象的初始化配置,禁止弹窗、隐藏窗口,避免无人值守时弹框卡死:
$excel = New-Object -ComObject excel.application $excel.Visible = $false $excel.DisplayAlerts = $false $excel.AskToUpdateLinks = $false
第二步:替换固定时长等待为状态轮询
删除原来按文件大小Sleep的代码,换成轮询Excel计算状态、文件就绪状态的逻辑,既不会等不够也不会浪费时间:
打开文件后的等待逻辑
替换原来打开Workbook后的Sleep代码:
$workbook = $excel.workbooks.open($_.fullname) # 轮询等待工作簿加载、计算完成,最长等待10分钟防止无限卡死 $maxWait = 600 $waitCount = 0 while ($excel.CalculationState -ne [Microsoft.Office.Interop.Excel.XlCalculationState]::xlDone -and $waitCount -lt $maxWait) { Start-Sleep -Seconds 2 $waitCount += 2 } # 超过50MB的大文件额外补10秒缓冲即可 if($_.Length/1MB -gt 50MB) { Start-Sleep -Seconds 10 }
保存文件后的等待逻辑
替换原来SaveAs后的Sleep代码,同时调整Move操作的顺序——必须等SaveAs执行完、文件落盘后再做移动备份:
$FileNameToMove += ".xlsx" $ProductionFileNameToMove += ".xlsx" $workbook.saveas($FileNameToMove, $xlFixedFormat) $workbook.close() # 释放Workbook COM对象,避免留僵尸进程 [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null $workbook = $null # 等待文件落盘完成再移动 while (-not (Test-Path $FileNameToMove) -or (Get-Item $FileNameToMove).IsReadOnly) { Start-Sleep -Seconds 1 } # 备份逻辑放到SaveAs完成之后 Move-Item -Path $FileNameToMove -Destination $FileNameBackUp
第三步:优化资源释放
脚本最后释放Excel进程的逻辑补充COM对象显式释放,避免后台残留僵尸Excel进程占内存:
$excel.Quit() [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null $excel = $null [gc]::collect() [gc]::WaitForPendingFinalizers()
更优实现建议
微软本身不推荐也不支持在服务端、无人值守场景下用Office Interop做自动化,稳定性差还容易出各种诡异问题。如果条件允许,建议替换为不需要安装Excel的PowerShell模块ImportExcel,直接通过OpenXML规范处理xls/xlsx文件,没有GUI依赖,在任务计划程序下运行稳定性高很多,也不需要写各种等待逻辑。
内容的提问来源于stack exchange,提问作者HAJJAJ
相关产品推荐
相关产品推荐

