Fusionar carpetas duplicadas en SharePoint con PowerShell
Un lector de este blog se comunicó conmigo si conocía una manera de fusionar carpetas duplicadas en SharePoint Online. Los usuarios ya habían trabajado en las carpetas duplicadas, por lo que no solo necesitábamos fusionar las carpetas sino también conservar el último archivo modificado.
Todos los archivos o carpetas duplicados tenían el mismo patrón,
En caso de que se pregunte qué pasó; Un usuario eliminó archivos localmente sin detener primero la sincronización. Debido a la gran cantidad de archivos que fueron eliminados (más de 750.000), pidieron a Microsoft que los restaurara, lo cual hicieron.
Sólo debido a un error de comunicación, el trabajo de restauración se realizó en dos partes, lo que resultó en archivos duplicados. Microsoft no pudo ayudar con la limpieza, así que intervine.
Esta es una de esas situaciones en las que una solución de respaldo de terceros realmente sería un salvavidas. He escrito antes sobre ello; ¿Necesita una solución de respaldo para Microsoft 365? Como puede ver en este caso, los archivos se pueden restaurar, pero tener una solución de respaldo facilitaría mucho la restauración.
Encontrar los elementos duplicados
Fusionar las carpetas y archivos duplicados requerirá un par de pasos. Las carpetas duplicadas no solo se encuentran en el nivel raíz de la biblioteca de documentos, sino que también pueden tener 3 niveles de profundidad en una subcarpeta. Por lo tanto, debemos trabajar de forma recursiva en todas las carpetas, buscando elementos con (1) en el nombre.
He dividido el script en un par de pasos, cada uno de ellos traducido a su propia función:
- Encontrar archivos y carpetas duplicados
- Cree la ruta de destino (la ubicación original)
- Mover el contenido de la carpeta
- Comparar fechas de archivos
- Mover un solo elemento
Conéctese al sitio de SharePoint
Antes de que podamos hacer algo, debemos conectarnos al sitio de SharePoint. Podemos hacer esto simplemente con PnPOnline, y estoy usando el interruptor de inicio de sesión web aquí para que podamos usar nuestro inicio de sesión normal con MFA aquí.
Al final del artículo, mostraré el guión completo.
# SharePoint url
$sharepointUrl = 'https://lazyadmin.sharepoint.com/'
# Site
$site = 'sites/lab01'
# Library name
$libraryName = 'Duplicates'
# Login
$url = $sharepointUrl+ '/' + $site
Connect-PnPOnline -Url $url -UseWebLoginEncontrar los archivos duplicados
Como también necesitamos procesar las subcarpetas, el truco aquí es revisar las carpetas de forma recursiva. Usamos la función Get-PnPFolderItem aquí, pero luego de forma recursiva. La función se basa en un script de Josh Einstein que puedes encontrar aquí.
# 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
}
}
}Entonces, para cada elemento, verificamos si es una carpeta. Cuando es una carpeta, verificamos el nombre de la carpeta si contiene (1). Si es así, estamos moviendo el contenido de la carpeta, si no, “abrimos” la carpeta y revisamos el contenido.
Si no es una carpeta, sino un archivo, nuevamente verificamos el nombre. Si contiene un (1), moveremos el archivo; de lo contrario, no haremos nada.
Creando la ruta de destino
Si encontramos un elemento duplicado, debemos crear la ruta original a partir de la ruta del elemento. También debemos verificar si la carpeta original existe y, en caso contrario, volver a crearla.
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)', '')
}
}Lo que verás en cada función es que estoy usando la variable $whatIf. Al comienzo del script, declaré esta variable. Esto me permite probar el script sin mover ni crear ningún archivo o carpeta.
La mayoría de las funciones PnP admiten el modificador WhatIf, pero, por ejemplo, Resolve-PnPFolder no lo admite. De esta manera puedo simplemente probarlo escribiendo en la consola lo que haría el script.
La función create-targetPath recreará la ruta, comprobará si la carpeta existe y, en caso contrario, la recreará con la función Resolve-PnPfolder.
Mover carpeta y subcarpetas con PowerShell
Cuando encontramos una carpeta duplicada, queremos mover la carpeta y todo el contenido (incluidas las subcarpetas) a la ubicación original. Para cada subcarpeta, también debemos crear una ruta de destino.
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
}
}
}Comparar fechas de archivos en SharePoint Online y mover los archivos
Ahora llegamos a la parte de la que se trata: mover los archivos reales. Antes de que podamos mover el archivo, debemos verificar si el archivo existe en la ubicación original. Si es así, necesitamos comparar las fechas del archivo; en este caso queremos conservar el último archivo modificado.
# 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"
}
}
}
}Agregué un contador aquí que contará la cantidad de archivos que se mueven. Cuando vaya a probar un script como este, es posible que solo desee mover un par de archivos primero y luego verificar los resultados antes de continuar.
# 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
}
}Entonces comparamos las fechas y, si el archivo original es más nuevo, eliminamos el archivo duplicado. En la función de eliminar archivo, agregué el modificador -Reciclar para que nuestro archivo se mueva a la papelera de reciclaje. Puede eliminar este interruptor si desea eliminar los archivos permanentemente.
Concluyendo
Es bueno saber que es posible que termines con algunas carpetas vacías en tu biblioteca. He utilizado un script independiente que puedes ejecutar para limpiar las carpetas vacías. Puedes encontrar el script completo aquí en mi GitHub.
Pruebe siempre este tipo de scripts en un sitio de prueba de SharePoint con algunos archivos de prueba. Asegúrate de establecer whatif en $true y la variable force en $false cuando comiences.
Si tiene alguna pregunta, simplemente deje un comentario a continuación.