Combinar carpetas duplicadas en SharePoint con PowerShell

Un lector de este blog se acercó a mí si conocía una forma de combinar 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, (1), en su nombre de archivo.

Esto hizo que fuera muy fácil encontrar los archivos y carpetas y fusionarlos con la ubicación original.

En caso de que te estés preguntando qué pasó; Un usuario eliminó archivos localmente sin detener primero la sincronización.

Debido a la gran cantidad de archivos que se eliminaron (más de 750.000), le pidieron a Microsoft que lo restaurara, y así lo hicieron.

Solo 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 eso; ¿Necesita una solución de copia de seguridad para Microsoft 365? Como puede ver en este caso, los archivos se pueden restaurar, pero tener una solución de copia de seguridad facilitaría mucho la restauración.

Encontrar los elementos duplicados

Fusionar las carpetas y los archivos duplicados requerirá un par de pasos.

Las carpetas duplicadas no solo están 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 desglosado el script en un par de pasos, cada uno traducido a su propia función:

  • Encontrar los archivos y carpetas duplicados
  • Crear 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, necesitamos 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í.

AAl 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 -UseWebLogin

Encontrar los archivos duplicados

Debido a que 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 guión 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), entonces vamos a mover el archivo, de lo contrario no hacemos nada.

Crear la ruta de destino

Si encontramos un elemento duplicado, debemos crear la ruta original a partir de la ruta de los elementos.

También debemos verificar si la carpeta original existe y, si no, volver a crear la carpeta original.

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 son variables $whatIf. Al comienzo del script, he declarado esta variable.

Esto me permite probar el script sin tener que mover o crear ningún archivo o carpeta.

La mayoría de las funciones PnP admiten el conmutador WhatIf, pero, por ejemplo, Resolve-PnPFolder no lo admite.

Entonces, de esta manera, simplemente puedo probarlo escribiendo en la consola lo que haría el script.

Entonces, la función create-targetPath recreará la ruta y verificará si la carpeta existe y, si no, la volverá a crear con la función Resolve-PnPfolder.

Mover carpetas 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 necesitamos 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

Así que ahora llegamos a la parte de lo 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 de los archivos, queremos conservar el último archivo modificado en este caso.

# 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
    }
}

Así que comparamos las fechas y, si el archivo original es más reciente, eliminamos el archivo duplicado.

En la función de eliminación de archivos, agregué el interruptor -Reciclar para que el archivo se mueva a la papelera de reciclaje.

Puede eliminar este interruptor si desea eliminar los archivos de forma permanente.

Terminando

Es bueno saber que puede terminar con algunas carpetas vacías en su biblioteca. He usado un script separado que puede ejecutar para limpiar las carpetas vacías.

Puedes encontrar el guión completo. aquí en mi GitHub.

Pruebe siempre este tipo de secuencias de comandos en un sitio de prueba de SharePoint con algunos archivos de prueba.

Asegúrese de establecer whatif en $true y forzar en $false cuando comience.

Si tiene alguna pregunta, simplemente deje un comentario a continuación.

Otros artículos relacionados

Power Automate - Editor de expresiones y actualización de contenido dinámico

Power Automate – Editor de expresiones y actualización de contenido dinámico

Una de las cosas más molestas de Power Automate, el antiguo Microsoft Flow, es el editor de expresiones pequeño y ...
Leer Más
Net Use Delete - Cómo eliminar conexiones de red

Net Use Delete – Cómo eliminar conexiones de red

Cuando desee eliminar unidades de red de su computadora con Windows 10 u 11, puede usar el explorador. Otra opción ...
Leer Más
Migrar las unidades de inicio de los usuarios a OneDrive para empresas con PowerShel

Migrar las unidades de inicio de los usuarios a OneDrive para empresas con PowerShel

Tenemos Office 365 desde hace casi 3 años y una de las cosas en mi lista de tareas pendientes era ...
Leer Más
Revisión de la copia de seguridad empresarial de Hornetsecurity 365 Total Protection

Revisión de la copia de seguridad empresarial de Hornetsecurity 365 Total Protection

#patrocinado A medida que las amenazas de correo electrónico avanzadas se vuelven más sofisticadas, la seguridad del correo electrónico y ...
Leer Más
Deshabilitar Autoguardado para Office 365

Deshabilitar Autoguardado para Office 365

Este artículo describe cómo puede deshabilitar el guardado automático en Office 365 para cada archivo con el uso de PowerShell ...
Leer Más
Forma rápida de encontrar la carpeta de inicio en Windows 10 y 11

Forma rápida de encontrar la carpeta de inicio en Windows 10 y 11

La carpeta de inicio de Windows 10 contiene accesos directos a aplicaciones que se inician después de iniciar sesión en ...
Leer Más

Deja un comentario