SharePoint 文件已被修改错误 (Set-PnPListItem) 解决实战
2025-03-12 19:08:45
SharePoint "文件已被修改" 错误解决实战:Set-PnPListItem 报错处理
最近碰到一个棘手的问题,我的脚本在向 SharePoint 文档库添加文件并填充列信息时,时不时会报 "Error: The file XXXX has been modified by XXXXX on XXXXX." 这个错。 之前好好的,突然就不行了,很让人头疼。今天把我的排查和解决过程分享给大家。
一、 问题回放
我的脚本主要流程是这样的:
- 读取 Excel 文件。
- 根据 Excel 中的信息,向 SharePoint 文档库添加文件。
- 用 Excel 中的数据填充文档库中相应文件的列信息。
之前我用 Get-PnPFile
检查文件是否已存在,如果存在就用 Set-PnPListItem
更新它。问题就出在这个 Set-PnPListItem
上,报错非常随机, 而且在虚拟机环境中运行.
二、 为什么会这样?版本冲突!
仔细分析后,我发现问题根源在于 SharePoint 的版本控制机制 。
简单来说,SharePoint 会跟踪每个文件的修改。每次你编辑文件,SharePoint 都会创建一个新版本。如果你尝试更新的文件版本与服务器上的最新版本不一致,就会发生版本冲突,也就是我碰到的这个报错。
可能导致冲突的场景有很多,比如:
- 并发编辑: 多人同时编辑同一个文件。
- 网络延迟: 你的脚本在更新文件时,可能服务器上已经有了更新的版本。
- 脚本逻辑问题: 你的脚本可能在短时间内多次尝试更新同一个文件。
- 缓存: 客户端或者服务端可能存在缓存
尤其是在虚拟机环境,网络问题可能加剧.
三、 解决方法:各个击破
针对上面说的原因,我尝试了以下几种方法,最终解决了问题。
1. "先礼后兵":获取最新版本再更新
最直接的想法是,在更新文件之前,先确保拿到的是最新版本。
原理: 每次更新前,都先从服务器获取文件的最新信息,包括其版本号,然后再基于这个版本号进行更新。
代码示例:
# 假设 $item 是你要更新的列表项
$item = Get-PnPListItem -List "你的列表名" -Id 你的文件ID -Fields "ID","FileRef","FileLeafRef","Modified","Editor","Modified_x0020_By"
#获取server端的文件的相关信息
$ServerItem = Get-PnPProperty -ClientObject $item -Property File
try{
#获取server file version
$ServerFileVersions = Get-PnPProperty -ClientObject $ServerItem.Versions -Property *
}
catch
{
Write-Host "获取版本信息错误"
}
# 检查是否获取成功以及版本信息列表是否非空
if ($null -ne $ServerFileVersions -and $ServerFileVersions.Count -gt 0) {
# 使用VersionLabel获取最新版本的VersionLabel。注意:如果最新版本是minor version (比如0.2),这里也可能会产生冲突。
$LastVersionLabel = $ServerFileVersions[-1].VersionLabel
# 更新列表项
$item["你的列名"] = "你的新值"
Set-PnPListItem -List "你的列表名" -Identity $item.Id -Values @{"你的列名" = "你的新值"} -SystemUpdate # 或 -UpdateOverwriteVersion
$ctx = Get-PnPContext
#在SystemUpdate后,file Version会跳到下一个Major版本,所以要fetch才能更新Item的值。
$refreshedItem = Get-PnPListItem -List "你的列表名" -Identity $item.ID
$refreshedItem["File_x0020_Size"] = [Microsoft.SharePoint.Client.Utilities.Utility]::GetFileLength($ctx, $refreshedItem["FileRef"])
$refreshedItem.Update()
$ctx.ExecuteQuery()
Write-Host "更新文件大小"
}
else {
Write-Host "未能获取到文件的版本信息。"
}
安全建议:
使用-SystemUpdate
参数或者 -UpdateOverwriteVersion
. 请确认权限足够.
-SystemUpdate :更新不会更改Editor/Modified字段
-UpdateOverwriteVersion : 此方法可能会覆盖服务器上的最新更改。
根据需要,最好使用 -SystemUpdate
.如果使用了-SystemUpdate
,后面必须重新获取item后(例如$refreshedItem
), 才能继续更新.
2. "退一步海阔天空": 稍等片刻再重试
有时候,问题可能只是暂时的网络波动或者服务器繁忙。 这种情况下,可以尝试“等一会儿再试试”。
原理: 在捕获到特定错误时,暂停脚本执行一段时间,然后重试更新操作。
代码示例:
$retryCount = 3
$retryDelay = 5 # 秒
for ($i = 1; $i -le $retryCount; $i++) {
try {
Set-PnPListItem -List "你的列表名" -Identity $item.Id -Values @{"你的列名" = "你的新值"}
break # 成功就跳出循环
}
catch [Microsoft.SharePoint.Client.ServerException] {
if ($_.Message -like "*has been modified by*") {
Write-Host "尝试 $i: 遇到版本冲突,${retryDelay} 秒后重试..."
Start-Sleep -Seconds $retryDelay
}
else {
throw # 其他错误,直接抛出
}
}
}
原理:
通过Start-Sleep
延迟重试.
3. "釜底抽薪":避免并发修改
如果你的场景允许多人同时编辑文件,或者你的脚本可能在短时间内多次更新同一个文件,那就需要考虑避免并发修改。
原理:
- 使用文件锁定:SharePoint支持文件锁定,但在SharePoint Online 中,pnp Powershell中锁定不是强制性的.
- 单线程化操作:通过一定机制,保证一段时间只有一个实例操作此文件.
实现方案探讨 (代码略,根据实际情况选择):
-
队列机制: 将文件更新操作放入队列,逐个处理,确保同一时间只有一个操作在执行。
适用环境:对实时性要求不高的时候。 -
文件锁定 (仅供参考,SharePoint Online上作用可能不明显): 在更新文件前尝试锁定文件,更新完成后释放锁定。
#尝试CheckOut,如果不成功,可能是因为已经其他人checkout了 try{ Set-PnPFileCheckedOut -Url $item["FileRef"] -Force }catch { Write-Host "Check Out文件失败" }
4."版本回退" (慎用!):强制覆盖
如果实在不行,而且你确定 可以覆盖服务器上的最新版本,可以使用 -UpdateOverwriteVersion
参数。
原理: 强制更新列表项,忽略版本冲突。
代码示例:
Set-PnPListItem -List "你的列表名" -Identity $item.Id -Values @{"你的列名" = "你的新值"} -UpdateOverwriteVersion
** 警告:** 这个方法会直接覆盖服务器上的最新更改,可能导致数据丢失,请务必谨慎使用!
5.进阶:Event Receiver
如果有技术能力和环境允许,还可以尝试Event Receiver
原理: Event Receiver 是 SharePoint 中的一种事件处理机制,可以捕捉列表项的添加、更新、删除等事件,并执行自定义代码。 可以监听ItemUpdating
事件, 做一些更细腻的检查或者自定义的并发处理。
注意: Event Receiver 开发和部署稍微复杂,如果不是必须,先尝试上面其他简单方法。
这里不做示例。
四、总结
SharePoint 文件版本冲突问题, 往往是由多种因素引起的。 解决问题的关键是找到根本原因,然后选择合适的解决策略。
排查问题的思路可以按照网络环境->Sharepoint并发控制机制->脚本逻辑
来做逐步排查. 希望上面的方案对大家有所帮助!