返回

SharePoint 文件已被修改错误 (Set-PnPListItem) 解决实战

windows

SharePoint "文件已被修改" 错误解决实战:Set-PnPListItem 报错处理

最近碰到一个棘手的问题,我的脚本在向 SharePoint 文档库添加文件并填充列信息时,时不时会报 "Error: The file XXXX has been modified by XXXXX on XXXXX." 这个错。 之前好好的,突然就不行了,很让人头疼。今天把我的排查和解决过程分享给大家。

一、 问题回放

我的脚本主要流程是这样的:

  1. 读取 Excel 文件。
  2. 根据 Excel 中的信息,向 SharePoint 文档库添加文件。
  3. 用 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并发控制机制->脚本逻辑来做逐步排查. 希望上面的方案对大家有所帮助!