Skip to content

Assess multiple sites in bulk — a tenant-wide readiness review.

Reads a file with one site URL per line, runs the recursive assessment for each site, and aggregates the results into a combined report. Useful for tenant-level readiness reviews before a migration program. Per-scan detail records (e.g. the LargeSites list) are merged so the combined report holds one row per site.

Permissions

Requires: read access to each site.

View source

import argparse
import json
import os

from office365.migration import MigrationAssessor
from office365.migration.assessment.report import AssessmentReport, ScanReport
from office365.sharepoint.client_context import ClientContext
from tests.settings import client_id, password, tenant, username


def write_report(report, output_dir: str) -> str:
    """Write the combined assessment (issues + scan details) as one JSON file."""
    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 main():
    parser = argparse.ArgumentParser(description="Bulk-assess a list of sites")
    parser.add_argument("--sites-file", required=True, help="file with one site URL per line")
    parser.add_argument("--output", default="/tmp", help="directory for the combined AssessmentReport.json report")
    args = parser.parse_args()

    urls = [line.strip() for line in open(args.sites_file, encoding="utf-8") if line.strip()]
    combined = AssessmentReport()

    for url in urls:
        ctx = ClientContext(url).with_username_and_password(tenant, client_id, username, password)
        print(f"Assessing {url}…", flush=True)
        report = MigrationAssessor(ctx.web).assess().execute_query().value
        print(report.summary())

        combined.total_webs += report.total_webs
        combined.total_lists += report.total_lists
        combined.total_files += report.total_files
        combined.total_size_gb += report.total_size_gb
        combined.lists_skipped = combined.lists_skipped or report.lists_skipped
        combined.webs_skipped = combined.webs_skipped or report.webs_skipped
        combined.issues.extend(report.issues)
        _merge_scan_reports(combined, report)

    print(f"\nCombined ({len(urls)} sites):")
    print(combined.summary())
    print("Report:", write_report(combined, args.output))


def _merge_scan_reports(combined: AssessmentReport, report: AssessmentReport) -> None:
    """Concatenate per-scan detail records across sites (one row per site)."""
    for name, scan in report.scan_reports.items():
        if name in combined.scan_reports:
            combined.scan_reports[name].records.extend(scan.records)
        else:
            combined.scan_reports[name] = ScanReport(name, scan.container, scan.columns, list(scan.records))


if __name__ == "__main__":
    main()

← Back to Assess