For most of my migration projects I have used ShareGate very successfully as the primary migration tool. ShareGate has got quite excessive logging to get insights about the quality of the migration. But what if ShareGate logs that everything is fine, but when in fact it is not? To get better confidence about the quality of ShareGate (which is just fine in my opinion) I also developed a PowerShell script to check if all documents have been migrated.
The following screenshot shows how the script should be used. Replace the source and destination URLs to your situation. Also change the Documents list name if needed.
Next the username and password should be supplied to connect to the SharePoint online tenant. The current user credentials are being used to connect to the source on-premise tenant.
Screen output:
and csv output:
listCompare.ps1
1# Copyright (C) www.jurjan.info - All Rights Reserved (MIT License)
2
3param(
4 [Parameter(Mandatory = $False, Position = 1)] [string]$siteUrlOnPrem,
5 [Parameter(Mandatory = $False, Position = 2)] [string]$siteUrlOnline,
6 [Parameter(Mandatory = $False, Position = 3)] [string]$listTitle
7)
8
9Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
10
11Add-Type -Path "$PSScriptRoot\Microsoft.SharePoint.Client.dll"
12Add-Type -Path "$PSScriptRoot\Microsoft.SharePoint.Client.Runtime.dll"
13
14function getAllListItems($_ctx, $_listName, $_rowLimit) {
15 $list = $_ctx.Web.Lists.GetByTitle($_listName)
16 $_ctx.Load($list)
17
18 $query = New-Object Microsoft.SharePoint.Client.CamlQuery
19 $query.ViewXml = "<View Scope='RecursiveAll'>
20 <RowLimit>$_rowLimit</RowLimit>
21 </View>"
22
23 $items = @()
24 do {
25 $listItems = $list.getItems($query)
26 $_ctx.Load($listItems)
27 $_ctx.ExecuteQuery()
28 $query.ListItemCollectionPosition = $listItems.ListItemCollectionPosition
29
30 foreach ($item in $listItems) {
31 $items += $item
32 }
33 Write-Host "Getting next batch of $_rowLimit"
34 }
35 While ($null -ne $query.ListItemCollectionPosition)
36
37 return $items
38}
39
40# SharePoint Online (destination)
41$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrlOnline)
42$userCredentials = Get-Credential
43$userName = $userCredentials.UserName
44$securePassword = $userCredentials.Password
45$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($userName, $securePassword)
46$clientContext.Credentials = $credentials
47
48if (!$clientContext.ServerObjectIsNull.Value) {
49 Write-Host "Connected to SharePoint Online site: '$siteUrlOnline'" -ForegroundColor Green
50}
51
52$theitemsonline = getAllListItems $clientContext $listTitle 2000 # get listitems in batches of 2000 (needed for large lists). Adjust 2000 to your own needs.
53Write-Host $theitemsonline.Count "items found"
54$onlineFiles = @()
55foreach ($onlineitem in $theitemsonline) {
56 $onlineFiles += $onlineitem["FileLeafRef"]
57}
58
59Write-Host "Connected to on-prem site: $siteUrlOnPrem" -ForegroundColor Green
60
61$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrlOnPrem)
62#$clientContext.Credentials = Get-Credential # optional use other credentials than current user
63$currentWeb = $clientContext.Web
64
65$lists = $currentWeb.Lists
66$list = $lists.GetByTitle($listTitle)
67$theitemsonprem = getAllListItems $clientContext $listTitle 2000 # get listitems in batches of 2000 (needed for large lists). Adjust 2000 to your own needs.
68Write-Host $theitemsonprem.Count "items found"
69
70$onPremFiles = @()
71foreach ($onpremitem in $theitemsonprem) {
72 $onPremFiles += $onpremitem["FileLeafRef"]
73}
74
75$a = Get-Date
76$fileName = $a.Ticks
77$tasks = @()
78$i = 0
79
80$differences = Compare-Object $onPremFiles $onlineFiles
81Write-Host $differences.Count files are missing, starting export to csv file "'ListCompare_Export_$fileName.csv'" -ForegroundColor Red
82
83$differences | ForEach-Object {
84 $completed = ($i*100)/$differences.Count
85 Write-Progress -Activity "Export " -percentComplete $completed;
86 Write-Host $_.SideIndicator $_.InputObject -ForegroundColor DarkYellow
87
88 $inputObjectAsString = $_.InputObject
89
90 if ($_.SideIndicator -eq "<=") {
91 Write-Host " missing online: " -ForegroundColor DarkYellow -NoNewLine
92 Write-Host $_.SideIndicator $_.InputObject -ForegroundColor DarkYellow
93 $theitem = $theitemsonprem | Where-Object {$_["FileLeafRef"] -eq $inputObjectAsString}
94 }
95
96 if ($_.SideIndicator -eq "=>") {
97 Write-Host " missing on-prem: " -ForegroundColor DarkYellow -NoNewLine
98 Write-Host $_.SideIndicator $_.InputObject -ForegroundColor DarkYellow
99 $theitem = $theitemsonline | Where-Object {$_["FileLeafRef"] -eq $inputObjectAsString}
100 }
101
102 if ($theitem -ne $null) {
103 $o = new-object psobject
104 $o | Add-Member -MemberType noteproperty -Name SideIndicator -value $_.SideIndicator;
105 $o | Add-Member -MemberType noteproperty -Name inputObjectAsString -value $inputObjectAsString;
106 $o | Add-Member -MemberType noteproperty -Name FileRef -value $theitem["FileRef"];
107 $tasks += $o;
108 }
109 $i++
110}
111
112$tasks | export-csv ".\ListCompare_Export_$fileName.csv" -noTypeInformation;
This concludes my blog post about SharePoint migrations. For questions, remarks or suggestions feel free to drop me a note in the contact form on the left.
If this project help you reduce time to develop, you can give me a cup of coffee 😄