Skip to content

Pre-migration assessment — surface blockers and warnings before moving data.

Basic usage of the modular MigrationAssessor: scan the site, then print a summary plus the flagged issues (blockers block a migration, warnings are advisory) and the SMAT-style scan detail reports.

Scans are registered in office365.migration.sharepoint.registry (mirroring SMAT's ScanDef.json); --disable-scan turns one off (its data is not collected), --only-scan runs just one.

The assessment is the "scan" phase of the migration workflow — pair it with MigrationJob (see migrate/migrate_files.py / migrate/export_list.py) to assess, then migrate, then verify.

Reference

View source

import argparse
import json
import os

from office365.migration import MigrationAssessor
from office365.migration.sharepoint.registry import SHAREPOINT_SCANS
from office365.sharepoint.client_context import ClientContext
from tests.settings import client_id, password, team_site_url, tenant, username


def write_report(report, output_dir: str) -> str:
    """Write the assessment (issues + scan details) as one ``AssessmentReport.json``."""
    data = {
        "issues": report.to_records(),
        "scans": {name: scan.to_records() for name, scan in report.scan_reports.items()},
    }
    os.makedirs(output_dir, exist_ok=True)
    path = os.path.join(output_dir, "AssessmentReport.json")
    with open(path, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2)
    return path


def print_scan_reports(report) -> None:
    """Print the SMAT-style detail reports (e.g. LargeSites)."""
    for name, scan in report.scan_reports.items():
        print(f"\n{name} ({scan.container.value}): {len(scan.records)} row(s)")
        for row in scan.to_records():
            print("  " + " | ".join(f"{k}={row[k]}" for k in scan.columns))


def main():
    parser = argparse.ArgumentParser(description="Assess a SharePoint site for migration readiness")
    parser.add_argument("--site-url", default=team_site_url, help="site URL to assess (scans subsites too)")
    parser.add_argument("--permissions", action="store_true", help="scan for unique permissions (slower)")
    parser.add_argument("--site-admins", action="store_true", help="include site collection admins in LargeSites")
    parser.add_argument("--no-recursive", action="store_true", help="scan only the root web, not subsites")
    parser.add_argument("--disable-scan", action="append", help="disable a scan (e.g. LargeSites)")
    parser.add_argument("--only-scan", help="run only this scan (e.g. LargeSites)")
    parser.add_argument("--output", default="/tmp", help="directory for the AssessmentReport.json report")
    args = parser.parse_args()

    ctx = ClientContext(args.site_url).with_username_and_password(
        tenant=tenant, client_id=client_id, username=username, password=password
    )
    assessor = MigrationAssessor(ctx.web)
    if args.permissions:
        assessor.include_permissions()
    if args.site_admins:
        assessor.include_site_admins()
    for name in args.disable_scan or []:
        assessor.disable_scan(name)
    if args.only_scan:
        for definition in SHAREPOINT_SCANS:
            if definition.name != args.only_scan:
                assessor.disable_scan(definition.name)

    print("Assessing…", flush=True)
    report = assessor.assess(recursive=not args.no_recursive).execute_query().value
    print(report.summary())
    print("Report:", write_report(report, args.output))

    print_scan_reports(report)

    if report.blockers:
        print("\nBlockers (must fix before migrating):")
        for issue in report.blockers:
            print(f"  - [{issue.category}] {issue.location}: {issue.message}")
            if issue.suggestion:
                print(f"    -> {issue.suggestion}")
    if report.warnings:
        print("\nWarnings (advisory):")
        for issue in report.warnings:
            print(f"  - [{issue.category}] {issue.location}: {issue.message}")


if __name__ == "__main__":
    main()

← Back to Assess