使用 PowerShell 合并 SharePoint 中的重复文件夹
如果我知道在 SharePoint Online 中合并重复文件夹的方法,该博客的一位读者联系了我。用户已经在重复的文件夹中工作过,因此我们不仅需要合并文件夹,还需要保留最后修改的文件。
所有重复的文件或文件夹的文件名都具有相同的模式 (1)。因此,这使得查找文件和文件夹并将它们与原始位置合并变得非常容易。
如果您想知道发生了什么;用户在未先停止同步的情况下在本地删除了文件。由于删除的文件数量很大(超过 750,000 个),他们要求 Microsoft 恢复,他们也这样做了。
仅由于通信错误,恢复作业分两部分完成,导致文件重复。 Microsoft 无法协助清理,所以我介入了。
在这种情况下,第三方备份解决方案确实可以成为救星。我之前写过相关文章;您是否需要 Microsoft 365 的备份解决方案。正如您在本例中所看到的,文件可以恢复,但拥有备份解决方案将使恢复变得更加容易。
查找重复项
合并重复的文件夹和文件将需要几个步骤。重复文件夹不仅位于文档库的根级别,还可以位于子文件夹的 3 层深处。因此,我们需要递归地遍历所有文件夹,查找名称中带有 (1) 的项目。
我已将脚本分解为几个步骤,每个步骤都转换为自己的函数:
- 查找重复的文件和文件夹
- 创建目标路径(原始位置)
- 移动文件夹内容
- 比较文件日期
- 移动单个项目
连接到 SharePoint 网站
在我们执行任何操作之前,我们需要连接到 SharePoint 站点。我们可以使用 PnPOnline 简单地完成此操作,并且我在这里使用 Web 登录开关,因此我们可以在这里使用 MFA 进行正常登录。
在文章的最后,我将展示完整的脚本。
# SharePoint url
$sharepointUrl = 'https://lazyadmin.sharepoint.com/'
# Site
$site = 'sites/lab01'
# Library name
$libraryName = 'Duplicates'
# Login
$url = $sharepointUrl+ '/' + $site
Connect-PnPOnline -Url $url -UseWebLogin查找重复文件
因为我们还需要处理子文件夹,所以这里的技巧是递归地遍历文件夹。我们在这里使用 Get-PnPFolderItem 函数,但随后以递归方式使用。该函数基于 Josh Einstein 的脚本,您可以在此处找到该脚本。
# Recursively calls Get-PnpFolderItem for a given Document Library
# Based on: https://gist.github.com/josheinstein/3ace0c9f8e25d07583ceb57d13f71b2e
Function Get-PnpFolderItemRecursively($FolderSiteRelativeUrl) {
# Get all items
$items = @(Get-PnPFolderItem -FolderSiteRelativeUrl $FolderSiteRelativeUrl)
Foreach ($item in $items) {
# Strip the Site URL of the item path because Get-PnpFolderItem wants it
# to be relative to the site, not an absolute path.
$itemPath = $item.ServerRelativeUrl -replace "^$(([Uri]$item.Context.Url).AbsolutePath)/",''
# Check if the item is a folder
If ($item -is [Microsoft.SharePoint.Client.Folder])
{
# Check if the folder name contains (1) on the end
# If - if the folder name contains a (1) on the end, then it's a duplicate folder that we need to move or merge
# Else - if the folder doesn't contain (1), then we open the folder and search through the next level
if ($item.name -like "*(1)")
{
# Duplicate folder found
Write-Host " - Duplicatie folder found: " $itemPath -ForegroundColor Yellow
# Move the content of the folder to the original location
Move-FolderItemsRecursively($itemPath)
}
else
{
# Is doesn't contain (1), but it's a folder, search through the next level by recursing into this function.
Get-PnpFolderItemRecursively $itemPath
}
}
else
{
# Item is a file
# Check if items name contains a (1), if true, move the file
if ($item.name -like "*(1)")
{
$targetPath = Create-TargetPath -itemPath $itemPath -targetPath $item["FileRef"].trim("*(1)") -relativePath $relativePath
Write-Host $newTargetPath;
Move-CustomItem -SiteRelativeUrl $itemPath -targetPath $targetPath -item $item
}
# Else skip to next
}
}
}因此,对于每个项目,我们都会检查它是否是一个文件夹。当它是一个文件夹时,我们检查文件夹名称是否包含(1)。如果是,我们将移动文件夹内容,如果不是,我们“打开”文件夹并浏览内容。
如果它不是文件夹,而是文件,那么我们再次检查名称。如果它包含 (1),那么我们将移动该文件,否则我们什么都不做。
创建目标路径
如果我们发现重复的项目,我们需要从该项目的路径创建原始路径。我们还需要检查原始文件夹是否存在,如果不存在则重新创建原始文件夹。
Function Create-TargetPath {
[CmdletBinding()]
param(
[parameter (Mandatory=$true)]
$itemPath,
[parameter (Mandatory=$true)]
$item,
[parameter (Mandatory=$false)]
$relativePath
)
process
{
# Build new path
$path = $itemPath.replace($item.name,'')
$targetPath = "/" + $site + "/" + $path + $item.name
if ($whatIf -ne $true)
{
# Check if target folder exists, create if necessary
Write-host ' - Check if target folder exists' $path.replace('(1)', '') -BackgroundColor DarkMagenta;
$result = Resolve-PnPFolder -SiteRelativePath $path.replace('(1)', '') -ErrorAction SilentlyContinue
}
else{
Write-host ' - Create target folder if it does not exists' $path.replace('(1)', '') -BackgroundColor DarkMagenta;
}
Write-Output $targetPath.replace('(1)', '')
}
}您将在每个函数中看到我正在使用变量 $whatIf。在脚本的开头,我声明了这个变量。这使我可以测试脚本,而无需实际移动或创建任何文件或文件夹。
大多数 PnP 函数确实支持 WhatIf 开关,但例如 Resolve-PnPFolder 不支持它。这样我就可以通过向控制台写入脚本将执行的操作来简单地测试它。
create-targetPath 函数将重新创建路径,检查文件夹是否存在,如果不存在,则使用 Resolve-PnPfolder 函数重新创建它。
使用 PowerShell 移动文件夹和子文件夹
当我们找到重复的文件夹时,我们希望将该文件夹和所有内容(包括子文件夹)移动到原始位置。对于每个子文件夹,我们还需要创建一个目标路径。
Function Move-FolderItemsRecursively($FolderSiteRelativeUrl) {
# Get all items in this sub folder
$items = @(Get-PnPFolderItem -FolderSiteRelativeUrl $FolderSiteRelativeUrl)
foreach ($item in $items) {
# Strip the Site URL off the item path, because Get-PnpFolderItem wants it
# to be relative to the site, not an absolute path.
$itemPath = $item.ServerRelativeUrl -replace "^$(([Uri]$item.Context.Url).AbsolutePath)/",''
# If this is a directory, recurse into this function.
# Otherwise, build target path and move file
if ($item -is [Microsoft.SharePoint.Client.Folder])
{
Move-FolderItemsRecursively $itemPath
}
else
{
$targetPath = Create-TargetPath -itemPath $itemPath -item $item
Move-CustomItem -SiteRelativeUrl $itemPath -targetPath $targetPath -item $item
}
}
}比较 SharePoint Online 中的文件日期并移动文件
现在我们来到了最重要的部分,移动实际文件。在移动文件之前,我们需要检查文件是否存在于原始位置。如果是这样,我们需要比较文件日期,在这种情况下我们希望保留最后修改的文件。
# Move file to original folder
Function Move-CustomItem {
[CmdletBinding()]
param(
[parameter (Mandatory=$true)]
$siteRelativeUrl,
[parameter (Mandatory=$true)]
$targetPath,
[parameter (Mandatory=$true)]
$item
)
process
{
$moveFile = Compare-FileDates -sourceFilePath $siteRelativeUrl -targetFilePath $targetPath;
$global:moveLimitCounter++
if ($moveFile -eq $true)
{
if ($moveLimitCounter -eq $moveLimit)
{
Write-Warning 'Move limit reached'
exit;
}
if ($whatIf -ne $true)
{
# Move the file
Write-host ' - Move item to' $targetPath -BackgroundColor DarkYellow;
Move-PnPFile -SiteRelativeUrl $siteRelativeUrl -TargetUrl $targetPath -OverwriteIfAlreadyExists -Force:$force
Write-Host "`r`n"
}
else
{
Write-host ' - Move file from' $siteRelativeUrl -BackgroundColor DarkCyan
Write-host ' to' $targetPath -BackgroundColor DarkCyan
Write-Host "`r`n"
}
}
}
}我在这里添加了一个计数器,用于计算移动的文件数量。当您要测试这样的脚本时,您可能只想先移动几个文件,然后在继续之前检查结果。
# Check if the file already exists in the target location
# If the file exists, we need to compare the dates to keep the latest files
Function Compare-FileDates ()
{
[CmdletBinding()]
param(
[parameter (Mandatory=$true)]
$targetFilePath,
[parameter (Mandatory=$true)]
$sourceFilePath
)
$targetFileExists = Get-PnPFile -Url $targetFilePath -ErrorAction SilentlyContinue
If($targetFileExists)
{
$sourceFile = Get-PnPFile -Url $sourceFilePath -AsListItem
$targetFile = Get-PnPFile -Url $targetFilePath -AsListItem
$sourceFileDate = Get-date $sourceFile['Modified']
$targetFileDate = Get-date $targetFile['Modified']
write-host ' - Comparing files dates: duplicate file: '$sourceFileDate 'original file: '$targetFileDate
# Check if the source file is newer then the target file
If ($sourceFile['Modified'] -gt $targetFile['Modified'])
{
write-host ' - Duplicate file is newer, move the file' -BackgroundColor DarkGreen
write-output $true
}
else
{
# Remove file
if ($whatIf -ne $true)
{
Write-host ' - Target file is newer. Removing duplicate file' -BackgroundColor DarkRed
Write-Host "`r`n"
Remove-PnPFile -SiteRelativeUrl $sourceFilePath -Recycle -Force:$force
}
else
{
Write-Host 'Remove file' $sourceFilePath -ForegroundColor Red
Write-Host "`r`n"
}
write-output $false
}
}
else
{
# Target file doesn't exists
Write-host ' - Target file does not exist' -BackgroundColor DarkGreen
Write-Output $true
}
}因此,我们比较日期,如果原始文件较新,则我们删除重复的文件。在删除文件功能中,我添加了开关-Recycle,这样我们的文件就会被移动到回收站。如果您想永久删除文件,可以删除此开关。
总结
很高兴知道您的库中可能会出现一些空文件夹。我使用了一个单独的脚本,您可以运行它来清理空文件夹。您可以在我的 GitHub 上找到完整的脚本。
始终使用一些测试文件在测试 SharePoint 网站上测试这些类型的脚本。开始时,请确保将 whatif 设置为 $true,将变量 force 设置为 $false。
如果您有任何疑问,请在下面发表评论。