Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Workspace to Exchange Calendar Migration

A dry-run-first toolkit for restoring Google Workspace calendar exports from Google Takeout into Microsoft 365 Exchange Online.

The project is designed for organizations that no longer have access to the live Google Workspace tenant and must recover calendars from local ZIP/ICS exports. It inventories Takeout archives without bulk extraction, separates authoritative calendars from subscribed copies, provisions Exchange resources, and performs guarded Microsoft Graph imports.

Status: early-stage migration tooling. Always run against pilot mailboxes and disposable resources before production. Complex recurrence exceptions are reported for review rather than silently flattened.

Features

  • Streams ICS and resource metadata directly from Takeout ZIP files.
  • Generates archive checksums and ZIP-member inventories.
  • Classifies primary user calendars, resources, legacy calendars, appointment schedules, and subscribed copies.
  • Produces editable resource, principal, organizer, and permission mappings.
  • Deduplicates event components by Google UID plus RECURRENCE-ID.
  • Separates quiet historical restoration from active meetings that may send invitations.
  • Converts common daily, weekly, monthly, and yearly recurrence rules to Microsoft Graph.
  • Routes cancellations, recurrence exceptions, exclusions, attachments, and unsupported patterns to manual review.
  • Uses a deterministic transaction ID and local SQLite state for resumable, idempotent imports.
  • Verifies that the migration app can access in-scope mailboxes and cannot access an out-of-scope mailbox.
  • Automates Exchange room/equipment provisioning, Places metadata, calendar processing, folder permissions, and scoped application RBAC.
  • Keeps backup, cleanup planning, destructive cleanup, import, and reconciliation as separate operations.

Safety model

The following safeguards are intentional:

  • Source Takeout data is treated as immutable.
  • All generated reports and configuration are ignored by Git by default.
  • Inventory and manifest commands are read-only.
  • Import requires --apply --confirm 'RESTORE CALENDARS'.
  • Active import requires an additional explicit notification acknowledgment.
  • Cleanup requires a completed target backup, a reviewed deletion plan, and a separate destructive confirmation.
  • Deleting organizer meetings with attendees requires an additional cancellation-risk acknowledgment.
  • Application access should be granted through Exchange Online RBAC for Applications, scoped to a migration-only mail-enabled security group.
  • The app should not receive tenant-wide Microsoft Graph application roles when Exchange Application RBAC is used.

See AGENTS.md for the repository's non-negotiable working rules.

Requirements

Local migration application

  • Python 3.11 or later.
  • OpenSSL available on PATH for certificate-based client assertions.
  • Network access to Microsoft Entra ID and Microsoft Graph during tenant operations.

Exchange automation

  • PowerShell 7.4 or later on Windows, macOS, Linux, or Azure Cloud Shell.
  • ExchangeOnlineManagement PowerShell module.
  • An account with Exchange Administrator access and sufficient Exchange Organization Management authority to delegate Application RBAC roles.
  • Permission to create an Entra app registration and upload a certificate.

Privileged Role Administrator or Global Administrator is only necessary if tenant-wide Microsoft Graph application consent is used. This project instead recommends Exchange Application RBAC without tenant-wide Graph application roles.

Installation

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip setuptools
python -m pip install -e .
calendar-migrate --version

The command is generated from the [project.scripts] entry in pyproject.toml and is available whenever the virtual environment is activated.

Workflow overview

1. Protect the source export

Keep at least one untouched backup. Add the actual Takeout directory to .gitignore; never commit calendar data, event bodies, attendee addresses, exported metadata, or source-generated reports.

Example layout:

workspace2exchange-calendar-migration/
├── takeout-export/              # ignored local data
├── config/                      # ignored reviewed mappings
├── artifacts/                   # ignored reports and manifests
├── scripts/
├── src/
└── tests/

2. Inventory the Takeout export

The domain is required and is never inferred from project source code:

calendar-migrate inventory \
  --source ./takeout-export \
  --domain example.org \
  --timezone America/New_York \
  --graph-timezone 'Eastern Standard Time' \
  --output artifacts/inventory

--timezone is the IANA fallback for source events without usable timezone metadata. --graph-timezone is the corresponding Microsoft/Windows timezone used by Graph. Supplying both prevents the migration from silently assuming the original author's locale.

Important outputs include:

  • archive_checksums.csv
  • zip_members.csv
  • calendars.csv
  • resources.proposed.csv
  • principals.proposed.csv
  • organizers.proposed.csv
  • permissions.proposed.csv
  • migration.proposed.json
  • inventory_summary.json

3. Initialize and review configuration

calendar-migrate initialize-config \
  --inventory artifacts/inventory \
  --config-dir config

Review every row in:

  • config/resources.csv
  • config/principals.csv
  • config/organizers.csv
  • config/permissions.csv
  • config/migration.json

Nothing is approved automatically. For each resource, choose a target SMTP address, resource type, disposition, and approval status. Classify principals that have no primary calendar as users, groups, aliases, or ignored records. Explicitly map any legacy Google group-calendar organizers.

Validate the reviewed configuration:

calendar-migrate validate --config config/migration.json

4. Build the dry-run manifest

calendar-migrate manifest \
  --config config/migration.json \
  --output artifacts/dry-run

Review:

  • manifest_summary.json
  • event_manifest.csv
  • event_manifest.jsonl
  • duplicates.csv
  • manual_review.csv
  • notification_impact.csv

The JSONL file contains the exact Microsoft Graph payloads. Do not hand-edit it; correct the mappings or transformation logic and regenerate it.

Historical and active events

Creating a Microsoft Graph event with attendees sends meeting invitations. The manifest therefore uses two phases:

History

  • Restored quietly without active attendee relationships.
  • Preserves calendar occupancy without replaying old invitations.
  • Written to each approved historical target where the item belongs.

Active

  • Created only in the selected organizer mailbox.
  • Includes mapped internal attendees and Exchange resources.
  • May send invitations and room-processing messages.
  • Requires the notification-impact report and an extra command-line confirmation.

Recurring series with future occurrences are active. RECURRENCE-ID, EXDATE, cancelled components, and unsupported recurrence patterns are placed in manual review.

Microsoft 365 application access

Create a dedicated single-tenant Entra app registration with a short-lived certificate. Do not add tenant-wide Microsoft Graph application permissions when using Exchange Application RBAC.

Generate a certificate outside the repository:

mkdir -p ../migration-secrets
openssl req -x509 -newkey rsa:3072 -sha256 -nodes -days 90 \
  -subj '/CN=Workspace to Exchange Calendar Migration' \
  -keyout ../migration-secrets/migration-private-key.pem \
  -out ../migration-secrets/migration-certificate.pem

Upload only the public certificate to the app registration. Record:

  • Tenant ID.
  • Application/client ID.
  • Enterprise application service-principal object ID.
  • External private-key and public-certificate paths.

Put those values in the ignored config/migration.json.

Exchange Online automation

Install the module and test the administrator account:

Install-Module ExchangeOnlineManagement -Scope CurrentUser
./scripts/Test-AdminPrerequisites.ps1 -AdminUserPrincipalName admin@example.org

All mutating PowerShell scripts default to dry run.

Provision resources

./scripts/Provision-Resources.ps1 `
  -MappingPath ./config/resources.csv `
  -AdminUserPrincipalName admin@example.org

Apply only after review:

-Apply -Confirmation 'CREATE EXCHANGE RESOURCES'

Configure scoped application access

./scripts/Configure-AppRbac.ps1 `
  -ResourceMappingPath ./config/resources.csv `
  -PrincipalMappingPath ./config/principals.csv `
  -AdminUserPrincipalName admin@example.org `
  -AppId '<application-client-id>' `
  -ServicePrincipalObjectId '<enterprise-application-object-id>'

Apply only after review:

-Apply -Confirmation 'CONFIGURE SCOPED APP ACCESS'

Application RBAC changes can take time to propagate. Verify both allowed and denied access:

calendar-migrate verify-graph \
  --config config/migration.json \
  --in-scope pilot-user@example.org \
  --out-of-scope excluded-user@example.org

Configure room calendar permissions

./scripts/Set-CalendarPermissions.ps1 `
  -ResourceMappingPath ./config/resources.csv `
  -PermissionMappingPath ./config/permissions.csv `
  -AdminUserPrincipalName admin@example.org

Apply only after review:

-Apply -Confirmation 'GRANT CALENDAR PERMISSIONS'

Calendar Editor/Delegate access is different from Set-CalendarProcessing -ResourceDelegates, which controls approval of booking requests.

Backup and cleanup

Back up existing target calendar data before planning any deletion:

calendar-migrate backup --config config/migration.json \
  --mailbox pilot-user@example.org \
  --output artifacts/target-backup

calendar-migrate plan-clear --config config/migration.json \
  --mailbox pilot-user@example.org \
  --output artifacts/clear-plan

plan-clear never deletes anything. Review the plan before using execute-clear. Organizer events with attendees can send cancellations and require a separate acknowledgment.

Guarded imports

Pilot one quiet historical action:

calendar-migrate import \
  --config config/migration.json \
  --manifest artifacts/dry-run/event_manifest.jsonl \
  --phase history --limit 1 \
  --apply --confirm 'RESTORE CALENDARS'

Pilot one active meeting only with approved test recipients:

calendar-migrate import \
  --config config/migration.json \
  --manifest artifacts/dry-run/event_manifest.jsonl \
  --phase active --limit 1 \
  --apply --confirm 'RESTORE CALENDARS' \
  --allow-notifications \
  --notification-confirm 'SEND MIGRATION INVITATIONS'

Rerunning an already-successful action with the same payload hash is skipped using the ignored SQLite state database.

Reconciliation and revocation

calendar-migrate reconcile \
  --config config/migration.json \
  --output artifacts/reconcile

After migration, revoke the Exchange application assignment:

./scripts/Remove-AppRbac.ps1 `
  -AdminUserPrincipalName admin@example.org `
  -AppId '<application-client-id>'

Review the dry run, then use:

-Apply -Confirmation 'REVOKE MIGRATION APP ACCESS'

Remove the Entra certificate credential or delete the app registration, securely delete the private key, and retain non-secret audit evidence.

Testing

python -m unittest discover -s tests -v

See docs/PILOT_RUNBOOK.md for a staged tenant pilot.

License

This project is licensed under the GNU General Public License v3.0 or later. You may use, modify, and redistribute it under the GPL's terms. It is provided without warranty; see the license for details.

Limitations

  • The importer does not silently recreate complex recurrence exceptions. They require review or future exception-handling support.
  • Google appointment-schedule products are inventoried separately and are not treated as ordinary calendars.
  • External attendees are excluded by default to prevent accidental messages.
  • ICS attachments require review; inaccessible Google Drive attachments cannot be recovered from ICS alone.
  • Calendar organizer identity cannot be changed in-place in Outlook; changing organizer requires creating a new meeting.
  • This project is not affiliated with or supported by Google or Microsoft.

About

Tools for migrating data from Google Workspace to Microsoft incl. Outlook/Exchange calendars

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages