Showing posts with label Microsoft 365. Show all posts
Showing posts with label Microsoft 365. Show all posts

May 7, 2026

Building Site-Aware Enterprise AI Agents on Microsoft 365 Using Claude Agent SDK

Introduction

An enterprise running seven department-specific SharePoint intranet sites needed AI that could actually operate within their systems - not just answer questions about them. Here is what a single task looked like before we got involved - and after.

Before - Sales proposal workflow After - same task, one sentence
Open Pipeline Tracker on SharePoint - find the deal Type: "Draft a proposal for XYZ Corp's cloud migration deal and send it to their CTO."
Switch to Client Contacts - find the CTO's email Agent queries Pipeline Tracker - pulls deal value, stage, scope notes
Open Word - hunt for the proposal template on the shared drive Agent looks up CTO in Client Contacts - name, email, title
Manually fill in deal value, scope, and terms Branded proposal generated from python-docx template and uploaded to Sales Collateral
Save, switch back to SharePoint, upload to Sales Collateral library Email compose panel opens - pre-filled with CTO's address, subject, and body
Open Outlook - type the CTO's email, write the body, attach, send Review, tweak one line, hit Send
40 minutes  ·  6 applications  ·  1 task One sentence  ·  Five systems  ·  Done

We assessed the organisation's requirements against Copilot Cowork and Claude Cowork. Both are genuinely capable products - but neither could query custom SharePoint list schemas, connect to a SQL employee database, generate documents from branded templates, or switch to a different specialist persona depending on which department site the user was on. They needed something those products are structurally not built to do.

Same panel. Same backend. Same LLM. Completely different specialist per site, loaded at runtime from a JSON manifest. This is what we built. Here is how:

The Architecture in One Sentence

A single SPFx floating panel on every SharePoint page sends messages to a FastAPI backend, which enters a Claude agentic loop with a filtered set of MCP tools - filtered by which site the user is on, what they are allowed to do, and which department's data model applies. Each site loads its own agent persona, slash commands, and skill files - deep knowledge modules that teach the agent how a specific department's data is structured, what business rules apply, and which workflows exist.

System Architecture diagram

System Architecture - SPFx panel → FastAPI backend → Claude agentic loop → MCP server → enterprise data sources

That sentence hides a lot of machinery. Three decisions do most of the work: how each site loads its own specialist, how authentication is split between reads and writes, and how the agentic loop orchestrates everything in between. Let me walk through each.

Decision 1: The Plugin Manifest

When a user opens the Sales site and the panel initialises, the backend resolves their session: who are they (from the OBO token), which site are they on (from the site_url passed by the frontend), and what can they do (from the plugin manifest). The manifest is a single JSON file:

{
  "name": "sales",
  "display_name": "Sales Assistant",
  "entry_agent": "agents/sales-assistant.md",
  "connectors": ["sharepoint", "msgraph", "email", "docgen", "ems",
    "knowledge", "analytics", "automation"],
  "commands": ["commands/pipeline-report.md", "commands/add-lead.md", ...],
  "skills": ["skills/pipeline-management/SKILL.md",
    "skills/client-engagement/SKILL.md", ...],
  "sharepoint_lists": {
    "lists": {
      "Pipeline Tracker": "columns: OpportunityName, DealValue, PipelineStage, ..."
    }
  }
}

This manifest does five things at session start:

It loads a persona.

The entry_agent points to a markdown file that defines the agent's personality, role, behaviour rules, and domain expertise. The Sales agent is "friendly, professional, and results-driven." The Finance agent is precise and compliance-aware. The People agent is warm and policy-oriented. Same LLM, different specialist.

It filters the tool palette.

The connectors array controls which of the 58 MCP tools appear in Claude's tool list for this session. Sales gets SharePoint, Graph, Email, DocGen, EMS, Knowledge, Analytics, and Automation - but not Azure DevOps. Delivery gets Azure DevOps too. Technology gets everything. The LLM only sees tools it is allowed to use.

It loads skill files.

This is where the deep domain knowledge lives. Each skill is a markdown file that describes how a specific aspect of the department works. The Sales plugin has skills for pipeline management, client engagement, and competitive intelligence. A skill might describe how the Pipeline Tracker list is structured, what each column means, which OData filters produce useful results, what "stalled deal" means in this organisation's context, and what steps the agent should follow for a pipeline review. Skills are loaded into the system prompt based on the site and the user's query - they are the agent's domain training, delivered at runtime through text, not fine-tuning.

It injects list schemas.

The sharepoint_lists block is injected into the system prompt. This is how the agent knows the column is called PipelineStage, not Stage. It knows DealValue is the field for revenue, not Value or Amount. It never guesses. If a column is not in the manifest, the agent cannot reference it.

It registers slash commands.

Each command is a markdown file with trigger phrases, required parameters, the agent to hand off to, and step-by-step instructions. When a user types /pipeline-report, the command's markdown gets injected into the system prompt for that turn.

Plugin-Per-Site Architecture diagram

Plugin-Per-Site Architecture - 7 sites, auto-discovered, manifest-driven, session-filtered

The consequence of this design is that adding a new department - an eighth site, a regional hub, a project workspace - requires creating one folder with one JSON manifest and a few markdown files. No code changes. No backend redeployment. The registry discovers it on next restart.

The key insight: The plugin folder is the architecture. Not the LLM, not the framework, not the cloud infrastructure. The entire system's behaviour - which specialist responds, which tools are available, which data is accessible, which commands exist, which domain knowledge the agent draws on - is determined by which plugin.json file gets loaded and which skill files sit alongside it. Everything else is shared plumbing. This is what makes the system maintainable at scale: changing a department's AI behaviour is editing a JSON file and a few markdown documents, not shipping code.

Decision 2: Dual-Auth and the Invisible Session

Authentication is where most custom AI implementations get it wrong. Either they use the user's token for everything (writes are unauditable and tied to individual permissions) or they use an app-only token for everything (losing the identity context of who asked for the action).

We split it:

Reads use the user's delegated OBO token.

When the agent queries a SharePoint list or fetches calendar events, it does so as the user. If the user cannot see a site, neither can the agent. The existing M365 permission model is preserved - no data leakage, no privilege escalation.

Writes use an app-only certificate token.

  • All write operations - list item creation, document upload, email send - execute through a controlled service identity, not the user's token.
  • Every write is routed through an audit logger: who requested it, what changed, tool name, payload, status, and duration.
  • site_url, OBO token, app-only token, and the user's permission set are never parameters the LLM sees - they are injected from the session object before the agentic loop begins.

In the initial version, SharePoint tools accepted site_url as a tool parameter. During early integration testing, Claude hallucinated a URL - it substituted the Sales site URL when a user on the Finance site asked about budget items. The query returned nothing. The agent confidently reported "no budget items found." It was a silent, plausible failure - the worst kind. We refactored site_url out of every tool interface within the day and moved it to session-level injection. The LLM never sees it, never chooses it, never hallucinates it.

If the LLM does not need a value to reason about the task, do not put it in the tool interface. Every parameter visible to the LLM is a surface for hallucination. Session-level injection eliminates that surface.

Decision 3: The Agentic Loop

When a user sends a message, the backend does not call Claude once and return the answer. It enters a loop:

Agentic Loop diagram

Agentic Loop - Request Lifecycle with tool execution feedback loop

  • Multi-step tool chains. A single message like "find stalled deals, draft follow-ups, and notify the sales lead" triggers five sequential tool calls - SharePoint OData query, EMS employee lookup, three email draft preparations - each feeding the next decision.
  • Real-time status streaming. Each tool call emits an SSE event to the frontend ("Fetching list data: Pipeline Tracker...", "Looking up employee...") so the user sees the agent working, not a loading spinner.
  • Model is a config variable. Claude Sonnet is the default - fast enough for real-time streaming, capable enough for multi-step orchestration. Swapping models is a single environment variable change; the architecture is not coupled to any provider.
  • We tested Azure OpenAI + Semantic Kernel. The loop runs and tools get called. But in head-to-head testing, Claude produced fewer hallucinated tool parameters, followed complex multi-step prompts more faithfully, and handled branching logic - where a tool result determines whether to call the next tool or skip to a different action - more consistently. That is why it is our default.

What the Agent Can Actually Do

58 tools across 9 connectors. Here are the ones that matter most to daily operations:

Connector What it does Available on
SharePoint Read list items with OData filters, create and update items, browse document libraries, upload files, search across the site. Primary data layer. All sites
EMS Direct SQL via pyodbc against the HR database: employee lookup, org chart traversal, leave balances, skills search, project assignments, team allocation, capacity planning, budget tracking. No REST wrapper. All sites
Document generation Branded offer letters, proposals, and reports via python-docx. Budget workbooks via openpyxl. Generated on the backend, uploaded to SharePoint, download card in chat. Sales, People, Finance, Operations
Email Two-step workflow: agent drafts, human reviews in inline compose panel, then confirms Send. Never auto-sends. All sites
Azure DevOps Query and create work items, get sprint status. Delivery, Technology only
Proactive alerts Five APScheduler handlers: stalled deals, budget thresholds, expiring contracts, onboarding gaps, morning brief digest. Push to Teams channels and notification bell before anyone opens a browser. All sites (handler-specific)
MS Graph Users, calendar, Teams channels, mail integration. All sites
Analytics Natural language reports, anomaly detection. All sites
Knowledge Federated search, expert finder. All sites

Lessons from the Build

Every implementation surfaces lessons that inform the next one.

SharePoint field name complexity runs deeper than expected.

Display names and internal names diverge in non-obvious ways. "Status" might be stored as BudgetStatus. "Details" might be AnnouncementDetails. The initial deployment surfaced field name mismatches that cost debugging cycles. Our fix - a PowerShell schema export script that generates verified field mappings per site - is now a standard first step in our deployment methodology.

Not every data source needs an API layer.

Our first design had a full REST API wrapper around the SQL employee database. We replaced it with direct pyodbc queries wrapped in the MCP tool contract. Simpler, faster, easier to maintain.

The in-process MCP server will need extraction.

Running the tool server in-process was the right call for initial development - no serialisation overhead, easy debugging. But for production scale, tool execution needs to run independently so long-running operations (document generation, cross-site searches) do not block the API layer. The architecture was designed for this extraction - MCP's HTTP transport makes it a configuration change, not a rewrite - which is planned for the next phase.

Retry logic is now day-one infrastructure.

Claude's API occasionally returns transient errors under load. Without exponential backoff (1s, 2s, 4s), these surface as user-facing failures. Retry logic is now part of our standard agentic infrastructure from the start, based on this experience.

Production hardening - monitoring, observability, error recovery, and scale testing - deserves its own post. We will publish that next.

A Note on Portability

This case study uses SharePoint as the enterprise platform and Claude as the LLM, but the pattern is platform-agnostic. The same plugin-per-site architecture has been applied with Confluence, custom intranets, and internal portals. The frontend can be any web surface that supports a JavaScript embed - the SPFx panel is one implementation, not a requirement. The connectors, the plugin manifest pattern, the dual-auth model, and the session-scoped tool filtering are all transferable. If your organisation runs on a different stack, the architecture adapts to it.

Conclusion

The plugin-per-site pattern, dual-auth model, session-scoped tool filtering, and in-process MCP server described here form a reusable enterprise architecture - not a one-off implementation. Adding a new department means creating a folder with a JSON manifest and a few markdown skill files. No code changes, no redeployment. The system's behaviour is entirely determined by configuration, not by the codebase.

This architecture was designed and deployed by Binary Republik. It is adaptable to any organisation's departmental structure, data model, and governance requirements - and transferable to any enterprise platform with programmatic APIs.

If you have any questions you can reach out to our AI Consulting team here.

March 3, 2026

How to Audit Specific User Permissions Across All SharePoint Online Sites Using PowerShell

Introduction

Managing user access in SharePoint Online can be challenging – especially when you want to audit permissions for specific users across all site collections.

We use a PowerShell script:

  • Retrieves permissions for multiple users
  • Identifies direct and group-based permissions
  • Scan all accessible SharePoint sites
  • Exports results to an Excel (.xlsx) report

We will guide you step by step on how to configure and run the script.

Overview of the blog in image - Managing SharePoint Online User Permissions with PowerShell

Pre-Requisites:

  1. Admin Credentials:

    You must login using a SharePoint Administrator account.

  2. Azure AD Application Details

    Collect the following values from the Azure AD App Registration:

    • Client ID
    • Tenant ID
  3. Permission to All SharePoint Sites

    Only site you can access will return data.

  4. Users.csv File

    The file contains the list of user email addresses you want to check

    Example Users.csv

    User Email csv file image
  5. Script and CSV File in the Same Folder.

    • GetSpecificUsersPermission.ps1
    • Users.csv

Configure the Script:

  • Open the GetSpecificUsersPermission.ps1 file and update:
  • AdminSiteURL → your admin URL
  • https://yourtenant-admin.sharepoint.com
  • Client ID → your registered application’s Client ID
  • Tenant ID → your Azure AD Tenant ID
  • CSV file name if you changed it.

PowerShell Script: GetSpecificUsersPermission.ps1


# Parameters
$AdminSiteURL = "https://yourtenant-admin.sharepoint.com"
$ClientId = "ClientId"
$TenantId = "TenantId"
$usersCSV = "users.csv"

# Get the folder where this script is located
$scriptFolder = $PSScriptRoot

$ReportOutput = Join-Path -Path $scriptFolder -ChildPath "SpecificUsersPermissionReport.csv"

$UsersCsvPath = Join-Path -Path $scriptFolder -ChildPath $usersCSV

Write-Host "CSV will be saved to: $ReportOutput"
Write-Host "Reading users from: $UsersCsvPath"

$UsersToCheck = Import-Csv -Path $UsersCsvPath | Select-Object -ExpandProperty UserEmail

# Connect to Admin Center
$AdminConnection = Connect-PnPOnline -Url $AdminSiteURL -ClientId $ClientId `
-Tenant $TenantId -Interactive -ReturnConnection

# Get all site collections
$Sites = Get-PnPTenantSite -Connection $AdminConnection

$Results = @()

foreach ($Site in $Sites) {
	Write-Host "Processing site: $($Site.Url)" -ForegroundColor Cyan

	# Connect to the actual site
	$SiteConnection = Connect-PnPOnline -Url $Site.Url -ClientId $ClientId `
	-Tenant $TenantId

	# Get the root web and role assignments
	$Web = Get-PnPWeb -Connection $SiteConnection -Includes `
	RoleAssignments, HasUniqueRoleAssignments

	Get-PnPProperty -ClientObject $Web -Property RoleAssignments

	foreach ($RoleAssignment in $Web.RoleAssignments) {

		Get-PnPProperty -ClientObject $RoleAssignment -Property `
		RoleDefinitionBindings, Member

		# Direct user
		if ($RoleAssignment.Member.PrincipalType -eq "User") {

			$UserEmail = ($RoleAssignment.Member.LoginName -split '\|')[-1]

			if ($UsersToCheck -contains $UserEmail) {

				$Results += [PSCustomObject]@{
					SiteURL           = $Site.Url
					UserOrGroupName   = $RoleAssignment.Member.Title
					Type              = "Direct User"
					PermissionLevels  = ($RoleAssignment.RoleDefinitionBindings |
										  Select -ExpandProperty Name) -join ", "
				}
			}
		}

		# SharePoint Group
		elseif ($RoleAssignment.Member.PrincipalType -eq "SharePointGroup") {

			try {
				$Group = Get-PnPGroup -Identity $RoleAssignment.Member.Title `
				-Includes Users -Connection $SiteConnection

				foreach ($User in $Group.Users) {

					$UserEmail = ($User.LoginName -split '\|')[-1]

					if ($UsersToCheck -contains $UserEmail) {

						$Results += [PSCustomObject]@{
							SiteURL           = $Site.Url
							UserOrGroupName   = $User.Title
							Type              = "User (via Group: $($RoleAssignment.Member.Title))"
							PermissionLevels  = ($RoleAssignment.RoleDefinitionBindings |
												  Select -ExpandProperty Name) -join ", "
						}
					}
				}
			}
			catch {
				Write-Warning "Cannot access group $($RoleAssignment.Member.Title) in site $($Site.Url). Skipping."
			}
			finally {
				if ($SiteConnection) {
					Disconnect-PnPOnline -Connection $SiteConnection
				}
			}
		}
	}
}

if ($AdminConnection) {
	Disconnect-PnPOnline
}

# Export results to CSV
$Results | Export-Csv -Path $ReportOutput -NoTypeInformation

Write-Host "Report generated successfully at $ReportOutput"
	

How to Run the Script:

  1. Open PowerShell
  2. Navigate to your script folder:

    Cd “C:\YourFolder”

  3. Run the script:

    .\GetSpecificUsersPermission.ps1

  4. Enter your admin credentials
  5. Complete browser authentication
  6. Wait for the report to be generated

Output Report:

The script generates an Excel report containing:

  • Site URL
  • User Email
  • Direct Permissions
  • Group memberships
  • Role definitions
  • Permission levels

You will find the .xlsx report in the same folder as the script

Conclusion:

This approach gives SharePoint administrators a quick and efficient way to:

  • Audit Permissions.
  • Verify access.
  • View direct & group permissions.
  • Export clean reports for governance.
  • Just update the script, run it and your Excel report is ready.

If you have any questions you can reach out our SharePoint Consulting team here .

January 23, 2026

A Complete Step-by-Step Guide to Google Workspace to Microsoft 365 Email Migration


Introduction:

This blog describes an email migration from Google Workspace to Microsoft 365, focused exclusively on transitioning Gmail mailboxes to Exchange Online. The migration involved precise user-to-user mailbox mapping, preservation of primary SMTP addresses, and configuration of email aliases to ensure uninterrupted mail flow across multiple domains.

A phased, domain-based migration approach was used to reduce risk, validate mail delivery, and maintain identity consistency during cutover, providing a practical reference for executing real-world Google Workspace to Microsoft 365 email migrations.


Note: This article focuses on email migration.

To migrate files and folders, refer to this guide for Google Drive to OneDrive for Business (ODFB) migration.

Adding Domain in Microsoft 365 Account

Step 1: Access the Microsoft 365 Portal

Begin by navigating to office.com in your web browser. Sign in using your Global Administrator credentials to access the main dashboard.

Step 2: Launch the Admin Center

From your Microsoft 365 Home dashboard, locate the Admin tile (usually found under "Quick access" or by clicking the app launcher) and click it to enter the management portal.


Step 3: Access Domain Management

Inside the Admin Center, locate the left-hand navigation menu. Click on Settings to expand the list, then select Domains. This is where you will configure and verify the external domain you wish to migrate from Google Workspace.


Step 4: Initiate the Add Domain Wizard

In the Domains management pane, look for the toolbar at the top of the list. Click on the + Add domain button to begin the wizard. This will launch the setup process for linking your existing Google Workspace domain (e.g., yourcompany.com) to your Microsoft 365 tenant.


Step 5: Input Your Domain Name

The Add a domain wizard will now appear. In the designated text field, type the exact name of the domain you are migrating from Google Workspace (e.g., yourcompany.com). Once entered, click the Use this domain button to proceed to the verification stage.


Step 6: Verify Domain Ownership

Microsoft must confirm that you own the domain before proceeding. You will be presented with a few verification options. The most common and secure method is to select Add a TXT record to the domain's DNS records. Select this option and click Verify (or continue) to retrieve the specific record values you need to add to your DNS host.


Step 7:  Defer the DNS Connection

The wizard will now ask how you want to connect your domain. Since this is a migration and you are not ready to switch mail flow (MX records) yet, it is crucial to select Skip and do this later. This prevents any immediate disruption to your current Google Workspace emails. Click Continue to finalize the setup without altering your DNS.


Step 8: Finalize the Domain Setup

You will now see a confirmation screen stating, "Domain setup is complete." This indicates that Microsoft 365 has successfully verified your domain ownership. Since we chose to skip the DNS record updates for now (to keep your current email active), simply click the Done button to close the wizard.

Note: Once you click the Done button, your new domain will appear in the active domains list. It may also be automatically set as your Default domain for new user creation.


Provisioning Users for Microsoft 365 Migration

Step 9: Access User Management

In the Microsoft 365 Admin Center, locate the left-hand navigation pane. Click on Users to expand the menu, then select Active users. This dashboard is where you will create the destination accounts for your migration.


Step 10: Initiate User Creation

On the Active user's page, you will see the management dashboard for your user accounts. To start provisioning a new user, click the Add a user button located in the toolbar at the top of the list.


Step 11: Enter Basic User Details

The Add a user wizard will open to the Basics tab. Here, you must fill in the user's identity information, including First name, Last name, and Display name.


Step 12: Assign Product Licenses

On the Product licenses page, first select the appropriate country from the Location dropdown menu. Next, ensure the Assign user a product license option is selected. Check the box next to the specific license you wish to assign (e.g., Microsoft 365 Business Standard) and click Next.


Step 13: Configure Optional Settings

You will arrive at the Optional settings page. Here, you can assign specific administrative Roles to the user if needed, such as "Global admin" or "User admin". For a standard user, you can leave the default role ("User: no administration access") and click Next to proceed.


Step 14: Review and Finalize

You will now reach the Review and finish page. This is your chance to verify all the details you have configured, including the display name, username, and assigned licenses. If everything looks correct, click the Finish adding button to officially create the user account.


Step 15: Confirmation of User Creation

After a few seconds of processing, a confirmation window will appear stating "User added to active users." This indicates the account has been successfully created. You can view or copy the password details here if needed. Once done, click the Close button to exit the wizard.

Note: You must repeat these steps for every single active user you intend to migrate. Ensure all accounts are created and assigned valid licenses before proceeding to the migration phase.

Once all users are added, remember to set your custom domain as the Default. To do this, navigate to Settings > Domains in the Admin Center, select your new domain, and choose Set as default.


Migrating Google Workspace Mailboxes to Microsoft 365

Step 16: Access the Exchange Admin Center

In the Microsoft 365 Admin Center, locate the left-hand navigation menu. Click on ... Show all to expand the full list of options. Scroll down to the "Admin centers" section and select Exchange. This will launch the modern Exchange Admin Center (EAC) where the migration will be managed.


Step 17: Initiate a New Migration Batch

In the Exchange Admin Center dashboard, locate the Migration option in the left-hand navigation pane. Click it to open the migration management screen. From the top menu bar, select Add migration batch to launch the configuration wizard.


Step 18: Define Batch Name and Path

The Add migration batch wizard will open. First, enter a unique name for your migration (e.g., "GWorkspace_Migration") in the Migration batch name field. Next, locate the Select the mailbox migration path dropdown menu and choose Migration to Exchange Online. Click Next to proceed.


Step 19: Select Migration Type

On the Migration type screen, you will be presented with several options. Select Google Workspace (Gmail) migration from the list to specify the source environment. Click Next to continue to the prerequisites check.


Step 20: Configure Migration Prerequisites

The Prerequisites for Google Workspace migration window will now appear. You must choose how to prepare your Google Workspace environment for the data transfer. You have two options:
  • Automate the configuration: Let Microsoft 365 handle the setup automatically (Recommended).
  • Manually configure: Upload your own JSON key file and configure settings manually (Advanced).
 

Automate the Configuration of Your Google Workspace for Migration

Step 21: Initiate Automated Configuration

Under the Automate the configuration of your Google Workspace for migration section, click on the Start button. This will trigger the automated setup process which handles the necessary permissions and API connections for you.

Important Requirement: Before proceeding, ensure you have the Super Admin credentials for the Google Workspace tenant you are migrating from. The automated configuration tool requires these elevated privileges to establish the necessary connections and permissions.


Step 22: Retrieve Client ID, Scopes, and Private Key

Once the automation process finishes, the wizard will display your client ID and the required API Scopes. A direct link will also be provided to the Google Workspace Admin console, where you must add these scopes to authorize the connection.

Important: A Private Key (JSON file) will automatically download to your computer. Save this file securely, as it serves as the credential key for the migration endpoint.


Step 23: Configure Domain-Wide Delegation

In the Microsoft 365 prerequisites window, locate and click the blue Link next to the text "Click the link to add scopes for API access".

A new browser window will open, taking you to the Google Workspace Admin console. On the "Domain-wide Delegation" page, click the Add new button. You will then use the Client ID and API Scopes that were generated in the previous step to grant the necessary permissions.


Step 24: Authorize the API Connection

In the Add a new client ID pop-up window that appears in the Google Admin Console:
  • Paste the Client ID (copied from the Microsoft 365 wizard) into the Client ID field.
  • Paste the OAuth scopes (also from the wizard) into the OAuth scopes field.
  • Click the Authorize button to save the configuration and grant the necessary permissions.


Manually Configure Google Workspace for Migration

If the automated tool is not an option, or if you prefer full control over the security settings, you can manually configure the necessary Google Cloud components.

Step 25: Create a Google Cloud Project

  • Click on the project dropdown menu in the top navigation bar (often labeled Select a project).
  • In the pop-up window, click New Project.
  • Enter a project name (e.g., "M365-Migration-Project") and click Create.


Step 26: Configure and Create the Project

In the New Project screen that appears:
Enter a descriptive name in the Project name field (e.g., "M365-Migration-Project").
Under Location, browse and select your organization (if applicable) or leave it as "No organization".
Click the Create button to initialize the new project.



Step 27: Select the Created Project

Once the project is successfully created, a notification will appear in the top-right corner of the Google Cloud Console. Click on SELECT PROJECT within this notification to switch your active dashboard to the new project.


Step 28: Create a Service Account

  1. Navigate to the IAM & Admin section in the Google Cloud Console (or visit https://console.cloud.google.com/iam-admin ).
  2. From the left-hand menu, click on Service accounts.
  3. At the top of the main pane, click the + CREATE SERVICE ACCOUNT button.

 


Step 29: Define Service Account Details

In the Create service account form:
  1. Enter a descriptive name in the Service account name field (e.g., m365-migration-svc).
  2. The Service account ID field will populate automatically based on your entry.
  3. Click the CREATE AND CONTINUE button to save and proceed to the next step.

 


Step 30: Grant Access to the Service Account

  1. In the "Grant this service account access to project" section, click the Select a role dropdown menu.
  2. Choose Owner (under "Basic" or by searching for "Owner") to give the service account full access to the project.
  3. Click CONTINUE to apply the role.
  4. Finally, click DONE at the bottom of the page to complete the service account creation.

 


Step 31: Retrieve and Save the Client ID

After creating the service account, you will be redirected to the Service accounts list.
  1. Locate your newly created service account in the table.
  2. Find the OAuth 2 Client ID (a long string of numbers) in the corresponding column.
  3. Copy this Client ID and save it in a secure location (like a Notepad file). You will need this ID later to configure domain-wide delegation in the Google Admin console.

 


Step 32: Generate the Private Key (JSON)

  1. In the Service Accounts list, click on the email address (link) of the service account you just created.
  2. On the service account details page, click the KEYS tab in the top navigation bar.
  3. Click the ADD KEY dropdown button and select Create new key.
  4. In the pop-up window, ensure JSON is selected as the "Key type".
  5. Click CREATE. The JSON file containing your private key will automatically download to your computer.

 


Step 33: Select Key Type and Create

  1. On the service account details page, switch to the KEYS tab.
  2. Click the ADD KEY dropdown and select Create new key.
  3. A pop-up window titled "Create private key" will appear. Select JSON as the Key type.
  4. Click the CREATE button. The file will download immediately.


Step 34: Save the Downloaded Key

After clicking Create, a confirmation window will appear stating that the Private key saved to your computer. The JSON file will automatically download to your browser's default download location.

Crucial: This is the only time you can view or download this specific private key. If you lose it, you will need to generate a new one.


Step 35: Enable Required Google Workspace APIs

To allow the migration tool to access and move your data, you must manually enable three specific APIs in your project.
1. Navigate to the API Library:
2. Enable the following three APIs: Repeat the search and enable process for each of these services:
  • Gmail API
  • Google Calendar API
  • Google People API
  • Google Contacts API

Note: If an API is already enabled, the button will say "Manage" instead of "Enable". You can simply move to the next one.


Step 36: Add Domain-Wide Delegation

  1. Open a new tab and log in to the Google Admin Console (admin.google.com).
  2. Navigate to the Domain-wide Delegation page. You can use this direct link: https://admin.google.com/ac/owl/domainwidedelegation . (Alternatively, go to Security > Access and data control > API controls > Manage Domain Wide Delegation).
  3. Click the Add new button to register your service account.



Step 37: Enter Client ID and Authorize Scopes

In the Add a new client ID popup window that appears:
  1. Client ID: Paste the long numeric string you saved earlier (from Step 7).
  2. OAuth scopes: Copy and paste the exact list of scopes below into this field: https://mail.google.com,https://www.googleapis.com/auth/contacts,https://www.googleapis.com/auth/calendar,https://www.googleapis.com/auth/gmail.settings.sharing,https://www.google.com/m8/feeds
  3. Click the Authorize button.

 


You have now successfully completed all the required configurations in the Google Cloud Console and Google Workspace Admin Console (Service Account, APIs, and Domain-Wide Delegation).
  • Leave the Google tabs open (just in case you need to copy values again).
  • Switch your browser tab back to the Microsoft 365 Exchange Admin Center.
  • You should still be on the Add migration batch wizard where you left off.

 

Step 38: Complete Prerequisites Check

Back in the Microsoft 365 Exchange Admin Center:
  1. Verify that you are on the Prerequisites for Google Workspace migration page.
  2. Since you have manually completed all the listed tasks (Service Account creation, API enablement, and Domain-wide delegation) in the previous steps, you can now proceed.
  3. Click the Next button at the bottom of the screen.


Step 39: Create a New Migration Endpoint

  1. The Set a migration endpoint window will now appear.
  2. Select the option: Create a new migration endpoint.
  3. Click Next.


Step 40: Configure Endpoint General Information

  1. On the General information page, locate the Migration endpoint name field.
  2. Type a unique name for this endpoint (e.g., GWorkspace_Endpoint).
  3. Leave the Max concurrent migrations and Max concurrent incremental syncs fields at their default values (unless you have specific reasons to change them).
  4. Click Next.


Step 41: Configure Google Workspace Connection

1. On the Google Workspace configuration page:
  • Email address: Enter the email address of the Google Workspace Super Admin (this must be the account used to create the Service Account).
  • JSON key: Click the button to upload (often labeled Choose File or Import) and select the .json file you downloaded to your computer earlier.
2. Once the file is uploaded and the email is entered, click Next.


Step 42: Confirm Endpoint Creation

  1. You should now see a confirmation or the list of endpoints showing your new Migration Endpoint has been created successfully.
  2. Click Next to proceed to the user selection stage.


Step 43: Create the User Migration CSV File

You need to create a simple CSV file that tells Microsoft 365 which users to migrate. Since we are using the Service Account (API) method, you do not need user passwords.
  1. Open Excel or a plain text editor (like Notepad).
  2. In the first row (A1), enter the exact header: Email Address (Note: Ensure there are no spaces in the header).
  3. In the rows below, list the Microsoft 365 email addresses for every user you want to migrate in this batch.
  4. Save the file as a .csv (Comma Separated Values) file (e.g., migration_users.csv).


Step 44: Import the User List (CSV)

  1. On the Add user details page of the wizard, select the option to Manually upload a CSV file.
  2. Click the Choose File (or "Browse") button.
  3. Select the .csv file you just created (e.g., migration_users.csv).
  4. Once the file is uploaded and validated, click Next to proceed.


Step 45: Configure Migration Settings

1. On the Move configuration window:
  • Target delivery domain: Enter your Microsoft 365 routing domain (typically yourcompany.onmicrosoft.com). This ensures email routing works correctly during the migration.
  • Select items to migrate: Check the boxes for the data types you want to move:
  1. Mail
  2. Calendar
  3. Contacts (You can also select Rules if available/needed).
2. Click Next to proceed.


Step 46: Schedule and Start the Migration Batch

1. On the Schedule batch migration page, configure the final settings:
  • Send a report to: By default, your admin email is selected. You can add other recipients who should receive the final status report.
  • Start the migration batch: Select Automatically start the batch (or "Automatically processing the batch").
  • End the migration batch: Select Automatically complete the migration batch.
  • Time zone: Select your local time zone from the dropdown menu to ensure reports and schedules align with your time.
2. Click the Save button to finalize the wizard and begin the migration process.


Step 47: Finalize Batch Creation

  1. Wait for Processing: The system will take a moment to process your request.
  2. Confirmation: You will see a status message indicating Batch creation successful.
  3. Action: Click the Done button to close the wizard.
Note: After clicking Done, you will be returned to the migration dashboard where you can monitor the progress of your new batch.


Step 48: Monitor Migration Progress

  1. After clicking "Done," you will be redirected to the main Migration dashboard.
  2. Locate your batch in the list. You will see the Status column change to Syncing.
  3. Wait for Completion: The migration process will now copy data from Google Workspace to Microsoft 365. This can take significant time depending on the size of the mailboxes. You do not need to keep the window open; the process runs in the background.


Step 49: Verify Migration Completion

  1. Wait for Data Transfer: As mentioned, this process will take time depending on the size of the mailboxes (ranging from minutes for test accounts to hours or days for large organizations).
  2. Refresh Status: Periodically click the Refresh button (circular arrow icon) in the toolbar to update the list.
  3. Confirm Completion: Once the data transfer is finished, the Status column will change from "Syncing" to Synced or Completed (depending on the batch finalization settings you chose).


We have successfully completed the full migration from Google Workspace to Microsoft 365. I hope this article has helped you understand how to perform a seamless Google Workspace migration using the automated batch method.

Important Post-Migration Reminder: Now that your data has been migrated, the final step is to update your MX Records in your DNS settings to point to Microsoft 365. This is critical to ensure that all new incoming emails are delivered directly to your new Microsoft 365 mailboxes instead of Google Workspace.

Update MX Records to Route Mail to Microsoft 365

While your old emails have been migrated, new emails may still be going to Google Workspace. To switch the flow of new emails to Microsoft 365, you must update your domain's MX (Mail Exchange) records.

To finalize the mail flow, you need to update your DNS records. Microsoft 365 provides a wizard to help you with this.
  1. Open the Microsoft 365 Admin Center.
  2. In the left-hand navigation pane, go to Settings > Domains.
  3. Click on your specific domain name (e.g., yourcompany.com) to open its details.
  4. Click on the Manage DNS (or Continue setup) button to view the required records.


Choose DNS Connection Method

  • After clicking "Manage DNS," you will see the Connection options (or "How do you want to connect your domain?") page.
  • Select the radio button for Add your own DNS records.
Note: This option allows you to manually copy the required MX, SPF, and CNAME records and paste them into your domain registrar (e.g., GoDaddy, Cloudflare, Namecheap). This is often safer than allowing Microsoft to access your DNS settings automatically.
  • Click Continue.

Update DNS Records

  • The wizard will now display a list of the specific records you need to add to your domain registrar (MX, CNAME, and TXT/SPF).
  • Action: Log in to your domain registrar's website (e.g., GoDaddy, Namecheap) and create the new records exactly as shown on the screen.
  • MX Record: Directs email to Microsoft 365.
  • CNAME (Autodiscover): Helps Outlook and mobile apps find the server automatically.
  • TXT (SPF): Authorizes Microsoft 365 to send email on your behalf.
  • Once you have added all the values in your registrar, return to this window and click Continue.

Conclusion

Mission Accomplished!

By following this step-by-step procedure, you have successfully migrated your organization from Google Workspace to Microsoft 365. You’ve handled everything from setting up the Google Service Account to configuring the final DNS records.

Your users can now log in to their new Microsoft 365 accounts with all their historical emails, contacts, and calendars ready to go.