Excellent — here’s Section 9 of your full article, expanding it into a complete, production-style migration guide.
This section shows how to run FileMigrationServiceV2 inside a console app with a progress bar, job summary, and estimated completion time.


Running the Migration with Progress Bar and Job Summary (Console App Example)

Overview

This section integrates the migration logic into a simple C# console application, allowing you to:

  • Display a real-time progress bar
  • Show file-by-file migration updates
  • Estimate total time and remaining duration
  • Output a summary report once migration is finished

This version is perfect for running migrations from your local machine or as a scheduled job in an Azure VM.


1️⃣ Program Structure

We’ll create a simple console entry point (Program.cs) that:

  1. Authenticates both SharePoint contexts
  2. Retrieves all files to be migrated
  3. Iterates through them using FileMigrationServiceV2
  4. Displays migration progress in the console

2️⃣ Full Example – Program.cs

using Microsoft.SharePoint.Client;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security;
using System.Threading;

namespace Contoso.MigrationTool
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Title = "Contoso SharePoint Migration Console";
            Console.WriteLine("===============================================");
            Console.WriteLine("  SHAREPOINT MIGRATION TOOL - Version 2.0");
            Console.WriteLine("  Preserves versions, authors, timestamps & comments");
            Console.WriteLine("===============================================\n");

            string sourceUrl = "https://contoso.sharepoint.com/sites/Engineering";
            string targetUrl = "https://contoso.sharepoint.com/sites/Archive";
            string libraryName = "Project Documents";
            string sourceRoot = "/sites/Engineering/Project Documents";
            string targetRoot = "/sites/Archive/Project Documents";

            Console.Write("Enter your SharePoint username: ");
            string username = Console.ReadLine();

            SecureString password = GetSecurePassword("Enter your password: ");
            var sourceCtx = new ClientContext(sourceUrl)
            {
                Credentials = new SharePointOnlineCredentials(username, password)
            };

            var targetCtx = new ClientContext(targetUrl)
            {
                Credentials = new SharePointOnlineCredentials(username, password)
            };

            // Load all items
            var list = sourceCtx.Web.Lists.GetByTitle(libraryName);
            var query = new CamlQuery { ViewXml = "<View Scope='RecursiveAll'><RowLimit>5000</RowLimit></View>" };
            var items = list.GetItems(query);

            sourceCtx.Load(items, each => each.Include(
                item => item["FileRef"],
                item => item.File,
                item => item.FileSystemObjectType));
            sourceCtx.ExecuteQuery();

            var files = items.Where(i => i.FileSystemObjectType == FileSystemObjectType.File).ToList();
            int total = files.Count;
            Console.WriteLine($"\nFound {total} files to migrate.\n");

            Stopwatch timer = Stopwatch.StartNew();
            int index = 0;

            foreach (var item in files)
            {
                index++;
                string fileUrl = item.File.ServerRelativeUrl;
                string relativeSubFolder = Path.GetDirectoryName(fileUrl.Replace(sourceRoot, "")).Replace("\\", "/");
                string finalTargetFolder = targetRoot.TrimEnd('/') + relativeSubFolder;

                DrawProgressBar(index, total, fileUrl);

                try
                {
                    FileMigrationServiceV2.MigrateFileWithVersions(
                        sourceCtx,
                        targetCtx,
                        fileUrl,
                        finalTargetFolder,
                        publishAfterMigration: true
                    );
                }
                catch (Exception ex)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine($"Error migrating {fileUrl}: {ex.Message}");
                    Console.ResetColor();
                }

                // Add short delay to avoid throttling
                Thread.Sleep(100);
            }

            timer.Stop();
            Console.WriteLine("\n===============================================");
            Console.WriteLine($"Migration complete! {total} files processed.");
            Console.WriteLine($"Total time: {timer.Elapsed:hh\\:mm\\:ss}");
            Console.WriteLine($"Log saved at: {Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MigrationLog.csv")}");
            Console.WriteLine("===============================================");
        }

        private static SecureString GetSecurePassword(string prompt)
        {
            Console.Write(prompt);
            SecureString pass = new SecureString();
            while (true)
            {
                var key = Console.ReadKey(intercept: true);
                if (key.Key == ConsoleKey.Enter)
                {
                    Console.WriteLine();
                    break;
                }
                else if (key.Key == ConsoleKey.Backspace && pass.Length > 0)
                {
                    pass.RemoveAt(pass.Length - 1);
                    Console.Write("\b \b");
                }
                else if (!char.IsControl(key.KeyChar))
                {
                    pass.AppendChar(key.KeyChar);
                    Console.Write("*");
                }
            }
            pass.MakeReadOnly();
            return pass;
        }

        private static void DrawProgressBar(int current, int total, string fileName)
        {
            int barWidth = 50;
            double progress = (double)current / total;
            int filled = (int)(progress * barWidth);

            Console.CursorLeft = 0;
            Console.Write("[");
            Console.Write(new string('=', filled));
            Console.Write(new string(' ', barWidth - filled));
            Console.Write($"] {current}/{total} files ({progress * 100:0.0}%)");
            Console.WriteLine();
            Console.WriteLine($"Migrating: {Path.GetFileName(fileName)}");
        }
    }
}


3️⃣ Example Console Output

===============================================
  SHAREPOINT MIGRATION TOOL - Version 2.0
  Preserves versions, authors, timestamps & comments
===============================================

Found 45 files to migrate.

[==========                              ] 10/45 files (22.2%)
Migrating: Proposal_v1.docx
   Version migrated: 2024-07-05 09:13
   Version migrated: 2024-08-01 17:45
   Current version migrated and published.
...
[========================================] 45/45 files (100%)
Migration complete! 45 files processed.
Total time: 00:18:42
Log saved at: C:\MigrationTool\MigrationLog.csv
===============================================


4️⃣ Key Features in the Console App

ComponentDescription
Progress BarDisplays migration percentage in real time.
Throttling DelayAdds a 100ms delay per file to reduce SharePoint Online throttling risk.
Job TimerMeasures total migration duration.
Error HandlingDisplays failed files in red for visibility.
CSV LogAutomatically created from FileMigrationServiceV2 for audit compliance.

5️⃣ Optional Enhancements

FeatureImplementation Idea
Parallel MigrationUse Parallel.ForEach() with throttling to process files in batches.
Azure Storage LogsStore logs in an Azure Blob container instead of local CSV.
Real-time UI DashboardFeed logs into a WPF or MAUI app showing charts and job metrics.
Incremental MigrationCompare Modified timestamps before copying to skip unchanged files.
E-mail SummarySend the CSV log automatically to admins when migration finishes.

6️⃣ Full Project Layout

📂 Contoso.MigrationTool
 ┣ 📜 Program.cs
 ┣ 📜 FileMigrationServiceV2.cs
 ┣ 📜 MigrationLog.csv
 ┗ 📜 app.config


7️⃣ Best Practice Checklist

AreaRecommendation
AuthenticationAlways use modern credentials or MSAL for token-based login.
PermissionsEnsure Full Control on both source and target libraries.
LoggingKeep every run’s CSV as a permanent audit record.
Error RecoveryImplement retry logic for transient SharePoint exceptions.
TestingStart with small libraries before scaling up full migrations.

8️⃣ References


Conclusion

This console-based approach turns your migration utility into a transparent, traceable, and user-friendly process.
You can now run large SharePoint migrations with:

  • Real-time feedback
  • Precise progress tracking
  • Auditable CSV logs
  • Safe version and metadata preservation

Edvaldo Guimrães Filho Avatar

Published by