Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
.DS_Store
__pycache__/
*.pyc
*.pyo
*/__pycache__/
__pycache__/
*/*/__pycache__/
*/*/test_*
.env
env/
outputs/
output/
reports/
certificate/
data/
test_*.py

Expand Down
38 changes: 15 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,27 +185,25 @@ Please follow the [official Microsoft guide](https://learn.microsoft.com/en-us/p

To scan your tenant, you need to register an app in the Microsoft Entra ID (formerly Azure AD) portal.

> **💡 AUTOMATED SETUP AVAILABLE:** You can completely automate Steps 1-3 below (App Creation, API Permissions, Admin Consent, and Secret Generation) by running the provided PowerShell script `app_creation_script.ps1` after [downloading the tool from github repository](#running-the-tool). Simply execute `.\scripts\app_creation_script.ps1` from your PowerShell terminal or right click and select "Run with Powershell" and follow the prompts thereafter. Note that this requires the Microsoft Graph PowerShell module. If you prefer manual setup, follow the steps below.

> **💡 AUTOMATED SETUP AVAILABLE:** You can completely automate Steps 1-3 below (App Creation, API Permissions, Admin Consent, and Secret Generation) by running the provided PowerShell script. Simply execute `.\scripts\app_creation_script.ps1` from your PowerShell terminal and follow the prompts. Note that this requires the Microsoft Graph PowerShell module. If you prefer manual setup, follow the steps below.
In order to execute the script right click and select "Run with Powershell"

### 1. Register the App
1. Go to [portal.azure.com](https://portal.azure.com/).
2. Navigate to **Microsoft Entra ID > App registrations > New registration**.
3. Name the app (e.g., "Deal Assistant Tool").
4. Select **"Accounts in this organizational directory only"** (Single Tenant).
5. Click **Register**.
6. **(Required for Delegated Auth Flows)**: Go to **Manage > Authentication**. Under **Redirect URI configuration**, click **Add Redirect URI** -> **Web**. Add `http://localhost` as the redirect URI.
6. **(Required for Delegated Auth & eDiscovery)**: Go to **Authentication**. Under **Redirect URI configuration**, click **Add Redirect URI** -> **Web**. Add `http://localhost` as the redirect URI.

### 2. API Permissions
In your new app, go to **API permissions > Add a permission**, and assign permissions based on the workloads you plan to scan. *Don't forget to click **"Grant admin consent"** after adding these permissions.*

#### 2.1. Deal Assistant Telemetry Permissions ([Usage and Adoption](#introduction))
#### 2.1. Deal Assistant Telemetry Permissions (Usage and Adoption)
The Usage and Adoption tab performs extensive tenant auditing. While the following permissions are recommended for a complete report, you may choose to grant only a subset.
**NOTE: Be aware that any missing permissions will simply cause the tool to gracefully skip those specific telemetry sections.**

**2.1.1. Microsoft APIs > Microsoft Graph:**

**<u>Application permissions</u>**:
**2.1.1. Microsoft Graph API Application permissions:**
* `Reports.Read.All`: Used to retrieve active user trends, mailbox/SharePoint usage reports, M365 Apps, and Email Client usage.
* `Directory.Read.All`: Used to read tenant organization configuration data, Domain, User, and Group summaries.
* `Policy.Read.All`: Required for Conditional Access & Authentication mechanics.
Expand All @@ -222,22 +220,18 @@ The Usage and Adoption tab performs extensive tenant auditing. While the followi
* `SensitivityLabels.Read.All`: to read all sensitivity labels
* `Application.Read.All`: Required to retrieve App Registrations directory details and Service Principal SSO configurations.

**<u>Delegated permissions</u>**
**2.1.2. Microsoft Graph API Delegated permissions:**
* `eDiscovery.Read.All`: Required to retrieve active/closed Microsoft Purview eDiscovery cases on behalf of the user.
* `Policy.Read.All`: Required to retrieve Mobile Device Management (MDM) Policies on behalf of the user.
* `offline_access`: required to maintain access to data you have given the app access to

> **Note** : When [running the deal assistant tool](#running-the-tool) with [Delegated Authentication Flow enabled](#1-delegated-authentication-flow), the user who logs in must be an eDiscovery administrator for these permissions to work in the deal assistant app.

**2.1.2. APIs my organization uses > Office 365 Exchange Online**

**Application Permissions:**
* `Exchange.ManageAsApp`: required to read data governance and security policies (sensitive, information types, exchange connectors etc)
* `Exchange.ManageAsAppV2`: required to read data governance and security policies (sensitive, information types, exchange connectors etc)
> **Note** : The user who logs in must be an eDiscovery administrator.

#### 2.2. Migration Planner
**2.1.3. Office 365 Exchange Online Application Permissions (under `APIs my organization uses`):**
* `Exchange.ManageAsApp`: required to read data governance ans security policies (sensitive, information types, exchange connectors etc)
* `Exchange.ManageAsAppV2`: required to read data governance ans security policies (sensitive, information types, exchange connectors etc)

**Microsoft APIs > Microsoft Graph > Application Permissions**
#### 2.2. Migration Planner Permissions (Microsoft Graph Application Permissions)

**2.2.1. Shared Core Permissions**
* `User.Read.All` (To list users)
Expand Down Expand Up @@ -311,7 +305,7 @@ You will need three values for the tool:
## Advanced Authentication & Setup (Deal Assistant)

### 1. Delegated Authentication Flow
Certain features like **eDiscovery** require **Delegated Authentication**. On the login screen of deal assistant, you can check the box to enable Delegated Authentication.
Certain features like **eDiscovery** require **Delegated Authentication**. On the login screen, you can check the box to enable Delegated Authentication.
> **⚠️ IMPORTANT WARNING**: If you enable this, your Entra App Registration MUST have `http://localhost` registered as a redirect URI. Otherwise, the interactive browser login popup will fail!

### 2. PowerShell & Certificate-Based Authentication
Expand Down Expand Up @@ -346,12 +340,10 @@ A standard Client Secret cannot authorize PowerShell modules—a certificate is

Upon launching, you are greeted with the **Unified Login Screen**:
* Enter your **Tenant ID**, **Client ID**, and **Client Secret**.
* Select if you wish to use [Delegated Authentication](#1-delegated-authentication-flow).
* Click **Connect & Continue**. Follow any [certificate upload instructions](#2-powershell--certificate-based-authentication) if prompted.
* Select if you wish to use **Delegated Authentication**.
* Click **Connect & Continue**. Follow any certificate upload instructions if prompted.

Once authenticated, the tool provides a left-hand navigation sidebar with three main tabs:
* **[Usage and Adoption](#tab-1-usage-and-adoption)**
* **[Migration Planner](#tab-2-migration-planner)**
Once authenticated, the tool provides a left-hand navigation sidebar with two main tabs:

## Tab 1: Usage and Adoption
This tab loads the M365 Telemetry dashboard.
Expand Down
4 changes: 1 addition & 3 deletions core/graph/network_security/conditional_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,10 @@ def run_conditional_access_pipeline(
if is_cancelled_callback and is_cancelled_callback(): break
resp = session.get(url, headers=headers)
if resp.status_code == 200:
data = resp.json() or {}
data = resp.json()
value_list = data.get("value", [])

for p in value_list:
if not p:
continue
p["name"] = p.get("displayName") or p.get("name") or "N/A"
p["state"] = p.get("state") or "N/A"

Expand Down
4 changes: 1 addition & 3 deletions core/graph/security/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,10 @@ def run_authentication_pipeline(
if is_cancelled_callback and is_cancelled_callback(): break
resp = session.get(url, headers=headers)
if resp.status_code == 200:
data = resp.json() or {}
data = resp.json()
value_list = data.get("value", [])
if writer:
for p in value_list:
if not p:
continue
name = p.get("displayName", "N/A")
state = p.get("state", "N/A")

Expand Down
41 changes: 1 addition & 40 deletions deal_assistant.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,5 @@
# Copyright 2026 Google LLC

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# https://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
The Deal Assistant is a comprehensive desktop application designed to help deployment partners and IT administrators assess a Microsoft 365 tenant before migration. It is split into two primary modules:

Usage and Adoption: A deep telemetry and discovery module providing insights into a tenant's directory, license usage, security governance, and endpoint management.
Migration Planner: Independently assess Exchange Online (Emails, Contacts, Calendars), Files in OneDrive / SharePoint, or Microsoft Teams to provide volume metrics and generate optimized Migration Batch Plans with estimated completion times.

DISCLAIMER
==========
The estimations provided by this tool are calculated projections intended for
preliminary planning only.
Actual migration timelines (ETAs) and batch execution may vary based on
real-time network conditions, source/target throttling policies, migration
configurations, and the volume of delta migrations.
The estimations do not constitute a performance guarantee or a binding service
level agreement (SLA).
Please note that this release is an experimental feature with limited and
evolving functionality.
Partners and/or Customers should NOT use this product for production projects
where business outcomes rely on the product functionality or timelines.
Partners and/or Customers should NOT commit hard migration timelines based on
this product.
By using this tool, you acknowledge that log files generated by this script
contain personally identifiable information and that you are solely responsible
for securing your local environment and these files.
Use of this tool is governed by the Apache 2.0 license.

"""
"""Standalone application for the License Usage and Telemetry view."""

import os
import pandas as pd
Expand Down
25 changes: 0 additions & 25 deletions docs/m365_telemetry_scaling_skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,6 @@ graph TD
backoff=backoff_val
)
```
4. **API Payload Null Safety**:
- Never assume keys returned by Microsoft Graph API or PowerShell scripts are always non-null. If a key's value is explicitly set to `null` (None) in the API payload, `dict.get("key", {})` returns `None` (bypassing the default `{}` argument).
- Chaining lookups on this (e.g. `p.get("grantControls", {}).get("builtInControls")`) will crash with `AttributeError: 'NoneType' object has no attribute 'get'`.
- **Rule**: Always use the falsy check operator `or` to define defaults when chaining dictionary queries:
```python
grant_controls = p.get("grantControls") or {}
controls = grant_controls.get("builtInControls") or []
```

---

Expand Down Expand Up @@ -314,23 +306,6 @@ Follow these rules to prevent missing data in the exported PDF:
- Ensure the structure of data retrieved (e.g., list of dictionaries, flat rows, or tuples) matches exactly what the PDF builder (`telemetry/pdf_report.py`) expects.
- If `pdf_report.py` expects nested objects (e.g. `closedBy` containing `user`), ensure the data returned by `.last_data` or `load_csv()` matches this format or parse it accordingly.

5. **XML/HTML Parsing Safety inside Paragraphs**:
- ReportLab `Paragraph` flowables attempt to parse content strings as basic XML/HTML markup. Raw HTML tags (like `<div style="...">` or `<span style="...">` found in disclaimers/descriptions) or unescaped characters (like `&`, `<`, or `>`) will cause ReportLab to abruptly crash with `xml.parsers.expat.ExpatError` or `ValueError: findSpanStyle not implemented in this parser`.
- **Rule**: Always wrap **all** dynamic string variables retrieved from Graph or PowerShell inside `escape_text(val)` before constructing a `Paragraph`.
- **Important Pitfall**: When using Python f-strings, be sure to escape the variables inside the string (e.g. `Paragraph(f"Error: {escape_text(err)}")`), or escape the entire dynamically composed string if no static HTML is present. Static layout formatting (like `<b>` or `<br/>`) must remain outside the `escape_text()` call.

6. **Isolated Section Rendering Fail-safes**:
- To prevent a single query or formatting error in one section from failing the entire report download, all major PDF sections must be wrapped in independent `try...except` blocks.
- If an exception occurs, use `logger.exception("Failed to format [Section Name] section in PDF")` to write the full traceback to the telemetry logs, and append a warning paragraph to the `story` using `section_err_style`:
```python
try:
# Build section...
story.append(section_table)
except Exception as e:
logger.exception("Failed to format Feature section in PDF")
story.append(Paragraph(f"⚠️ Error formatting Feature section: {escape_text(str(e))}", section_err_style))
```

---

## 8. Documentation Update Guidelines
Expand Down
Loading