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.
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()