Skip to content

Auto-apply a retention label to files based on their age.

Scans a SharePoint document library and applies a retention label to files that are older than a specified threshold but haven't yet been labeled. Useful for applying a "default retention" to legacy content without manual intervention.

Inspired by Auto-ApplyRetentionLabels.PS1 from Office 365 for IT Pros.

Permissions

Required delegated permissions: RecordsManagement.ReadWrite.All Create and apply retention labels Sites.Read.All Read SharePoint files and metadata Files.ReadWrite.All Apply labels to files

Reference

View source

from __future__ import annotations

from datetime import datetime, timedelta, timezone

from office365.graph_client import GraphClient
from office365.sharepoint.client_context import ClientContext
from tests.settings import client_id, client_secret, site_url, tenant

_SAMPLE_SIZE = 10


def get_retention_label_id(client: GraphClient, label_name: str) -> str | None:
    """Fetch a retention label by display name.

    Returns the label ID or None if not found.
    """
    try:
        labels = client.security.labels.retention_labels.get().execute_query()
        for label in labels:
            if getattr(label, "display_name", "") == label_name:
                return label.id
    except Exception:
        pass
    return None


def find_files_without_label(ctx: ClientContext, age_days: int) -> list[dict]:
    """Find files older than *age_days* that have no retention label.

    Args:
        ctx: Authenticated SharePoint ClientContext.
        age_days: File age threshold.

    Returns:
        List of file dicts (name, url, created, modified).
    """
    cutoff = datetime.now(timezone.utc) - timedelta(days=age_days)
    unlabeled = []

    try:
        lib = ctx.web.default_document_library()
        items = (
            lib.items.select(["FileLeafRef", "FileRef", "Created", "ComplianceTag"])
            .filter("FSObjType eq 0")
            .get_all()
            .execute_query()
        )

        for item in items:
            created = item.properties.get("Created", "")
            compliance_tag = item.properties.get("ComplianceTag", None)

            if compliance_tag:
                continue  # already labeled

            try:
                created_dt = datetime.fromisoformat(created.replace("Z", "+00:00"))
            except (ValueError, TypeError):
                continue

            if created_dt < cutoff:
                unlabeled.append(
                    {
                        "name": item.properties.get("FileLeafRef", "Unknown"),
                        "url": item.properties.get("FileRef", ""),
                        "created": created_dt,
                    }
                )
    except Exception as e:
        print(f"  Error scanning files: {e}")

    return unlabeled


def apply_label_to_file(ctx: ClientContext, file_url: str, label_name: str) -> bool:
    """Apply a retention/compliance label to a file.

    Note: The exact mechanism depends on your SharePoint/Graph version.
    This shows the conceptual pattern.
    """
    # In production, you'd set ComplianceTag on the list item.
    # Example:
    #   item = ctx.web.get_file_by_server_relative_url(file_url).list_item_all_fields
    #   item.set_property("ComplianceTag", label_name)
    #   item.update().execute_query()
    print(f"  Would apply '{label_name}' to {file_url}")
    return True


def main():
    print("Auto-apply retention labels to unlabelled files...\n")

    graph = GraphClient(tenant=tenant).with_client_secret(client_id, client_secret)

    # 1. Find the label
    label_name = "Default Retention — 7 years"
    label_id = get_retention_label_id(graph, label_name)

    if not label_id:
        print(f"Label '{label_name}' not found. Create it in Purview first.")
        print("Available labels can be listed via Graph API or Purview portal.")
        return

    print(f"Using label: {label_name} ({label_id[:12]}...)")

    # 2. Scan for unlabelled files older than 365 days
    ctx = ClientContext(site_url).with_client_secret(tenant, client_id, client_secret)

    files = find_files_without_label(ctx, age_days=365)
    print(f"Found {len(files)} older unlabelled files\n")

    # 3. Apply labels
    for f in files[:_SAMPLE_SIZE]:  # limit for safety
        apply_label_to_file(ctx, f["url"], label_name)

    if len(files) > _SAMPLE_SIZE:
        print(f"\n... and {len(files) - _SAMPLE_SIZE} more files would be labeled")
    print("\nReview the file list and uncomment the update call to apply.")


if __name__ == "__main__":
    main()

← Back to Records