为何命令行/PowerShell无法复制已打开PST文件快照,资源管理器却可行?
无法通过VSS快照复制正在使用的Outlook PST文件问题
我尝试自动备份本地Outlook归档文件(.pst),但用户可能正在运行Outlook并读写这些PST文件。原本想通过创建VSS卷影副本,从快照复制文件来解决,但测试失败了——手动用文件资源管理器复制正常,但用xcopy、robocopy或Copy-Item时都会报错“无法复制正在被访问的文件”。
以下是我编写的PowerShell代码:
#Data $SourceDir=$env:USERPROFILE+'\Documents\Outlook Files' #Location of PST files $TargetDir='\\NetworkLocation\Resource\Folder' $logFile='"'+$SourceDir+'\Outlook-backup.log"' if (-not (Test-Path -LiteralPath $SourceDir)){ throw "Source: `"$SourceDir`" does not exist" } if (-not (Test-Path -LiteralPath $TargetDir)){ throw "Target: `"$SourceDir`" does not exist" } $SourceDir = Get-Item -LiteralPath $SourceDir $TargetDir = Get-Item -LiteralPath $TargetDir #Create Volume Shadow Copy $s1 = (Get-WmiObject -List Win32_ShadowCopy).Create("C:\\", "ClientAccessible") try{ $s2 = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $s1.ShadowID } $d = $s2.DeviceObject + "\\" #Create symlink to the snapshot for easy access cmd /c mklink /d C:\shadowcopy "$d" #Find file location inside snapshot $null=$SourceDir.FullName -match "(?<=^[a-z]:\\).*$" $ShadowSource = "C:\shadowcopy\"+$Matches[0] if (-not (Test-Path -LiteralPath $ShadowSource)){ throw "Shadow source: `"$ShadowSource`" does not exist" } $ShadowSource = $ShadowSource | Get-Item #Copy file from snapshot robocopy.exe $ShadowSource.FullName $TargetDir.fullname /MIR /COPY:DAT /J /Z /COMPRESS /R:3 /W:5 /NP /UNILOG:$logFile #/L #ROBOCOPY FAILS TO COPY THE FILES } catch{ Write-Host "Error occurred $($_.ScriptStackTrace)" Write-Host $_ } finally{ #Delete snapshot (In two different ways just in case) "vssadmin delete shadows /Shadow=""$($s1.ShadowID.ToLower())"" /Quiet" | iex "vssadmin delete shadows /Shadow=""$($s2.ID.ToLower())"" /Quiet" | iex #Delete Junction cmd.exe /c "rmdir C:\shadowcopy\ & exit" }
robocopy针对每个PST文件记录的错误为:
ERROR 33 (0x00000021) [...]The process cannot access the file because another process has locked a portion of the file.
问题原因
VSS快照确实是只读的,但问题出在两个点:
- 工具的文件访问模式:文件资源管理器复制PST时会使用兼容Outlook锁的读取逻辑(比如跳过锁定区域、调用备份API),而robocopy/xcopy默认的读取模式会触发锁冲突——即使是快照,工具的逻辑仍会检测到原始文件的锁标记。
- 符号链接的路径解析问题:用
mklink /d创建的目录符号链接,可能让工具误判为访问原始文件而非快照,间接触发锁检测。
解决方案
方案1:修改robocopy参数并直接访问快照路径
去掉符号链接步骤,直接用VSS设备路径访问快照,同时给robocopy加上备份模式参数:
#Data $SourceDir=$env:USERPROFILE+'\Documents\Outlook Files' $TargetDir='\\NetworkLocation\Resource\Folder' $logFile="$SourceDir\Outlook-backup.log" if (-not (Test-Path -LiteralPath $SourceDir)){ throw "Source: `"$SourceDir`" does not exist" } if (-not (Test-Path -LiteralPath $TargetDir)){ throw "Target: `"$TargetDir`" does not exist" } $SourceDir = Get-Item -LiteralPath $SourceDir $TargetDir = Get-Item -LiteralPath $TargetDir #Create Volume Shadow Copy $s1 = (Get-WmiObject -List Win32_ShadowCopy).Create("C:\\", "ClientAccessible") try{ $s2 = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $s1.ShadowID } $shadowDevice = $s2.DeviceObject # 直接构造快照内的源路径(去掉C:\前缀,拼接设备路径) $shadowSourcePath = Join-Path -Path $shadowDevice -ChildPath $SourceDir.FullName.Substring(2) # 使用/B参数启用备份模式,绕过文件锁 robocopy.exe $shadowSourcePath $TargetDir.FullName /MIR /COPY:DAT /B /R:3 /W:5 /NP /UNILOG:$logFile } catch{ Write-Host "Error occurred $($_.ScriptStackTrace)" Write-Host $_ } finally{ # 仅执行一次快照删除即可 vssadmin delete shadows /Shadow="$($s1.ShadowID.ToLower())" /Quiet }
关键修改:
- 跳过符号链接,直接通过VSS设备路径访问快照文件
- 添加
/B参数:让robocopy使用Windows备份API,和资源管理器用同一套无锁读取逻辑
方案2:使用PowerShell原生备份类cmdlet
如果系统支持,可直接用Start-BitsTransfer的备份模式,自动处理VSS和锁问题:
$SourceDir = "$env:USERPROFILE\Documents\Outlook Files" $TargetDir = "\\NetworkLocation\Resource\Folder" $logFile = "$SourceDir\Outlook-backup.log" # 用BITS服务的备份模式传输,自动处理VSS和文件锁 Start-BitsTransfer -Source "$SourceDir\*.pst" -Destination $TargetDir -TransferType Backup -Priority High
额外注意事项
- 确保脚本运行用户拥有本地管理员/备份操作员权限,否则无法创建VSS快照和读取备份文件
- 不要重复执行快照删除命令,避免不必要的报错
- 部分Outlook版本对归档PST锁更严格,但VSS+备份模式的组合可以稳定处理
内容的提问来源于stack exchange,提问作者rovda
相关产品推荐
相关产品推荐

