PowerShell ile SharePoint'teki Yinelenen klasörleri birleştirme


Bu blogun bir okuyucusu, SharePoint Online'da yinelenen klasörleri birleştirmenin bir yolunu bilip bilmediğimi bana sordu. Kullanıcılar zaten kopya klasörlerde çalışmıştı, bu nedenle yalnızca klasörleri birleştirmemiz gerekmedi, aynı zamanda son değiştirilen dosyayı da korumamız gerekiyordu.

Tüm yinelenen dosya veya klasörlerin dosya adlarında aynı model ((1)) vardı. Bu, dosya ve klasörleri bulmayı ve bunları orijinal konumlarıyla birleştirmeyi gerçekten kolaylaştırdı.

Ne olduğunu merak ediyorsanız; Bir kullanıcı, önce senkronizasyonu durdurmadan dosyaları yerel olarak sildi. Silinen çok sayıda dosya (750.000'den fazla) nedeniyle, Microsoft'tan dosyayı geri yüklemesini istediler ve bunu da yaptılar.

Yalnızca bir iletişim hatası nedeniyle geri yükleme işi iki parça halinde yapıldı ve bu da dosyaların kopyalanmasına neden oldu. Microsoft temizleme konusunda yardımcı olamadı, ben de devreye girdim.

Bu, üçüncü taraf bir yedekleme çözümünün gerçekten cankurtaran olabileceği durumlardan biridir. Bu konuda daha önce yazmıştım; Microsoft 365 için bir yedekleme çözümüne mi ihtiyacınız var? Bu örnekte de görebileceğiniz gibi dosyalar geri yüklenebilir ancak bir yedekleme çözümüne sahip olmak geri yüklemeyi çok daha kolay hale getirecektir.

Yinelenen öğeleri bulma

Yinelenen klasörleri ve dosyaları birleştirmek birkaç adım alacaktır. Yinelenen klasörler yalnızca belge kitaplığının kök düzeyinde değil, aynı zamanda bir alt klasörde 3 düzey derinlikte de olabilir. Bu nedenle, adında (1) olan öğeleri arayarak tüm klasörler üzerinde yinelemeli olarak çalışmamız gerekir.

Senaryoyu birkaç adıma ayırdım, her biri kendi işlevine çevrildi:

  • Yinelenen dosya ve klasörleri bulma
  • Hedef yolu oluşturun (orijinal konum)
  • Klasör içeriğini taşıma
  • Dosya tarihlerini karşılaştır
  • Tek bir öğeyi taşıma

SharePoint sitesine bağlanın

Herhangi bir şey yapmadan önce SharePoint sitesine bağlanmamız gerekiyor. Bunu PnPOnline ile basitçe yapabiliriz ve burada MFA ile normal girişimizi kullanabilmemiz için web giriş anahtarını kullanıyorum.

Makalenin sonunda senaryonun tamamını göstereceğim.

# SharePoint url
$sharepointUrl = 'https://lazyadmin.sharepoint.com/'

# Site
$site = 'sites/lab01'

# Library name
$libraryName = 'Duplicates'

# Login 
$url = $sharepointUrl+ '/' + $site
Connect-PnPOnline -Url $url -UseWebLogin

Yinelenen dosyaları bulma

Alt klasörleri de işlememiz gerektiğinden buradaki püf noktası, klasörler arasında yinelemeli olarak ilerlemektir. Burada Get-PnPFolderItem işlevini kullanıyoruz, ancak daha sonra yinelemeli bir şekilde. İşlev, Josh Einstein'ın burada bulabileceğiniz bir komut dosyasına dayanmaktadır.

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

Yani her öğe için bunun bir klasör olup olmadığını kontrol ediyoruz. Bir klasör olduğunda, klasör adının (1) içerip içermediğini kontrol ederiz. Eğer öyleyse, klasör içeriğini taşıyoruz, değilse, klasörü “açıyoruz” ve içeriğin üzerinden geçiyoruz.

Bu bir klasör değil, bir dosya ise, adı tekrar kontrol ederiz. Eğer (1) içeriyorsa dosyayı taşıyacağız, aksi takdirde hiçbir şey yapmayız.

Hedef yolu oluşturma

Yinelenen bir öğe bulursak, öğenin yolundan orijinal yolu oluşturmamız gerekir. Ayrıca orijinal klasörün var olup olmadığını kontrol etmemiz ve değilse orijinal klasörü yeniden oluşturmamız gerekir.

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)', '')
    }
}

Her fonksiyonda göreceğiniz şey, kullandığım $whatIf değişkenidir. Komut dosyasının başında bu değişkeni tanımladım. Bu, aslında herhangi bir dosya veya klasörü taşımadan veya oluşturmadan betiği test etmeme olanak tanıyor.

Çoğu PnP işlevi WhatIf anahtarını destekler, ancak örneğin Resolve-PnPFolder bunu desteklemez. Böylece komut dosyasının ne yapacağını konsola yazarak basitçe test edebilirim.

create-targetPath işlevi yolu yeniden oluşturur, klasörün var olup olmadığını kontrol eder ve yoksa onu Resolve-PnPfolder işleviyle yeniden oluşturur.

PowerShell ile klasörü ve alt klasörleri taşıma

Yinelenen bir klasör bulduğumuzda, klasörü ve tüm içeriği (alt klasörler dahil) orijinal konumuna taşımak isteriz. Her alt klasör için de bir hedef yol oluşturmamız gerekiyor.

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'da dosya tarihlerini karşılaştırma ve dosyaları taşıma

Şimdi asıl dosyaların taşınmasıyla ilgili kısma geldik. Dosyayı taşımadan önce dosyanın orijinal konumunda olup olmadığını kontrol etmemiz gerekir. Eğer öyleyse, dosya tarihlerini karşılaştırmamız gerekiyor, bu durumda en son değiştirilen dosyayı saklamak istiyoruz.

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

Buraya taşınan dosya sayısını sayacak bir sayaç ekledim. Bunun gibi bir betiği test edeceğiniz zaman, önce sadece birkaç dosyayı taşımak ve devam etmeden önce sonuçları kontrol etmek isteyebilirsiniz.

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

Böylece tarihleri karşılaştırırız ve orijinal dosya daha yeniyse kopya dosyayı kaldırırız. Dosyayı kaldır işlevinde -Recycle anahtarını ekledim, böylece dosyamız geri dönüşüm kutusuna taşınacak. Dosyaları kalıcı olarak silmek istiyorsanız bu anahtarı kaldırabilirsiniz.

Kapanıyor

Kitaplığınızda bazı boş klasörlerle karşılaşabileceğinizi bilmek güzel. Boş klasörleri temizlemek için çalıştırabileceğiniz ayrı bir komut dosyası kullandım. Komut dosyasının tamamını GitHub'ımda bulabilirsiniz.

Bu tür komut dosyalarını her zaman bir test SharePoint sitesinde bazı test dosyalarıyla test edin. Başlarken whatif değerini $true olarak ve force değişkenini $false olarak ayarladığınızdan emin olun.

Herhangi bir sorunuz varsa aşağıya bir yorum bırakmanız yeterli.

İlgili makaleler