ค้นหาเว็บไซต์

รวมโฟลเดอร์ที่ซ้ำกันใน SharePoint กับ PowerShell


ผู้อ่านบล็อกนี้ติดต่อฉันหากฉันรู้วิธีรวมโฟลเดอร์ที่ซ้ำกันใน SharePoint Online ผู้ใช้ได้ทำงานในโฟลเดอร์ที่ซ้ำกันแล้ว ดังนั้นเราจึงไม่เพียงแต่จำเป็นต้องรวมโฟลเดอร์เท่านั้น แต่ยังต้องเก็บไฟล์ที่แก้ไขล่าสุดไว้ด้วย

ไฟล์หรือโฟลเดอร์ที่ซ้ำกันทั้งหมดมีรูปแบบเดียวกัน (1) ในชื่อไฟล์ ซึ่งทำให้การค้นหาไฟล์และโฟลเดอร์และรวมเข้ากับตำแหน่งเดิมเป็นเรื่องง่ายมาก

ในกรณีที่คุณสงสัยว่าเกิดอะไรขึ้น ผู้ใช้ลบไฟล์ในเครื่องโดยไม่หยุดการซิงค์ก่อน เนื่องจากมีไฟล์จำนวนมากที่ถูกลบ (มากกว่า 750,000 ไฟล์) พวกเขาจึงขอให้ Microsoft กู้คืนไฟล์ ซึ่งพวกเขาก็ทำไปแล้ว

เนื่องจากข้อผิดพลาดในการสื่อสาร งานการกู้คืนจึงดำเนินการเป็นสองส่วน ส่งผลให้มีไฟล์ที่ซ้ำกัน Microsoft ไม่สามารถช่วยเหลือในการล้างข้อมูลได้ ดังนั้นฉันจึงเข้ามา

นี่เป็นหนึ่งในสถานการณ์ที่โซลูชันสำรองข้อมูลของบริษัทอื่นสามารถช่วยชีวิตได้จริงๆ ฉันเคยเขียนเกี่ยวกับเรื่องนี้มาก่อน คุณต้องการโซลูชันการสำรองข้อมูลสำหรับ Microsoft 365 หรือไม่ ดังที่คุณเห็นในกรณีนี้ ไฟล์ต่างๆ สามารถกู้คืนได้ แต่การมีโซลูชันการสำรองข้อมูลจะทำให้การกู้คืนง่ายขึ้นมาก

การค้นหารายการที่ซ้ำกัน

การรวมโฟลเดอร์และไฟล์ที่ซ้ำกันจะใช้เวลา 2-3 ขั้นตอน โฟลเดอร์ที่ซ้ำกันไม่เพียงแต่อยู่ที่ระดับรูทของไลบรารีเอกสารเท่านั้น แต่ยังสามารถลึกได้ถึง 3 ระดับในโฟลเดอร์ย่อยอีกด้วย ดังนั้นเราจึงต้องทำงานแบบวนซ้ำในโฟลเดอร์ทั้งหมด โดยค้นหารายการที่มี (1) อยู่ในชื่อ

ฉันได้แบ่งสคริปต์ออกเป็นสองขั้นตอน แต่ละขั้นตอนแปลเป็นฟังก์ชันของตัวเอง:

  • ค้นหาไฟล์และโฟลเดอร์ที่ซ้ำกัน
  • สร้างเส้นทางเป้าหมาย (ตำแหน่งเดิม)
  • ย้ายเนื้อหาของโฟลเดอร์
  • เปรียบเทียบวันที่ของไฟล์
  • ย้ายรายการเดียว

เชื่อมต่อกับไซต์ SharePoint

ก่อนที่เราจะดำเนินการใดๆ เราจำเป็นต้องเชื่อมต่อกับไซต์ SharePoint ก่อน เราสามารถทำได้ง่ายๆ ด้วย PnPOnline และฉันใช้สวิตช์การเข้าสู่ระบบเว็บที่นี่ เพื่อให้เราสามารถใช้การเข้าสู่ระบบปกติของเรากับ 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
    }
}

ดังนั้นเราจึงเปรียบเทียบวันที่ และหากไฟล์ต้นฉบับใหม่กว่า เราก็จะลบไฟล์ที่ซ้ำกันออก ในฟังก์ชันการลบไฟล์ ฉันเพิ่มสวิตช์ -รีไซเคิล เพื่อให้ไฟล์ถูกย้ายไปยังถังรีไซเคิล คุณสามารถลบสวิตช์นี้ได้หากต้องการลบไฟล์อย่างถาวร

ห่อขึ้น

เป็นการดีที่ทราบว่าคุณอาจพบโฟลเดอร์ว่างในไลบรารีของคุณ ฉันใช้สคริปต์แยกต่างหากที่คุณสามารถเรียกใช้เพื่อล้างโฟลเดอร์ว่างได้ คุณสามารถค้นหาสคริปต์ฉบับเต็มได้ที่นี่บน GitHub ของฉัน

ทดสอบสคริปต์ประเภทนี้บนไซต์ทดสอบ SharePoint ด้วยไฟล์ทดสอบบางไฟล์เสมอ ตรวจสอบให้แน่ใจว่าคุณตั้งค่า whatif เป็น $true และตัวแปร บังคับ เป็น $false เมื่อคุณเริ่มต้น

หากคุณมีคำถามใด ๆ เพียงแค่แสดงความคิดเห็นด้านล่าง

บทความที่เกี่ยวข้อง