diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md new file mode 100644 index 00000000000..cc4786b2d57 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md @@ -0,0 +1,252 @@ +# Migrate from the Log Analytics HTTP Data Collector API to the Log Ingestion API + +This guide describes how to migrate an existing Exchange Security Insights (ESI) deployment from the **legacy Log Analytics HTTP Data Collector API** (workspace ID + shared key) to the **Azure Monitor Log Ingestion API** (DCE + DCR + Entra ID identity). + +> [!IMPORTANT] +> Microsoft has announced the retirement of the Log Analytics HTTP Data Collector API. All ESI deployments must switch to the Log Ingestion API before the end of support. New tables use `_CL` schemas with typed columns and are managed through Data Collection Rules (DCR). + +## Why migrate + +- The legacy API relies on the workspace shared key, which is a long-lived secret. The Log Ingestion API uses **Entra ID identities** (service principal with certificate or system-assigned **managed identity**). +- The Log Ingestion API supports **DCR-based transforms** — the ESI templates now use `parse_json` to extract typed sub-columns (for example the new `Identity_*` columns) directly at ingestion. +- Legacy tables are auto-provisioned with generic string columns; DCR-based tables have explicit typed schemas, enabling better queries, cost control, and retention management. +- The legacy API endpoint is **deprecated** and will stop accepting new data at end of support. + +## Migration overview + +The migration is a three-step process. You can perform all three steps back-to-back or spread them over multiple maintenance windows because the collector supports both APIs during the transition (`SentinelLogIngestionAPIActivated` toggle). + +```text +Step 1 Step 2 Step 3 +Upgrade script Deploy Azure infrastructure Update collector configuration +CollectExchSecIns.ps1 -> DCE + DCRs + tables (ARM template) -> JSON / WinformConfig +(v8.0.0.0 or higher) + role assignments +``` + +| Step | Duration | Impact on production | +|------|----------------|-------------------------------------------------------| +| 1 | Short | None until the config file is switched (Step 3). | +| 2 | Short | None. New tables run in parallel with legacy tables. | +| 3 | Short | Ingestion path switches to the Log Ingestion API. | + +> [!TIP] +> Perform steps 1 and 2 in advance. Step 3 is the actual cutover and can be scheduled during a low-traffic window. + +## Prerequisites + +- Owner or Contributor on the target resource group. +- Access to the Log Analytics workspace hosting the current ESI tables. +- Ability to update the Automation Account `GlobalConfiguration` variable or the on-premises configuration file. +- (Recommended) Access to the [WinformConfig editor](./Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md) to edit `CollectExchSecConfiguration.json` interactively. + +## Step 1 — Upgrade the collector to v8.0.0.0 or higher + +The **v8.0.0.0** release of `CollectExchSecIns.ps1` adds native support for the Log Ingestion API through the `SentinelLogIngestionAPIActivated` switch, DCE/DCR endpoint properties, certificate-based authentication, and managed identity for Azure Automation. + +> [!IMPORTANT] +> Earlier versions of the collector only speak the legacy Log Analytics API. They cannot post to a DCE endpoint even if the configuration is updated. Upgrade the script **before** switching the configuration. + +### Azure Automation runbook + +1. Download the latest `CollectExchSecIns.ps1` (v8.0.0.0 or higher) from [the ESI script location](https://aka.ms/ESI-ExchangeCollector-Script). +2. Open the Automation Account, navigate to **Runbooks**, and select your ESI runbook. +3. Click **Edit** and replace the script content with the new version. +4. **Publish** the runbook. +5. Verify the runbook runs end-to-end with the existing (legacy) configuration. The behavior at this stage is unchanged — the collector still uses the legacy API because `SentinelLogIngestionAPIActivated` is still `false`. + +### On-premises / hybrid collector (server or scheduled task) + +1. Download the latest `CollectExchSecIns.ps1` (v8.0.0.0 or higher). +2. Replace the existing script file on the collector host (typical path: `C:\ESI\` or the location referenced by the scheduled task). +3. Run one manual execution to confirm the upgrade succeeded. Nothing else should change. + +Once running v8.0.0.0, the collector emits a runtime warning banner whenever it still uses the legacy Log Analytics API, reminding operators to complete the migration. This banner disappears after Step 3. + +## Step 2 — Deploy the Azure resources (DCE, DCRs, tables) + +The ARM template `azuredeploy_ESI_LogIngestionAPI.json` provisions everything you need: + +- 1 Data Collection Endpoint (DCE) +- Up to 3 custom Log Analytics tables (each optional): + - `ESIAPIExchangeOnPremConfig_CL` — Exchange On-Premises configuration. + - `ESIAPIExchangeOnlineConfig_CL` — Exchange Online configuration. + - `ExchangeOnlineMessageTracking_CL` — Message tracking. +- Up to 3 Data Collection Rules (one per table). + +> [!NOTE] +> The new tables live **alongside** the legacy `ESIExchangeConfig_CL` / `ExchangeOnlineMessageTracking_CL` tables. You can keep the legacy tables for historical queries or delete them after the migration. + +### 2.1 Deploy the template + +Deploy with Azure CLI, PowerShell, or the Azure Portal. Full instructions and parameter reference are in [README_LogIngestionAPI.md](./README_LogIngestionAPI.md). + +Minimal PowerShell deployment (deploy everything, defaults): + +```powershell +New-AzResourceGroupDeployment ` + -ResourceGroupName "rg-sentinel-esi" ` + -TemplateFile ".\ExchSecIns\Deployments\azuredeploy_ESI_LogIngestionAPI.json" ` + -workspaceName "law-sentinel-prod" +``` + +Deploy only the parts you need using the master switches: + +- Online-only tenant: set `deployOnPremConfigTable=false`. +- On-premises only: set `deployOnlineConfigTable=false` and `deployMessageTrackingTable=false`. +- Reuse existing tables: set `deployTables=false`. + +### 2.2 Collect the deployment outputs + +Record these values — they go into the collector configuration in Step 3: + +- `dataCollectionEndpointUri` +- `dataCollectionRuleOnPremisesConfigImmutableId` (if deployed) +- `dataCollectionRuleOnlineConfigImmutableId` (if deployed) +- `dataCollectionRuleMessageTrackingImmutableId` (if deployed) + +### 2.3 Set up the ingestion identity + +Choose one of the two authentication modes: + +- **Entra ID application + certificate** — recommended for on-premises and server scenarios. Follow the full procedure in [README_AzureMonitorSetup.md](./README_AzureMonitorSetup.md#step-1-create-a-self-signed-certificate). +- **System-assigned managed identity** — recommended for Azure Automation runbooks. Enable the system-assigned identity on the Automation Account. + +### 2.4 Assign the ingestion role + +Grant **Monitoring Metrics Publisher** to the identity on **each DCR** it will send data to. + +```powershell +$sp = Get-AzADServicePrincipal -ApplicationId "YOUR_APP_ID" # or the Automation Account MI + +New-AzRoleAssignment ` + -ObjectId $sp.Id ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions//resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" +# Repeat for DCR-ESI-OnPremisesConfig and DCR-ESI-MessageTracking as needed. +``` + +> [!WARNING] +> Without this role assignment, the collector receives `403 Forbidden` from the Log Ingestion API. Verify the role is present before Step 3. + +## Step 3 — Update the collector configuration + +The configuration change is the actual cutover. Once `SentinelLogIngestionAPIActivated` is `true`, the collector stops calling the legacy API on its next execution and starts using the DCE/DCR endpoint. + +### Option A — Update the configuration through the WinformConfig editor (recommended) + +The WinformConfig GUI walks you through the relevant fields, hides legacy fields (`WorkspaceId`, `WorkspaceKey`) once `SentinelLogIngestionAPIActivated` is set, and validates the payload before saving. + +1. From the ESI repository, open a PowerShell console and launch the editor: + + ```powershell + pwsh -ExecutionPolicy Bypass ` + -File .\ExchSecIns\WinformConfig\SetupCollectExchSecConfiguration.ps1 ` + -ConfigPath .\ExchSecIns\CollectExchSecConfiguration.json + ``` + +2. In the **Log Collection** section: + - Set `SentinelLogIngestionAPIActivated` to `True`. + - Fill `DataCollectionEndpointURI` with the DCE URI from Step 2.2. + - Fill `DCRImmutableId` with the DCR immutable ID matching the table you target (Online, OnPremises, or MessageTracking). + - Choose the authentication mode: + - **Managed identity** (Azure Automation): set `UseManagedIdentity` to `True`. + - **Certificate**: set `UseManagedIdentity` to `False`, then fill `TargetLogTenantID`, `TargetLogAppID`, and `TargetLogCertificateThumbprint`. + - `WorkspaceId` and `WorkspaceKey` become hidden and can stay as-is (they are ignored once `SentinelLogIngestionAPIActivated` is `True`). +3. Review the **Raw JSON preview** and the **diff summary**. +4. Save the file. If the collector runs from Azure Automation, upload the new JSON to the `GlobalConfiguration` variable. + +### Option B — Update the configuration file manually + +Edit `CollectExchSecConfiguration.json` and update the `LogCollection` section: + +```json +{ + "LogCollection": { + "ActivateLogUpdloadToSentinel": "true", + "SentinelLogIngestionAPIActivated": "true", + "DataCollectionEndpointURI": "https://..ingest.monitor.azure.com", + "DCRImmutableId": "dcr-00000000000000000000000000000000", + "UseManagedIdentity": "false", + "TargetLogTenantID": "", + "TargetLogAppID": "", + "TargetLogCertificateThumbprint": "", + "TargetLogAppSecretReference": "", + "LogTypeName": "ESIExchangeConfig" + } +} +``` + +Key rules: + +- `SentinelLogIngestionAPIActivated` **must** be `"true"` (string). +- `DCRImmutableId` must correspond to the DCR routing to the target table (choose `OnPremisesConfigImmutableId`, `OnlineConfigImmutableId`, or `MessageTrackingImmutableId` from the deployment outputs). +- `LogTypeName` maps to the stream name (`Custom-` is sent to the DCR). Use `ESIExchangeConfig`, `ESIExchangeOnlineConfig`, or `ExchangeOnlineMessageTracking`. +- For Azure Automation with certificate auth, import the certificate into the Automation Account certificate store and populate `TargetLogAppSecretReference` with the automation variable name that stores the certificate reference. +- `WorkspaceId` / `WorkspaceKey` are no longer used; leave them empty or remove them for clarity. + +### 3.1 Push the configuration + +- **Azure Automation**: open the Automation Account, navigate to **Variables**, edit `GlobalConfiguration`, and paste the updated JSON. Publish any linked runbook if required. +- **Server-based collector**: replace the local `CollectExchSecConfiguration.json` used by the scheduled task or manual invocation. + +### 3.2 Validate the cutover + +1. Trigger a manual run of the collector. +2. Confirm the runtime log no longer displays the **legacy API warning banner** (see below). +3. Query the new tables in Log Analytics: + + ```kql + ESIAPIExchangeOnlineConfig_CL + | where TimeGenerated > ago(1h) + | summarize count() by Section_s + ``` + +4. Optional: query the legacy tables to confirm ingestion has stopped, then plan their decommissioning. + +## Runtime warning banner (legacy API still in use) + +Starting with v8.0.0.0, the collector displays a highly visible warning at the start of every execution when it detects the legacy Log Analytics API is still in use. Sample output: + +```text +================================================================================ +!! WARNING: Legacy Log Analytics HTTP Data Collector API is still in use. +!! The API is deprecated by Microsoft and will be retired. +!! Please migrate to the Azure Monitor Log Ingestion API before end of support. +!! Migration guide: ESI-PublicContent/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md +================================================================================ +``` + +The banner clears after Step 3 is applied and `SentinelLogIngestionAPIActivated` is `true` in the effective configuration. + +## Post-migration housekeeping + +- Remove the workspace shared key from any password vault / secure store used for the legacy configuration. +- Update any monitoring / alerts to point to the new `_CL` tables. +- Update Sentinel analytic rules, hunting queries, and workbooks that reference the legacy tables. New sub-columns such as `Identity_DistinguishedName_s` or `Identity_ObjectGuid_g` unlock queries that were not possible with the legacy schema. +- After a full retention cycle, plan the decommissioning of the legacy tables through the Log Analytics workspace UI. + +## Rollback + +If a critical issue is discovered, the transition is reversible: + +1. Set `SentinelLogIngestionAPIActivated` back to `"false"` in `CollectExchSecConfiguration.json` (or via the WinformConfig editor). +2. Reinstate the legacy `WorkspaceId` / `WorkspaceKey` values. +3. Trigger the collector. + +The collector will resume publishing to the legacy tables. The DCE, DCRs, and new tables remain in place for a later retry. + +## Troubleshooting + +| Symptom | Likely cause | Action | +|------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|----------------------------------------------------------------------------------------------| +| The runtime banner still appears after step 3 | Old configuration cached, or the runbook still uses the previous JSON | Re-check `SentinelLogIngestionAPIActivated`, republish the runbook. | +| HTTP 403 from the Log Ingestion API | Missing role assignment on the DCR | Assign **Monitoring Metrics Publisher** on the correct DCR. | +| HTTP 400 `InvalidPayload` | Column type mismatch or stream name mismatch | Verify `DCRImmutableId` and `LogTypeName` match the target DCR / stream. | +| No data in the new tables | Wrong immutable ID (targets a different DCR) | Reconcile immutable IDs from the deployment outputs. | +| Payload rejected `PayloadTooLarge` | Segment > 1 MB | Lower `Advanced.MaximalSentinelPacketSizeMb` (default is `0.9` when the new API is enabled). | + +## Related documentation + +- ARM template and parameters: [README_LogIngestionAPI.md](./README_LogIngestionAPI.md) +- End-to-end Azure Monitor setup (Entra ID app, certificate, RBAC): [README_AzureMonitorSetup.md](./README_AzureMonitorSetup.md) +- WinformConfig editor: [ExchSecIns/WinformConfig/Readme.md](./WinformConfigReadme.md) diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/QUICKSTART-Forwarder.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/QUICKSTART-Forwarder.md new file mode 100644 index 00000000000..713775e585e --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/QUICKSTART-Forwarder.md @@ -0,0 +1,272 @@ +# 🚀 Quick Start Guide - Forwarder Pickup Processor + +## Quick Installation in 5 Minutes + +### 1️⃣ Prerequisites (2 minutes) + +```powershell +# Install Azure modules (if using Log Ingestion API) +Install-Module -Name Az.Accounts, Az.Monitor -Force -Scope CurrentUser + +# Verify installation +Get-Module -Name Az.Accounts -ListAvailable +``` + +### 2️⃣ Configuration (1 minute) + +```powershell +# Navigate to scripts folder +cd "C:\ESI\Scripts" + +# Copy configuration file +Copy-Item "Config\ForwarderPickupConfig.json" "Config\ForwarderPickupConfig-BACKUP.json" + +# Edit configuration +notepad "Config\ForwarderPickupConfig.json" +``` + +**Minimum configuration to modify:** + +```json +{ + "SentinelConnection": { + "UseLogIngestionAPI": true, + "DataCollectionEndpointURI": "https://VOTRE-DCE.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-VOTRE-DCR-ID", + "TenantID": "VOTRE-TENANT-ID", + "ApplicationId": "VOTRE-APP-ID", + "CertificateThumbprint": "VOTRE-CERT-THUMBPRINT" + } +} +``` + +### 3️⃣ Configuration Test (30 seconds) + +```powershell +# Test configuration +.\Test-ForwarderSetup.ps1 + +# Test with test file creation +.\Test-ForwarderSetup.ps1 -CreateTestFile + +# Test Azure connection +.\Test-ForwarderSetup.ps1 -TestConnection +``` + +### 4️⃣ Scheduled Task Installation (1 minute) + +```powershell +# Simple installation (15 minute interval) +.\Install-ForwarderScheduledTask.ps1 + +# With SYSTEM account (for Managed Identity) +.\Install-ForwarderScheduledTask.ps1 -UseSystemAccount + +# With service account +.\Install-ForwarderScheduledTask.ps1 -ServiceAccount "DOMAIN\svc-esi" -IntervalMinutes 10 + +# Customized +.\Install-ForwarderScheduledTask.ps1 ` + -ScriptPath "C:\ESI\Scripts\ForwarderPickupProcessor.ps1" ` + -ConfigPath "C:\ESI\Scripts\Config\ForwarderPickupConfig.json" ` + -IntervalMinutes 5 +``` + +### 5️⃣ Manual Test (30 seconds) + +```powershell +# Execute manually once +.\ForwarderPickupProcessor.ps1 + +# Check logs +Get-Content "C:\ESI\Logs\ForwarderProcessor_*.log" | Select-Object -Last 50 +``` + +--- + +## ✅ Deployment Checklist + +- [ ] PowerShell modules installed +- [ ] JSON configuration edited with your values +- [ ] Configuration test successful (0 errors) +- [ ] Folders created (Pickup, Archive, Error, Logs) +- [ ] Certificate installed (if certificate authentication) +- [ ] Permissions configured on DCR/Workspace +- [ ] CollectExchSecIns.ps1 configured in forwarder mode +- [ ] Scheduled task created +- [ ] Manual test successful +- [ ] Data visible in Sentinel + +--- + +## 🔍 Quick Verification + +### Check that forwarder is working: + +```powershell +# Count pending files +(Get-ChildItem "C:\ESI\ForwarderPickup" -Filter "*.json").Count + +# Count archived files +(Get-ChildItem "C:\ESI\Archive" -Filter "*.json").Count + +# Last task execution +Get-ScheduledTask -TaskName "ESI Forwarder Processor" | Get-ScheduledTaskInfo +``` + +### Check in Sentinel: + +```kql +// View data from last 24 hours +ESIExchangeConfig_CL +| where TimeGenerated > ago(24h) +| summarize Count=count() by bin(TimeGenerated, 1h) +| render timechart +``` + +--- + +## 🆘 Quick Troubleshooting + +### Problem: Files remain in Pickup + +```powershell +# Check logs +Get-Content "C:\ESI\Logs\ForwarderProcessor_*.log" | Select-String "Error|Failed" + +# Check error files +Get-ChildItem "C:\ESI\Error" + +# Execute manually with verbose +.\ForwarderPickupProcessor.ps1 -Verbose +``` + +### Problem: Authentication error + +```powershell +# Check certificate +Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq "YOUR-THUMBPRINT"} + +# Test Azure connection +Connect-AzAccount -CertificateThumbprint "THUMBPRINT" -Tenant "TENANT" -ApplicationId "APPID" +``` + +### Problem: Data doesn't appear in Sentinel + +```powershell +# Wait 10-15 minutes for first ingestion +# Check DCR +Get-AzDataCollectionRule -Name "YourDCR" + +# Check DCR streams +$dcr = Get-AzDataCollectionRule -Name "YourDCR" +$dcr.DataFlows +``` + +--- + +## 📱 Useful Commands + +```powershell +# Start task manually +Start-ScheduledTask -TaskName "ESI Forwarder Processor" + +# View task status +Get-ScheduledTask -TaskName "ESI Forwarder Processor" | Format-List * + +# Stop task +Stop-ScheduledTask -TaskName "ESI Forwarder Processor" + +# Disable temporarily +Disable-ScheduledTask -TaskName "ESI Forwarder Processor" + +# Re-enable +Enable-ScheduledTask -TaskName "ESI Forwarder Processor" + +# Remove task +Unregister-ScheduledTask -TaskName "ESI Forwarder Processor" -Confirm:$false +``` + +--- + +## 🎯 Recommended Production Configuration + +```json +{ + "PickupFolder": "C:\\ESI\\ForwarderPickup", + "ArchiveFolder": "D:\\ESI\\Archive", + "ErrorFolder": "D:\\ESI\\Error", + "LogFolder": "D:\\ESI\\Logs", + "DeleteAfterProcessing": false, + "MaxFilesPerRun": 100, + "FilePattern": "*.json", + "SentinelConnection": { + "UseManagedIdentity": true, + "UseLogIngestionAPI": true, + "DataCollectionEndpointURI": "https://prod-dce.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-xxxxx", + "MaxSegmentSizeMb": 0.9 + } +} +``` + +**Scheduled task:** Every 10-15 minutes +**Archive retention:** 30 days minimum +**Monitoring:** Alerts if ErrorFolder > 10 files + +--- + +## 📞 Support + +- **Logs** : `C:\ESI\Logs\ForwarderProcessor_*.log` +- **Erreurs** : `C:\ESI\Error\` +- **Documentation** : `README-ForwarderPickup.md` +- **Contact** : nilepagn@microsoft.com + +--- + +## 💡 Tips + +### Automatically purge old archives: + +```powershell +# Add to scheduled task or create separate task +Get-ChildItem "C:\ESI\Archive" -Filter "*.json" | + Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)} | + Remove-Item -Force +``` + +### Monitoring with PowerShell: + +```powershell +# Monitoring script to run every hour +$pickupCount = (Get-ChildItem "C:\ESI\ForwarderPickup" -Filter "*.json").Count +$errorCount = (Get-ChildItem "C:\ESI\Error" -Filter "*.json").Count + +if ($pickupCount -gt 50) { + Write-Warning "Too many pending files: $pickupCount" +} + +if ($errorCount -gt 10) { + Write-Error "Too many errors: $errorCount files" +} +``` + +### Quick statistics: + +```powershell +# Number of files processed today +$today = Get-Date -Format "yyyy-MM-dd" +$processed = (Get-ChildItem "C:\ESI\Archive" | + Where-Object {$_.LastWriteTime.ToString("yyyy-MM-dd") -eq $today}).Count +Write-Host "Files processed today: $processed" + +# Total size +$totalSize = (Get-ChildItem "C:\ESI\Archive" -Recurse | + Measure-Object -Property Length -Sum).Sum / 1MB +Write-Host "Data processed: $([Math]::Round($totalSize, 2)) MB" +``` + +--- + +**🎉 You're ready! The forwarder is now operational.** diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README-ForwarderPickup.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README-ForwarderPickup.md new file mode 100644 index 00000000000..9a61e7232c3 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README-ForwarderPickup.md @@ -0,0 +1,437 @@ +# Forwarder Pickup Processor + +## 📋 Overview + +The **Forwarder Pickup Processor** is a standalone PowerShell script that processes JSON files generated by `CollectExchSecIns.ps1` (forwarder mode) and injects them into Microsoft Sentinel. + +### Main Features + +- ✅ **Support for both APIs**: Log Analytics API (classic) and Log Ingestion API (DCR-based) +- ✅ **Batch processing**: Processes multiple files in a single execution +- ✅ **Automatic segmentation**: Splits large files into segments compliant with API limits +- ✅ **Robust error handling**: Archives successful files, isolates error files +- ✅ **Complete logging**: Detailed trace of all operations +- ✅ **Flexible authentication**: Managed Identity, Service Principal, or Interactive +- ✅ **Proxy support**: Proxy configuration for secure environments +- ✅ **Intelligent table name extraction**: Deduces log type from filename + +--- + +## 🚀 Installation and Configuration + +### 1. Prerequisites + +#### Required PowerShell modules: +```powershell +# Azure Az module (for Log Ingestion API) +Install-Module -Name Az.Accounts -Force -Scope CurrentUser +Install-Module -Name Az.Monitor -Force -Scope CurrentUser + +# Verify installation +Get-Module -Name Az.Accounts -ListAvailable +``` + +#### Required Azure permissions: + +**For Log Ingestion API (DCR-based):** +- Role: **Monitoring Metrics Publisher** on the Data Collection Rule (DCR) +- Service Principal or Managed Identity configured + +**For Log Analytics API (classic):** +- Workspace ID and Primary/Secondary Key from Log Analytics Workspace + +### 2. JSON File Configuration + +Edit `Config\ForwarderPickupConfig.json`: + +```json +{ + "PickupFolder": "C:\\ESI\\ForwarderPickup", + "ArchiveFolder": "C:\\ESI\\Archive", + "ErrorFolder": "C:\\ESI\\Error", + "LogFolder": "C:\\ESI\\Logs", + "DeleteAfterProcessing": false, + "MaxFilesPerRun": 0, + "FilePattern": "*.json", + "SentinelConnection": { + "UseManagedIdentity": false, + "UseLogIngestionAPI": true, + "DataCollectionEndpointURI": "https://your-dce.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "TenantID": "your-tenant-id", + "ApplicationId": "your-app-id", + "CertificateThumbprint": "THUMBPRINT", + "DefaultLogType": "ESIExchangeConfig", + "MaxSegmentSizeMb": 0.9 + } +} +``` + +### 3. CollectExchSecIns.ps1 Configuration + +In your `CollectExchSecConfiguration.json` configuration, enable forwarder mode: + +```json +{ + "SentinelLogCollector": { + "ActivateLogUpdloadToSentinel": true, + "UseForwarder": true, + "ForwarderPickupPath": "C:\\ESI\\ForwarderPickup", + "TogetherMode": false + } +} +``` + +--- + +## 📖 Usage + +### Manual Execution + +#### With default configuration: +```powershell +.\ForwarderPickupProcessor.ps1 +``` + +#### With command-line parameters: +```powershell +.\ForwarderPickupProcessor.ps1 ` + -PickupFolder "C:\ESI\ForwarderPickup" ` + -ArchiveFolder "C:\ESI\Archive" ` + -MaxFilesPerRun 50 +``` + +#### Delete files after processing: +```powershell +.\ForwarderPickupProcessor.ps1 -DeleteAfterProcessing +``` + +#### With proxy: +```powershell +.\ForwarderPickupProcessor.ps1 ` + -UseProxy ` + -ProxyUrl "http://proxy.contoso.com:8080" +``` + +### Scheduling + +#### With Windows Task Scheduler: + +1. **Create task**: +```powershell +$action = New-ScheduledTaskAction ` + -Execute "PowerShell.exe" ` + -Argument "-NoProfile -ExecutionPolicy Bypass -File `"C:\ESI\ForwarderPickupProcessor.ps1`"" + +$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 15) -RepetitionDuration ([TimeSpan]::MaxValue) + +$principal = New-ScheduledTaskPrincipal -UserId "DOMAIN\ServiceAccount" -LogonType Password + +$settings = New-ScheduledTaskSettingsSet ` + -AllowStartIfOnBatteries ` + -DontStopIfGoingOnBatteries ` + -StartWhenAvailable ` + -RunOnlyIfNetworkAvailable + +Register-ScheduledTask ` + -TaskName "ESI Forwarder Processor" ` + -Action $action ` + -Trigger $trigger ` + -Principal $principal ` + -Settings $settings ` + -Description "Processes pickup files and sends to Sentinel every 15 minutes" +``` + +2. **With Managed Identity (Azure VM)**: +```powershell +# Task runs under SYSTEM account which has access to Managed Identity +$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount +``` + +#### With Azure Automation (Runbook): + +```powershell +# Import script to Azure Automation +$AutomationAccountName = "YourAutomationAccount" +$ResourceGroupName = "YourResourceGroup" +$RunbookName = "ForwarderPickupProcessor" + +Import-AzAutomationRunbook ` + -Name $RunbookName ` + -Path ".\ForwarderPickupProcessor.ps1" ` + -ResourceGroupName $ResourceGroupName ` + -AutomationAccountName $AutomationAccountName ` + -Type PowerShell + +# Publish runbook +Publish-AzAutomationRunbook ` + -Name $RunbookName ` + -ResourceGroupName $ResourceGroupName ` + -AutomationAccountName $AutomationAccountName + +# Create schedule +$Schedule = New-AzAutomationSchedule ` + -Name "Every15Minutes" ` + -StartTime (Get-Date).AddMinutes(5) ` + -DayInterval 1 ` + -ResourceGroupName $ResourceGroupName ` + -AutomationAccountName $AutomationAccountName ` + -TimeZone "Romance Standard Time" + +# Associate with runbook +Register-AzAutomationScheduledRunbook ` + -Name $RunbookName ` + -ScheduleName "Every15Minutes" ` + -ResourceGroupName $ResourceGroupName ` + -AutomationAccountName $AutomationAccountName +``` + +--- + +## 🔧 Script Parameters + +| Parameter | Type | Description | Default | +|-----------|------|-------------|---------| +|-----------|------|-------------|--------| +| `ConfigurationFile` | string | Path to JSON configuration file | `.\Config\ForwarderPickupConfig.json` | +| `PickupFolder` | string | Folder containing files to process | Value from config file | +| `ArchiveFolder` | string | Archive folder for successful files | Value from config file | +| `ErrorFolder` | string | Folder for error files | Value from config file | +| `MaxFilesPerRun` | int | Max files per execution (0=unlimited) | 0 | +| `DeleteAfterProcessing` | switch | Delete instead of archive | false | +| `UseProxy` | switch | Enable proxy usage | false | +| `ProxyUrl` | string | Proxy URL | "" | + +--- + +## 📁 Folder Structure + +``` +C:\ESI\ +├── ForwarderPickup\ # JSON files deposited by CollectExchSecIns.ps1 +│ ├── ESIExchangeConfig-2026-03-24-10-30-00.json +│ └── ESIExchangeConfig-Page_1-2026-03-24-10-30-00.json +├── Archive\ # Successfully processed files +│ └── ESIExchangeConfig-2026-03-24-10-30-00.json +├── Error\ # Error files +│ └── ESIExchangeConfig-corrupted.json +└── Logs\ # Forwarder execution logs + └── ForwarderProcessor_20260324_103000.log +``` + +--- + +## 🏷️ Table Name Extraction + +The script intelligently extracts the Sentinel table name from the filename: + +| Filename | Sentinel Table | +|----------------|----------------| +|----------------|----------------| +| `ESIExchangeConfig-2026-03-24.json` | `ESIExchangeConfig_CL` | +| `ESIExchangeConfig-Page_1-2026-03-24.json` | `ESIExchangeConfig_CL` | +| `MyCustomTable-abc123.json` | `MyCustomTable_CL` | +| `data.json` | `ESIExchangeConfig_CL` (default) | + +> **Note**: The `_CL` suffix (Custom Log) is automatically added by Azure + +--- + +## 🔐 Authentication Configuration + +### 1. Managed Identity (recommended for Azure VM/Automation) + +```json +{ + "SentinelConnection": { + "UseManagedIdentity": true, + "UseLogIngestionAPI": true, + "DataCollectionEndpointURI": "https://xxx.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-xxx" + } +} +``` + +**Role assignment**: +```powershell +# Get Managed Identity Object ID +$vmName = "YourVMName" +$rgName = "YourResourceGroup" +$vm = Get-AzVM -Name $vmName -ResourceGroupName $rgName +$principalId = $vm.Identity.PrincipalId + +# Assign role on DCR +$dcrResourceId = "/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.Insights/dataCollectionRules/{dcr-name}" +New-AzRoleAssignment ` + -ObjectId $principalId ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope $dcrResourceId +``` + +### 2. Service Principal with certificate + +```json +{ + "SentinelConnection": { + "UseManagedIdentity": false, + "UseLogIngestionAPI": true, + "TenantID": "your-tenant-id", + "ApplicationId": "your-app-id", + "CertificateThumbprint": "ABC123...", + "DataCollectionEndpointURI": "https://xxx.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-xxx" + } +} +``` + +**Certificate installation**: +```powershell +# Import certificate to local store +$certPath = "C:\Certs\SentinelApp.pfx" +$certPassword = ConvertTo-SecureString "YourPassword" -AsPlainText -Force +Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\LocalMachine\My -Password $certPassword +``` + +### 3. Log Analytics API classic + +```json +{ + "SentinelConnection": { + "UseLogIngestionAPI": false, + "WorkspaceId": "12345678-1234-1234-1234-123456789012", + "WorkspaceKey": "YourPrimaryOrSecondaryKey==", + "MaxSegmentSizeMb": 31.9 + } +} +``` + +--- + +## 📊 Monitoring and Diagnostics + +### Execution Logs + +Logs are stored in the configured folder (`LogFolder`): + +``` +[2026-03-24 10:30:00] [Info] === Forwarder Pickup Processor v1.0.0 === +[2026-03-24 10:30:00] [Info] Started at: 2026-03-24 10:30:00 +[2026-03-24 10:30:01] [Info] Found 5 file(s) to process +[2026-03-24 10:30:05] [Success] Data successfully uploaded to Log Ingestion API +[2026-03-24 10:30:05] [Success] Successfully processed file: ESIExchangeConfig.json +[2026-03-24 10:30:10] [Success] === Processing Summary === +[2026-03-24 10:30:10] [Success] Total files processed: 5 +[2026-03-24 10:30:10] [Success] Failed files: 0 +[2026-03-24 10:30:10] [Success] Total data processed: 12.5 MB +``` + +### Verification in Sentinel + +```kql +// Check ingested data +ESIExchangeConfig_CL +| where TimeGenerated > ago(1h) +| summarize count() by bin(TimeGenerated, 5m) +| render timechart + +// Check data by environment +ESIExchangeConfig_CL +| where TimeGenerated > ago(24h) +| summarize RecordCount=count(), LastSeen=max(TimeGenerated) by ESIEnvironment +``` + +--- + +## ⚠️ Troubleshooting + +### Error: "Failed to connect to Azure" + +**Cause**: Authentication problem + +**Solution**: +- Check that certificate is installed and accessible +- Check Managed Identity permissions +- Test manually: `Connect-AzAccount` + +### Error: "Upload payload is too big" + +**Cause**: File too large for a segment + +**Solution**: +- Script automatically segments +- Check `MaxSegmentSizeMb` in config (0.9 for DCR, 31.9 for Log Analytics) + +### Files remain in Pickup folder + +**Cause**: Unhandled errors or script not scheduled + +**Solution**: +- Check logs in `LogFolder` +- Check files in `ErrorFolder` +- Execute manually to diagnose + +### Data doesn't appear in Sentinel + +**Cause**: Table doesn't exist or DCR misconfigured + +**Solution**: +- For DCR: Check that stream is configured in DCR +- For Log Analytics: Table creates automatically (wait 10-15 min) +- Check permissions on DCR/Workspace + +--- + +## 🔄 Complete Workflow + +```mermaid +graph TD + A[CollectExchSecIns.ps1] -->|Forwarder Mode| B[JSON Files in Pickup] + B --> C[ForwarderPickupProcessor.ps1] + C --> D{Read file} + D --> E{Extract LogType} + E --> F{Size > Limit?} + F -->|No| G[Send single segment] + F -->|Yes| H[Split into segments] + H --> I[Send each segment] + G --> J{Success?} + I --> J + J -->|Yes| K[Archive file] + J -->|No| L[Move to Error] + K --> M[Next file] + L --> M + M --> N{More files?} + N -->|Yes| D + N -->|No| O[Display summary] +``` + +--- + +## 📝 Best Practices + +1. **Scheduling**: Execute every 5-15 minutes for regular flow +2. **Cleanup**: Archive and purge old files periodically +3. **Monitoring**: Monitor logs and Error folder +4. **Testing**: Test first with `MaxFilesPerRun=1` before production +5. **Security**: Use Managed Identity when possible +6. **Performance**: Adjust `MaxSegmentSizeMb` according to API used + +--- + +## 📞 Support + +For questions or issues: +- Check logs in `LogFolder` folder +- Consult Microsoft Sentinel documentation +- Contact ESI team: nilepagn@microsoft.com + +--- + +## 📜 Version History + +### v1.0.0 (2026-03-24) +- ✨ Initial version +- ✅ Log Analytics API and Log Ingestion API support +- ✅ Automatic segment management +- ✅ Archiving and error handling +- ✅ Complete logging +- ✅ Managed Identity and Service Principal support diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README.md index d968fd364a0..20f588e5025 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README.md +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README.md @@ -3,19 +3,40 @@ This folder contains documentations related to the deployment of solutions Microsoft Exchange Security for Exchange On-Premises and Microsoft Exchange Security for Exchange Online : ## Overview of Deployement -[Deployment Overview](./Deployment-Overview.md) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/Deployment-Overview.md ## Deployment Microsoft Exchange Security for Exchange On-Premises -[Deployment for On-Premises Solution](./Deployment-MES-OnPremises.md) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/Deployment-MES-OnPremises.md ## Deployment Microsoft Exchange Security for Exchange Online -[Deployment for Online Solution](./Deployment-MES-Online.md) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/Deployment-MES-Online.md ## Collectors Information -[Collector Information](./ESICollector.md) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/ESICollector.md + +## Deploy the Azure Monitor Log Ingestion API (DCE / DCR / custom tables) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/README_LogIngestionAPI.md + +## Azure Monitor End-to-End Setup (Entra ID application, certificate, RBAC) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/README_AzureMonitorSetup.md + +## Migrate from the Log Analytics HTTP Data Collector API to the Log Ingestion API +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md + +## WinformConfig editor for CollectExchSecConfiguration.json +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/WinformConfigReadme.md + +## Forwarder / Pickup deployment +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/README-ForwarderPickup.md + +## Forwarder quick start +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/QUICKSTART-Forwarder.md ## How to deploy Woorkbooks -[Solution Workbooks information](./WorkbookDeployement.md) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/WorkbookDeployement.md + +## Workbook delegation +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/WorkbookDelegation.md ## VIP Management -[VIP Management](./VIPManagement.md) \ No newline at end of file +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/VIPManagement.md \ No newline at end of file diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README_AzureMonitorSetup.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README_AzureMonitorSetup.md new file mode 100644 index 00000000000..4765da77ad0 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README_AzureMonitorSetup.md @@ -0,0 +1,544 @@ +# Azure Monitor Log Ingestion API Setup for Exchange Security Insights + +This guide covers the full setup of Azure Monitor components required by the ESI Collector to ingest Exchange security data into Microsoft Sentinel using the Log Ingestion API. + +> [!IMPORTANT] +> The Log Ingestion API replaces the legacy Log Analytics HTTP Data Collector API (deprecated in 2025). All new deployments should use the Azure Monitor Ingestion API. + +## Architecture Overview + +The ingestion pipeline consists of these Azure components: + +```text +ESI Collector Script + │ + ▼ + Entra ID App Registration ──(certificate auth)──► Azure AD Token + │ + ▼ + Data Collection Endpoint (DCE) + │ + ▼ + Data Collection Rule (DCR) ──(transform + route)──► Log Analytics Custom Tables + │ + ▼ + Microsoft Sentinel Workspace +``` + +| Component | Purpose | +|-----------|---------| +| Entra ID Application | Authenticates the collector to the ingestion endpoint | +| Self-signed Certificate | Credential for the application (preferred over secrets) | +| Data Collection Endpoint (DCE) | HTTPS endpoint receiving log payloads | +| Data Collection Rule (DCR) | Defines schema, transforms, and routing to tables | +| Custom Log Analytics Tables | Store the collected Exchange configuration data | + +## Prerequisites + +Before you begin, verify these requirements: + +- An Azure subscription with Contributor access on the target resource group +- A Log Analytics workspace (where Microsoft Sentinel is enabled) +- PowerShell 7.0+ with the following modules installed: + - `Az.Accounts` + - `Az.Monitor` + - `Az.Resources` + - `Microsoft.Graph` (if creating the Entra ID Application via script) +- Azure CLI (alternative for deployment and permission management) +- OpenSSL or PowerShell `New-SelfSignedCertificate` for certificate generation + +## Step 1: Create a Self-Signed Certificate + +The ESI Collector authenticates to Azure using a certificate-based credential. You can use an existing PKI certificate or generate a self-signed one. + +### Option A: PowerShell (Windows) + +```powershell +# Create a self-signed certificate valid for 2 years +$cert = New-SelfSignedCertificate ` + -Subject "CN=ESI-Collector-Auth" ` + -CertStoreLocation "Cert:\CurrentUser\My" ` + -KeyExportPolicy Exportable ` + -KeySpec Signature ` + -KeyLength 2048 ` + -HashAlgorithm SHA256 ` + -NotAfter (Get-Date).AddYears(2) + +# Display the thumbprint (you need it for configuration) +Write-Host "Certificate Thumbprint: $($cert.Thumbprint)" + +# Export the public key (.cer) for uploading to Entra ID Application +Export-Certificate -Cert $cert -FilePath ".\ESI-Collector-Auth.cer" -Type CERT + +# (Optional) Export the PFX for Azure Automation import +$pwd = ConvertTo-SecureString -String "YourStrongPassword" -Force -AsPlainText +Export-PfxCertificate -Cert $cert -FilePath ".\ESI-Collector-Auth.pfx" -Password $pwd +``` + +### Option B: OpenSSL (Linux / macOS) + +```bash +# Generate private key and self-signed certificate (2 years) +openssl req -x509 -newkey rsa:2048 \ + -keyout esi-collector-key.pem \ + -out esi-collector-cert.pem \ + -sha256 -days 730 \ + -subj "/CN=ESI-Collector-Auth" \ + -nodes + +# Convert to PFX for Azure Automation +openssl pkcs12 -export \ + -out esi-collector.pfx \ + -inkey esi-collector-key.pem \ + -in esi-collector-cert.pem + +# Get the thumbprint +openssl x509 -in esi-collector-cert.pem -noout -fingerprint -sha1 \ + | sed 's/://g' | cut -d= -f2 +``` + +> [!NOTE] +> Record the certificate thumbprint. You need it when configuring the collector and when running from a server. For Azure Automation, import the PFX into the Automation Account certificate store. + +## Step 2: Register an Entra ID Application + +The application identity enables the collector to obtain tokens for the Azure Monitor ingestion endpoint. + +### Option A: Azure Portal + +1. Navigate to **Microsoft Entra ID** > **App registrations** > **New registration** +2. Set the display name to `ESI-Collector-LogIngestion` (or your preferred name) +3. Leave the redirect URI empty (no interactive sign-in needed) +4. Click **Register** +5. Note the **Application (client) ID** and **Directory (tenant) ID** from the Overview page +6. Navigate to **Certificates & secrets** > **Certificates** > **Upload certificate** +7. Upload the `.cer` file generated in Step 1 +8. Create a **Service Principal** (automatic when registering via portal) + +### Option B: PowerShell with Microsoft Graph + +```powershell +# Connect to Microsoft Graph +Connect-MgGraph -Scopes "Application.ReadWrite.All" + +# Create the application +$app = New-MgApplication -DisplayName "ESI-Collector-LogIngestion" + +# Create the service principal +$sp = New-MgServicePrincipal -AppId $app.AppId + +# Upload the certificate +$certContent = [System.IO.File]::ReadAllBytes(".\ESI-Collector-Auth.cer") +$base64Cert = [System.Convert]::ToBase64String($certContent) + +$keyCredential = @{ + Type = "AsymmetricX509Cert" + Usage = "Verify" + Key = [System.Convert]::FromBase64String($base64Cert) + DisplayName = "ESI-Collector-Auth" +} + +Update-MgApplication -ApplicationId $app.Id -KeyCredentials @($keyCredential) + +# Display information needed for configuration +Write-Host "Application (Client) ID: $($app.AppId)" +Write-Host "Object ID: $($app.Id)" +Write-Host "Service Principal Object ID: $($sp.Id)" +``` + +### Option C: Azure CLI + +```bash +# Create the application with the certificate +az ad app create --display-name "ESI-Collector-LogIngestion" + +# Get the App ID +APP_ID=$(az ad app list --display-name "ESI-Collector-LogIngestion" --query "[0].appId" -o tsv) + +# Create a service principal +az ad sp create --id $APP_ID + +# Upload the certificate +az ad app credential reset --id $APP_ID --cert @esi-collector-cert.pem --append + +echo "Application (Client) ID: $APP_ID" +``` + +> [!TIP] +> No API permissions are required on the Entra ID Application itself. Access control is handled through Azure RBAC on the Data Collection Rule. + +## Step 3: Deploy the Data Collection Endpoint, Rules, and Tables + +The ARM template in the `Deployments` folder creates all required Azure Monitor resources: + +- 1 Data Collection Endpoint (DCE) +- Up to 3 Custom Log Analytics tables (each controlled by a master switch): + - `ESIAPIExchangeOnPremConfig_CL` (Exchange On-Premises configuration) + - `ESIAPIExchangeOnlineConfig_CL` (Exchange Online configuration) + - `ExchangeOnlineMessageTracking_CL` (message tracking) +- Up to 3 Data Collection Rules (one per table): + - `DCR-ESI-OnPremisesConfig` + - `DCR-ESI-OnlineConfig` + - `DCR-ESI-MessageTracking` + +> [!TIP] +> Master parameters `deployTables`, `deployDataCollection`, `deployOnPremConfigTable`, `deployOnlineConfigTable`, and `deployMessageTrackingTable` let you deploy any subset. See [README_LogIngestionAPI.md](README_LogIngestionAPI.md) for the full parameter reference. + +### Option A: Using Azure CLI + +```bash +# Login and set subscription +az login +az account set --subscription "YOUR_SUBSCRIPTION_ID" + +# Create a resource group (if needed) +az group create --name "rg-sentinel-esi" --location "eastus" + +# Deploy the template +az deployment group create \ + --resource-group "rg-sentinel-esi" \ + --template-file azuredeploy_ESI_LogIngestionAPI.json \ + --parameters azuredeploy_ESI_LogIngestionAPI.parameters.json +``` + +### Option B: Using PowerShell + +```powershell +# Login and set subscription +Connect-AzAccount +Set-AzContext -SubscriptionId "YOUR_SUBSCRIPTION_ID" + +# Create a resource group (if needed) +New-AzResourceGroup -Name "rg-sentinel-esi" -Location "eastus" + +# Deploy the template +New-AzResourceGroupDeployment ` + -ResourceGroupName "rg-sentinel-esi" ` + -TemplateFile "azuredeploy_ESI_LogIngestionAPI.json" ` + -TemplateParameterFile "azuredeploy_ESI_LogIngestionAPI.parameters.json" +``` + +### Option C: Using Azure Portal + +1. Navigate to **Deploy a custom template** in the Azure Portal +2. Click **Build your own template in the editor** +3. Paste the content of `azuredeploy_ESI_LogIngestionAPI.json` +4. Fill in the parameters: + + | Parameter | Description | Example | + |-----------|-------------|---------| + | `workspaceName` | Name of your existing Log Analytics workspace | `law-sentinel-prod` | + | `location` | Azure region matching your workspace | `eastus` | + | `dataCollectionEndpointName` | Name for the DCE | `DCE-ESI-LogIngestion` | + | `dataCollectionRuleOnPremisesConfigName` | Name for the On-Premises Config DCR | `DCR-ESI-OnPremisesConfig` | + | `dataCollectionRuleOnlineConfigName` | Name for the Online Config DCR | `DCR-ESI-OnlineConfig` | + | `dataCollectionRuleMessageTrackingName` | Name for the Message Tracking DCR | `DCR-ESI-MessageTracking` | + | `retentionInDays` | Data retention period (30-730) | `90` | + | `deployTables` | Deploy the custom Log Analytics tables | `true` | + | `deployDataCollection` | Deploy the DCE and DCRs | `true` | + | `deployOnPremConfigTable` | Deploy the On-Premises config resources | `true` | + | `deployOnlineConfigTable` | Deploy the Exchange Online config resources | `true` | + | `deployMessageTrackingTable` | Deploy message tracking resources | `true` | + +5. Click **Review + create** + +### Collect Deployment Outputs + +After deployment, retrieve these values from the outputs: + +```powershell +$deployment = Get-AzResourceGroupDeployment ` + -ResourceGroupName "rg-sentinel-esi" ` + -Name "YOUR_DEPLOYMENT_NAME" + +# Values needed for configuration +$deployment.Outputs.dataCollectionEndpointUri.Value # DCE URI +$deployment.Outputs.dataCollectionRuleOnPremisesConfigImmutableId.Value # DCR Immutable ID (On-Premises Config) +$deployment.Outputs.dataCollectionRuleOnlineConfigImmutableId.Value # DCR Immutable ID (Online Config) +$deployment.Outputs.dataCollectionRuleMessageTrackingImmutableId.Value # DCR Immutable ID (Message Tracking) +``` + +Or via Azure CLI: + +```bash +az deployment group show \ + --resource-group "rg-sentinel-esi" \ + --name "YOUR_DEPLOYMENT_NAME" \ + --query properties.outputs +``` + +> [!IMPORTANT] +> Record the **DCE URI** and **DCR Immutable IDs** from the deployment outputs. You need these values for both the permission assignment and the collector configuration. + +## Step 4: Assign Permissions on the Data Collection Rule + +The Entra ID Application (service principal) requires the **Monitoring Metrics Publisher** role on each Data Collection Rule it sends data to. + +### Option A: Azure CLI + +```bash +# Get the service principal Object ID +SP_OBJECT_ID=$(az ad sp list --filter "appId eq 'YOUR_APP_ID'" --query "[0].id" -o tsv) + +# Assign on On-Premises Config DCR (if deployed) +az role assignment create \ + --role "Monitoring Metrics Publisher" \ + --assignee-object-id "$SP_OBJECT_ID" \ + --assignee-principal-type ServicePrincipal \ + --scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnPremisesConfig" + +# Assign on Online Config DCR (if deployed) +az role assignment create \ + --role "Monitoring Metrics Publisher" \ + --assignee-object-id "$SP_OBJECT_ID" \ + --assignee-principal-type ServicePrincipal \ + --scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" + +# Assign on Message Tracking DCR (if deployed) +az role assignment create \ + --role "Monitoring Metrics Publisher" \ + --assignee-object-id "$SP_OBJECT_ID" \ + --assignee-principal-type ServicePrincipal \ + --scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-MessageTracking" +``` + +### Option B: PowerShell + +```powershell +# Get the service principal Object ID +$sp = Get-AzADServicePrincipal -ApplicationId "YOUR_APP_ID" + +# Assign on On-Premises Config DCR (if deployed) +New-AzRoleAssignment ` + -ObjectId $sp.Id ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnPremisesConfig" + +# Assign on Online Config DCR (if deployed) +New-AzRoleAssignment ` + -ObjectId $sp.Id ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" + +# Assign on Message Tracking DCR (if deployed) +New-AzRoleAssignment ` + -ObjectId $sp.Id ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-MessageTracking" +``` + +### Option C: Azure Portal + +1. Navigate to **Monitor** > **Data Collection Rules** +2. Select your DCR (e.g., `DCR-ESI-OnlineConfig`) +3. Go to **Access control (IAM)** > **Add role assignment** +4. Select role **Monitoring Metrics Publisher** +5. Under Members, select **User, group, or service principal** +6. Search for your application name (`ESI-Collector-LogIngestion`) +7. Click **Review + assign** +8. Repeat for the On-Premises Config DCR and the Message Tracking DCR if deployed + +> [!WARNING] +> Without the **Monitoring Metrics Publisher** role on the DCR, the collector receives `403 Forbidden` errors when ingesting data. Verify the assignment before testing. + +## Step 5: Configure the ESI Collector + +Update the `CollectExchSecConfiguration.json` file with the values collected during setup. + +### Log Collection Section + +```json +{ + "LogCollection": { + "ActivateLogUpdloadToSentinel": "true", + "SentinelLogIngestionAPIActivated": "true", + "DataCollectionEndpointURI": "https://YOUR-DCE-NAME.region.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-00000000000000000000000000000000", + "UseManagedIdentity": "false", + "TargetLogTenantID": "YOUR-TENANT-ID", + "TargetLogAppID": "YOUR-APPLICATION-CLIENT-ID", + "TargetLogCertificateThumbprint": "YOUR-CERTIFICATE-THUMBPRINT", + "LogTypeName": "ESIExchangeConfig", + "CSVOutputFile": "ExchSecIns.csv", + "ExportDomainsInformation": "True" + } +} +``` + +| Field | Value Source | +|-------|-------------| +| `DataCollectionEndpointURI` | Deployment output: `dataCollectionEndpointUri` | +| `DCRImmutableId` | Deployment output matching the target table: `dataCollectionRuleOnPremisesConfigImmutableId`, `dataCollectionRuleOnlineConfigImmutableId`, or `dataCollectionRuleMessageTrackingImmutableId` | +| `TargetLogTenantID` | Entra ID > Overview > Tenant ID | +| `TargetLogAppID` | Entra ID > App registrations > Application (client) ID | +| `TargetLogCertificateThumbprint` | Certificate thumbprint from Step 1 | + +> [!NOTE] +> `DCRImmutableId` must correspond to the DCR routing to the table you want to target (on-premises, online, or message tracking). Set the ESI collector `LogTypeName` accordingly (`ESIExchangeConfig`, `ESIExchangeOnlineConfig`, or `ExchangeOnlineMessageTracking`). + +### Azure Automation Execution (Managed Identity) + +When running from Azure Automation with a managed identity, set `UseManagedIdentity` to `"true"` and assign the **Monitoring Metrics Publisher** role to the Automation Account's system-assigned managed identity on each DCR. + +```json +{ + "LogCollection": { + "SentinelLogIngestionAPIActivated": "true", + "UseManagedIdentity": "true", + "DataCollectionEndpointURI": "https://YOUR-DCE-NAME.region.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-00000000000000000000000000000000" + } +} +``` + +```powershell +# Assign Monitoring Metrics Publisher to the Automation Account's managed identity +$automationMI = (Get-AzAutomationAccount -ResourceGroupName "rg-automation" -Name "aa-esi-collector").Identity.PrincipalId + +# Repeat this assignment for every DCR you send to (OnPremises, Online, MessageTracking). +New-AzRoleAssignment ` + -ObjectId $automationMI ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" +``` + +## Step 6: Validate the Setup + +### Test Authentication + +```powershell +# Connect using the certificate +Connect-AzAccount ` + -CertificateThumbprint "YOUR_CERTIFICATE_THUMBPRINT" ` + -ApplicationId "YOUR_APP_ID" ` + -Tenant "YOUR_TENANT_ID" ` + -ServicePrincipal + +# Get a token for the Monitor endpoint +$context = Get-AzContext +$token = (Get-AzAccessToken -ResourceUrl "https://monitor.azure.com").Token + +Write-Host "Token acquired successfully" -ForegroundColor Green +``` + +### Send a Test Payload + +```powershell +$dceUri = "https://YOUR-DCE-NAME.region.ingest.monitor.azure.com" +$dcrImmutableId = "dcr-00000000000000000000000000000000" +$streamName = "Custom-ESIExchangeOnlineConfig" + +$uri = "$dceUri/dataCollectionRules/$dcrImmutableId/streams/$($streamName)?api-version=2023-01-01" + +$testData = @( + @{ + TimeGenerated = (Get-Date).ToUniversalTime().ToString("o") + EntryDate = (Get-Date).ToUniversalTime().ToString("o") + GenerationInstanceID = "test-validation" + ESIEnvironment = "TestEnvironment" + Section = "ValidationTest" + RawData = '{"test": true}' + } +) | ConvertTo-Json -AsArray + +$headers = @{ + "Authorization" = "Bearer $token" + "Content-Type" = "application/json" +} + +$response = Invoke-RestMethod -Uri $uri -Method Post -Body $testData -Headers $headers +Write-Host "Data sent successfully" -ForegroundColor Green +``` + +### Verify Data in Log Analytics + +Wait a few minutes, then query your workspace: + +```kql +ESIAPIExchangeOnlineConfig_CL +| where ESIEnvironment_s == "TestEnvironment" +| where Section_s == "ValidationTest" +| project TimeGenerated, ESIEnvironment_s, Section_s, GenerationInstanceID_g +``` + +## Stream Names Reference + +When the collector sends data via the Log Ingestion API, it targets these stream names: + +| Table | Stream Name | +|------------------------------------|------------------------------------------| +| `ESIAPIExchangeOnPremConfig_CL` | `Custom-ESIExchangeConfig` | +| `ESIAPIExchangeOnlineConfig_CL` | `Custom-ESIExchangeOnlineConfig` | +| `ExchangeOnlineMessageTracking_CL` | `Custom-ExchangeOnlineMessageTracking` | + +> [!NOTE] +> The on-premises and online configuration tables also expose extracted `Identity_*` sub-property columns (`Identity_Depth_d`, `Identity_DistinguishedName_s`, `Identity_ObjectGuid_g`, ...). These are populated by the DCR `transformKql` from the source `Identity` object using `parse_json`. See the full column list in [README_LogIngestionAPI.md](README_LogIngestionAPI.md#table-schemas). + +## Troubleshooting + +### Authentication Errors (401 / 403) + +1. Verify the certificate thumbprint matches the one uploaded to the Entra ID Application +2. Confirm the Tenant ID and Application ID in the configuration +3. Check that the **Monitoring Metrics Publisher** role is correctly assigned on the DCR (not the DCE or workspace) + +### DCE Not Reachable + +1. Validate the `DataCollectionEndpointURI` from the deployment outputs +2. Ensure firewall or NSG rules allow outbound HTTPS to `*.ingest.monitor.azure.com` +3. If using a proxy, configure `Useproxy` and `ProxyUrl` in the Advanced section of the configuration + +### Data Not Appearing in Tables + +1. Verify the `DCRImmutableId` corresponds to the correct DCR for the target table +2. Check that table schema columns match the stream declaration in the DCR +3. Review collector logs for payload size errors (1 MB limit per API call for Log Ingestion API) +4. Ensure `SentinelLogIngestionAPIActivated` is set to `"true"` in the configuration + +### Payload Size Errors + +The Log Ingestion API limits each request to 1 MB. The collector automatically segments larger payloads. If segmentation errors persist, reduce the `MaximalSentinelPacketSizeMb` value in the Advanced configuration section. + +## Complete Setup Checklist + +Use this checklist to track your progress: + +- [ ] Generate or obtain a certificate (Step 1) +- [ ] Record the certificate thumbprint +- [ ] Register an Entra ID Application (Step 2) +- [ ] Record the Application (Client) ID +- [ ] Record the Tenant ID +- [ ] Upload the certificate to the Application +- [ ] Deploy DCE, DCR, and tables via ARM template (Step 3) +- [ ] Record the DCE URI from deployment outputs +- [ ] Record the DCR Immutable ID(s) from deployment outputs +- [ ] Assign Monitoring Metrics Publisher role on each DCR (Step 4) +- [ ] Update `CollectExchSecConfiguration.json` with all values (Step 5) +- [ ] Validate authentication and test data ingestion (Step 6) +- [ ] Verify data appears in Log Analytics tables + +## Cleanup + +To remove all deployed resources: + +```powershell +# Remove role assignments first (repeat per DCR you assigned) +Remove-AzRoleAssignment ` + -ObjectId "SERVICE_PRINCIPAL_OBJECT_ID" ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" + +# Delete DCRs +Remove-AzDataCollectionRule -Name "DCR-ESI-OnPremisesConfig" -ResourceGroupName "rg-sentinel-esi" +Remove-AzDataCollectionRule -Name "DCR-ESI-OnlineConfig" -ResourceGroupName "rg-sentinel-esi" +Remove-AzDataCollectionRule -Name "DCR-ESI-MessageTracking" -ResourceGroupName "rg-sentinel-esi" + +# Delete DCE +Remove-AzDataCollectionEndpoint -Name "DCE-ESI-LogIngestion" -ResourceGroupName "rg-sentinel-esi" + +# Delete the Entra ID Application (optional) +Remove-MgApplication -ApplicationId "APP_OBJECT_ID" +``` + +> [!CAUTION] +> Custom Log Analytics tables cannot be deleted via API. They can only be removed through the Log Analytics workspace in the Azure Portal. Removing the workspace deletes all tables and data. diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README_LogIngestionAPI.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README_LogIngestionAPI.md new file mode 100644 index 00000000000..74b46b54922 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/README_LogIngestionAPI.md @@ -0,0 +1,323 @@ +# Exchange Security Insights - Log Ingestion API Deployment + +This ARM template deploys the Azure infrastructure required for Exchange Security Insights (ESI) data collection using the **Azure Monitor Log Ingestion API** (the modern replacement for the deprecated Log Analytics HTTP Data Collector API). + +> [!IMPORTANT] +> The legacy **Log Analytics HTTP Data Collector API** (workspace ID + shared key) is being retired by Microsoft. All new ESI deployments must use the Log Ingestion API. Existing deployments should migrate before the end-of-support date. See the migration guide: [Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md](./Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md). + +## Resources Deployed + +The template `azuredeploy_ESI_LogIngestionAPI.json` provisions the following resources into an **existing Log Analytics workspace**: + +1. **Data Collection Endpoint (DCE)** — HTTPS endpoint receiving log payloads. +2. **Custom Log Analytics Tables** (each deployment is optional via master switches): + - `ESIAPIExchangeOnPremConfig_CL` — Exchange **On-Premises** configuration data. + - `ESIAPIExchangeOnlineConfig_CL` — Exchange **Online** configuration data. + - `ExchangeOnlineMessageTracking_CL` — Message tracking logs. +3. **Data Collection Rules (DCR)** — one per table, defining schema, transform (`transformKql`), and routing: + - `DCR-ESI-OnPremisesConfig` + - `DCR-ESI-OnlineConfig` + - `DCR-ESI-MessageTracking` + +Each block is guarded by a boolean parameter so you can deploy the full stack or only a subset (for example, add on-premises collection to an existing online setup). + +## Template Parameters + +| Parameter | Type | Default | Purpose | +|-------------------------------------------|---------|----------------------------|--------------------------------------------------------------------------------------------------------------------| +| `workspaceName` | string | (required) | Name of the existing Log Analytics workspace (same subscription/resource group/region as the DCE). | +| `location` | string | `resourceGroup().location` | Azure region for the DCE and DCRs. Must match the workspace region. | +| `dataCollectionEndpointName` | string | `DCE-ESI-LogIngestion` | Name of the DCE. | +| `dataCollectionRuleOnPremisesConfigName` | string | `DCR-ESI-OnPremisesConfig` | Name of the on-premises config DCR. | +| `dataCollectionRuleOnlineConfigName` | string | `DCR-ESI-OnlineConfig` | Name of the Exchange Online config DCR. | +| `dataCollectionRuleMessageTrackingName` | string | `DCR-ESI-MessageTracking` | Name of the message tracking DCR. | +| `retentionInDays` | int | `90` (min 30, max 730) | Retention for the custom tables. | +| `deployTables` | bool | `true` | Master switch — deploy the custom Log Analytics tables. Set to `false` when the tables already exist. | +| `deployDataCollection` | bool | `true` | Master switch — deploy the DCE and DCRs. Set to `false` to only (re)deploy the tables. | +| `deployOnPremConfigTable` | bool | `true` | Deploy the on-premises config table and its DCR. | +| `deployOnlineConfigTable` | bool | `true` | Deploy the Exchange Online config table and its DCR. | +| `deployMessageTrackingTable` | bool | `true` | Deploy the message tracking table and its DCR. | + +> [!TIP] +> Use `deployTables=false` and `deployDataCollection=true` when reusing pre-existing tables (for example, after a schema-only redeployment). Use `deployTables=true` and `deployDataCollection=false` to only create or update table schemas. + +## Prerequisites + +- Azure subscription with **Contributor** (or higher) on the target resource group. +- An **existing Log Analytics workspace** (Microsoft Sentinel-enabled if you plan to detect on this data). +- Azure CLI or PowerShell with the `Az.*` modules. +- The Entra ID application (or managed identity) that will send data (see [README_AzureMonitorSetup.md](README_AzureMonitorSetup.md) for the full identity setup). + +## Deployment + +### Azure CLI + +```bash +az login +az account set --subscription "YOUR_SUBSCRIPTION_ID" + +# Create resource group (if needed) +az group create --name "rg-sentinel-esi" --location "eastus" + +az deployment group create \ + --resource-group "rg-sentinel-esi" \ + --template-file azuredeploy_ESI_LogIngestionAPI.json \ + --parameters workspaceName=law-sentinel-prod +``` + +### PowerShell + +```powershell +Connect-AzAccount +Set-AzContext -SubscriptionId "YOUR_SUBSCRIPTION_ID" + +New-AzResourceGroup -Name "rg-sentinel-esi" -Location "eastus" -Force + +New-AzResourceGroupDeployment ` + -ResourceGroupName "rg-sentinel-esi" ` + -TemplateFile "azuredeploy_ESI_LogIngestionAPI.json" ` + -workspaceName "law-sentinel-prod" +``` + +### Azure Portal + +1. Navigate to **Deploy a custom template**. +2. Select **Build your own template in the editor**. +3. Paste the content of `azuredeploy_ESI_LogIngestionAPI.json`. +4. Fill in the parameters and confirm the master switches. +5. Click **Review + create**. + +## Deployment Outputs + +| Output name | Purpose | +|---------------------------------------------------|----------------------------------------------------------------| +| `dataCollectionEndpointId` | Full resource ID of the DCE. | +| `dataCollectionEndpointUri` | HTTPS ingestion URI. Required by the collector. | +| `dataCollectionRuleOnPremisesConfigId` | Full resource ID of the on-premises DCR. | +| `dataCollectionRuleOnPremisesConfigImmutableId` | **Immutable ID** used by the collector for on-premises data. | +| `dataCollectionRuleOnlineConfigId` | Full resource ID of the online DCR. | +| `dataCollectionRuleOnlineConfigImmutableId` | **Immutable ID** used by the collector for online data. | +| `dataCollectionRuleMessageTrackingId` | Full resource ID of the message tracking DCR. | +| `dataCollectionRuleMessageTrackingImmutableId` | **Immutable ID** used by the collector for message tracking. | +| `onPremConfigTableName` | Confirms the on-premises table name (or `Not deployed`). | +| `configTableName` | Confirms the Exchange Online table name (or `Not deployed`). | +| `messageTrackingTableName` | Confirms the message tracking table name (or `Not deployed`). | + +Outputs of skipped resources return the string `Not deployed`. + +Retrieve them after deployment: + +```powershell +$deploy = Get-AzResourceGroupDeployment -ResourceGroupName "rg-sentinel-esi" -Name "YOUR_DEPLOYMENT_NAME" +$deploy.Outputs.dataCollectionEndpointUri.Value +$deploy.Outputs.dataCollectionRuleOnlineConfigImmutableId.Value +``` + +## Post-Deployment: Assign Ingestion Permissions + +The identity used by the ESI collector needs the **Monitoring Metrics Publisher** role on **each DCR** it sends data to. + +```bash +# Example: assign to a service principal on the Online Config DCR +az role assignment create \ + --role "Monitoring Metrics Publisher" \ + --assignee "YOUR_APP_OBJECT_ID" \ + --assignee-principal-type ServicePrincipal \ + --scope "/subscriptions//resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" +``` + +Repeat for `DCR-ESI-OnPremisesConfig` and `DCR-ESI-MessageTracking` as needed. + +## Update the Collector Configuration + +Update `CollectExchSecConfiguration.json` with the deployment outputs: + +```json +{ + "LogCollection": { + "ActivateLogUpdloadToSentinel": "true", + "SentinelLogIngestionAPIActivated": "true", + "DataCollectionEndpointURI": "", + "DCRImmutableId": "", + "UseManagedIdentity": "false", + "TargetLogTenantID": "YOUR_TENANT_ID", + "TargetLogAppID": "YOUR_APP_ID", + "TargetLogCertificateThumbprint": "YOUR_CERTIFICATE_THUMBPRINT", + "LogTypeName": "ESIExchangeConfig" + } +} +``` + +Pick the immutable ID matching the target table (on-premises, online, or message tracking). + +## Table Schemas + +Column suffixes follow Log Analytics conventions: `_s` string, `_d` real, `_g` guid, `_b` boolean, `_t` datetime, `_l` long, `_i` int. + +### `ESIAPIExchangeOnPremConfig_CL` and `ESIAPIExchangeOnlineConfig_CL` + +Both tables share the same schema (only the target audience differs). + +| Column | Type | Description | +|---------------------------------|----------|--------------------------------------------------------| +| `TimeGenerated` | datetime | Ingestion timestamp (set by `transformKql`). | +| `EntryDate_s` | string | Date of the configuration entry. | +| `GenerationInstanceID_g` | guid | Unique identifier for the collector execution. | +| `ESIEnvironment_s` | string | Exchange environment identifier. | +| `Section_s` | string | Configuration section name. | +| `ExecutionResult_s` | string | Execution result (`Success`, `Error`, ...). | +| `Identity_s` | string | Raw serialized `Identity` object (source of truth). | +| `Identity_Depth_d` | real | Depth of the identity in the directory hierarchy. | +| `Identity_DistinguishedName_s` | string | Distinguished name (LDAP DN). | +| `Identity_DomainId_s` | string | Domain identifier (serialized when object). | +| `Identity_IsDeleted_b` | boolean | Whether the identity is flagged as deleted. | +| `Identity_IsRelativeDn_b` | boolean | Whether the DN is relative. | +| `Identity_Name_s` | string | Identity name. | +| `Identity_ObjectGuid_g` | guid | Object GUID. | +| `Identity_Parent_s` | string | Parent identity (serialized when object). | +| `Identity_PartitionFQDN_s` | string | Partition FQDN. | +| `Identity_PartitionGuid_g` | guid | Partition GUID. | +| `Identity_Rdn_s` | string | Relative distinguished name (serialized when object). | +| `IdentityString_s` | string | Human-readable identity string. | +| `RawData_s` | string | Full raw configuration payload in JSON. | +| `Name_s` | string | Object name. | +| `ProcessedByServer_s` | string | Server that produced the entry. | +| `PSCmdL_s` | string | PowerShell cmdlet used to collect the entry. | +| `WhenChanged_t` | datetime | `WhenChanged` timestamp. | +| `WhenCreated_t` | datetime | `WhenCreated` timestamp. | + +> [!NOTE] +> The `Identity_*` sub-property columns are extracted from the source `Identity` object by the DCR's `transformKql` using `parse_json`. When `Identity` is `null` (some sections do not populate it), the sub-columns are `null`/empty — no ingestion error. + +### `ExchangeOnlineMessageTracking_CL` + +| Column | Type | Description | +|---------------------------------|----------|------------------------------------------------| +| `TimeGenerated` | datetime | Ingestion timestamp. | +| `schemaVersion_s` | string | Schema version of the log entry. | +| `clientIp_s` | string | Client IP address. | +| `clientHostname_s` | string | Client hostname. | +| `serverIp_s` | string | Server IP address. | +| `senderHostname_s` | string | Sender hostname. | +| `sourceContext_s` | string | Source context. | +| `connectorId_s` | string | Connector identifier. | +| `source_s` | string | Message source. | +| `eventId_s` | string | Event identifier. | +| `internalMessageId_s` | string | Internal message identifier. | +| `messageId_s` | string | Message identifier. | +| `networkMessageId_s` | string | Network message identifier. | +| `recipientAddress_s` | string | Recipient email address. | +| `recipientStatus_s` | string | Recipient delivery status. | +| `totalBytes_l` | long | Message size in bytes. | +| `recipientCount_i` | int | Number of recipients. | +| `relatedRecipientAddress_s` | string | Related recipient address. | +| `reference_s` | string | Message reference. | +| `messageSubject_s` | string | Email subject line. | +| `senderAddress_s` | string | Sender email address. | +| `returnPath_s` | string | Return path address. | +| `directionality_s` | string | Directionality (Originating / Incoming). | +| `messageInfo_s` | string | Additional message info. | +| `originalClientIp_s` | string | Original client IP. | +| `originalServerIp_s` | string | Original server IP. | +| `customData_s` | string | Custom metadata. | +| `transportTrafficType_s` | string | Transport traffic type. | +| `FilePath_s` | string | File path of the log entry. | +| `logId_s` | string | Log identifier. | +| `messageTrackingTenantId_s` | string | Tenant identifier for the message tracking log.| + +## Stream Names for API Ingestion + +When calling the Log Ingestion API directly, the stream name that follows the DCR immutable ID must match the DCR: + +| DCR | Stream declared | Output stream (table) | +|----------------------------|----------------------------------------|-------------------------------------------| +| `DCR-ESI-OnPremisesConfig` | `Custom-ESIExchangeConfig` | `Custom-ESIAPIExchangeOnPremConfig_CL` | +| `DCR-ESI-OnlineConfig` | `Custom-ESIExchangeOnlineConfig` | `Custom-ESIAPIExchangeOnlineConfig_CL` | +| `DCR-ESI-MessageTracking` | `Custom-ExchangeOnlineMessageTracking` | `Custom-ExchangeOnlineMessageTracking_CL` | + +Example API call: + +```powershell +$endpoint = "https://..ingest.monitor.azure.com" +$dcrImmutableId = "dcr-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +$streamName = "Custom-ESIExchangeOnlineConfig" + +$uri = "$endpoint/dataCollectionRules/$dcrImmutableId/streams/$streamName" + + "?api-version=2023-01-01" + +Invoke-RestMethod -Uri $uri -Method Post -Body $jsonData -Headers @{ + "Authorization" = "Bearer $accessToken" + "Content-Type" = "application/json" +} +``` + +## Monitoring + +Verify ingestion after deployment: + +```kql +// Exchange Online configuration ingestion +ESIAPIExchangeOnlineConfig_CL +| summarize count() by bin(TimeGenerated, 1h), Section_s +| render timechart + +// Exchange On-Premises configuration ingestion +ESIAPIExchangeOnPremConfig_CL +| summarize count() by bin(TimeGenerated, 1h), Section_s +| render timechart + +// Message Tracking ingestion +ExchangeOnlineMessageTracking_CL +| summarize count() by bin(TimeGenerated, 1h), directionality_s +| render timechart +``` + +## Troubleshooting + +### 403 Forbidden on ingestion + +Verify the identity used by the collector has the **Monitoring Metrics Publisher** role on the correct DCR (not the DCE, not the workspace). + +### Data does not appear + +1. Confirm the `DCRImmutableId` in the collector configuration matches the DCR routing to the target table. +2. Check the `transformKql` output columns match the destination table columns. +3. Review collector logs for payload size errors (Log Ingestion API limit is 1 MB per call — the collector auto-segments). + +### Table already exists + +If a table already exists with a different schema, either: + +- Redeploy with `deployTables=false` (leave tables as-is), or +- Manually align columns in the workspace, or +- Delete the table via the Log Analytics workspace and redeploy. + +### Deployment fails on cross-region resources + +The DCE, DCRs, and workspace must be in the **same region**. Adjust the `location` parameter or move the workspace. + +## Cleanup + +```bash +# Delete DCRs +az monitor data-collection rule delete --name "DCR-ESI-OnPremisesConfig" --resource-group "rg-sentinel-esi" +az monitor data-collection rule delete --name "DCR-ESI-OnlineConfig" --resource-group "rg-sentinel-esi" +az monitor data-collection rule delete --name "DCR-ESI-MessageTracking" --resource-group "rg-sentinel-esi" + +# Delete DCE +az monitor data-collection endpoint delete --name "DCE-ESI-LogIngestion" --resource-group "rg-sentinel-esi" +``` + +> [!CAUTION] +> Custom Log Analytics tables cannot be deleted via API. They can only be removed through the Log Analytics workspace in the Azure Portal, or by deleting the workspace itself. + +## Related Documentation + +- Full identity + permission setup: [README_AzureMonitorSetup.md](README_AzureMonitorSetup.md) +- Migration from the legacy Log Analytics API: [Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md](Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md) +- Sample payload: [sample-Custom-ESIExchangeConfig.json](sample-Custom-ESIExchangeConfig.json) + +## Support + +- GitHub: +- Microsoft Sentinel community: diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/WinformConfigReadme.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/WinformConfigReadme.md new file mode 100644 index 00000000000..3cdb9291db1 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Documentations/WinformConfigReadme.md @@ -0,0 +1,43 @@ +# CollectExchSec Configuration Editor (WinForms Modular) + +Features: +- Modular multi-file architecture +- Responsive multi-column question layout (FlowLayoutPanel, auto re-wrap on resize) +- Full conditional visibility + rehydration +- Per-field restore, validation (regex + hook) +- Array & addons editors +- UDS Log Processor and Instance Configuration grids +- Undo/Redo stack (Ctrl+Z / Ctrl+Y) with full state snapshots +- Logging (plain text) & telemetry (JSON lines) with session id +- Export of only changed settings (File > Export Changed Settings…) +- Clipboard operations (load, paste dialog, copy current JSON) +- Raw JSON preview & diff summary before saving +- Read-only toggle + +Run: +```powershell +pwsh -ExecutionPolicy Bypass -File .\Scripts\WinFormsMod\Main.ps1 -ConfigPath .\Config\CollectExchSecConfiguration.json +``` + +Logs: +- Created under `Scripts/WinFormsMod/logs/` + - actions_*.log: human readable + - telemetry_*.jsonl: structured events + +Exporting Changed Settings: +- Produces JSON with: + - Questions: dictionary of path->new value + - UDSLogProcessor: array of modified rows (full rows) + - InstanceConfiguration: hash of modified instances (full sub-objects) + +Customization: +- Adjust `$Global:QuestionPanelBaseWidth` & related constants in `UI.Sections.ps1`. +- Increase undo history via `UndoMax` in `State.ps1`. +- Extend validation hooks in `Validate-Question` (Utilities.ps1). +- Add encryption for secure fields prior to save if required. + +Future Ideas: +- Debounced undo snapshotting +- JSON schema validation +- Theming / high contrast +- Telemetry opt-in/out flag \ No newline at end of file diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Operations/Watchlists/ExchBuildNumber.csv b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Operations/Watchlists/ExchBuildNumber.csv index bcbd079c400..9e451ca187a 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Operations/Watchlists/ExchBuildNumber.csv +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Operations/Watchlists/ExchBuildNumber.csv @@ -1,7 +1,39 @@ Productname,CU,SU,BuildNbAll,BuilCUNb,Major,CUBuildNb,SUBuildNb +Exchange Server SE,RTM,Jul26SU,15.02.2562.045,15.02.2562,15.02,2562,45 +Exchange Server SE,RTM,Jun26SU,15.02.2562.043,15.02.2562,15.02,2562,43 +Exchange Server SE,RTM,May26HU,15.02.2562.041,15.02.2562,15.02,2562,41 +Exchange Server SE,RTM,Feb26SU,15.02.2562.037,15.02.2562,15.02,2562,37 +Exchange Server SE,RTM,Dec25SU,15.02.2562.035,15.02.2562,15.02,2562,35 +Exchange Server SE,RTM,Oct25SU,15.02.2562.029,15.02.2562,15.02,2562,29 +Exchange Server SE,RTM,Sep25HU,15.02.2562.027,15.02.2562,15.02,2562,27 +Exchange Server SE,RTM,Aug25SU,15.02.2562.020,15.02.2562,15.02,2562,20 +Exchange Server SE,RTM,NoSU,15.02.2562.017,15.02.2562,15.02,2562,17 +Exchange Server 2019,CU15,Jul26SU,15.02.1748.048,15.02.1748,15.02,1748,48 +Exchange Server 2019,CU15,Jun26SU,15.02.1748.046,15.02.1748,15.02,1748,46 +Exchange Server 2019,CU15,Feb26SU,15.02.1748.043,15.02.1748,15.02,1748,43 +Exchange Server 2019,CU15,Dec25SU,15.02.1748.042,15.02.1748,15.02,1748,42 +Exchange Server 2019,CU15,Oct25SU,15.02.1748.039,15.02.1748,15.02,1748,39 +Exchange Server 2019,CU15,Sep25HU,15.02.1748.037,15.02.1748,15.02,1748,37 +Exchange Server 2019,CU15,Aug25SU,15.02.1748.036,15.02.1748,15.02,1748,36 +Exchange Server 2019,CU15,May25HU,15.02.1748.026,15.02.1748,15.02,1748,26 +Exchange Server 2019,CU15,Apr25HU,15.02.1748.024,15.02.1748,15.02,1748,24 +Exchange Server 2019,CU15,NoSU,15.02.1748.010,15.02.1748,15.02,1748,10 +Exchange Server 2019,CU14,Jul26SU,15.02.1544.043,15.02.1544,15.02,1544,43 +Exchange Server 2019,CU14,Jun26SU,15.02.1544.041,15.02.1544,15.02,1544,41 +Exchange Server 2019,CU14,Feb26SU,15.02.1544.039,15.02.1544,15.02,1544,39 +Exchange Server 2019,CU14,Dec25SU,15.02.1544.037,15.02.1544,15.02,1544,37 +Exchange Server 2019,CU14,Oct25SU,15.02.1544.036,15.02.1544,15.02,1544,36 +Exchange Server 2019,CU14,Sep25HU,15.02.1544.034,15.02.1544,15.02,1544,34 +Exchange Server 2019,CU14,Aug25SU,15.02.1544.033,15.02.1544,15.02,1544,33 +Exchange Server 2019,CU14,May25HU,15.02.1544.027,15.02.1544,15.02,1544,27 +Exchange Server 2019,CU14,Apr25HU,15.02.1544.025,15.02.1544,15.02,1544,25 +Exchange Server 2019,CU14,Nov24SUv2,15.02.1544.014,15.02.1544,15.02,1544,14 +Exchange Server 2019,CU14,Nov24SU,15.02.1544.013,15.02.1544,15.02,1544,13 Exchange Server 2019,CU14,Apr24SU,15.02.1544.011,15.02.1544,15.02,1544,11 Exchange Server 2019,CU14,Mar24SU,15.02.1544.009,15.02.1544,15.02,1544,9 Exchange Server 2019,CU14,NoSU,15.02.1544.004,15.02.1544,15.02,1544,4 +Exchange Server 2019,CU13,Nov24SUv2,15.02.1258.039,15.02.1258,15.02,1258,39 +Exchange Server 2019,CU13,Nov24SU,15.02.1258.038,15.02.1258,15.02,1258,38 Exchange Server 2019,CU13,Apr24SU,15.02.1258.034,15.02.1258,15.02,1258,34 Exchange Server 2019,CU13,Mar24SU,15.02.1258.032,15.02.1258,15.02,1258,32 Exchange Server 2019,CU13,Nov23SU,15.02.1258.028,15.02.1258,15.02,1258,28 @@ -62,6 +94,17 @@ Exchange Server 2019,CU1,NoSU,15.02.330.005,15.02.330,15.02,330,5 Exchange Server 2019,RTM,Mar21SU,15.02.221.018,15.02.221,15.02,221,18 Exchange Server 2019,RTM,NoSU,15.02.221.012,15.02.221,15.02,221,12 Exchange Server 2019,Preview,NoSU,15.02.196.00,15.02.196,15.02,196, +Exchange Server 2016,CU23,Jul26SU,15.01.2507.071,15.01.2507,15.01,2507,71 +Exchange Server 2016,CU23,Jun26SU,15.01.2507.069,15.01.2507,15.01,2507,69 +Exchange Server 2016,CU23,Feb26SU,15.01.2507.066,15.01.2507,15.01,2507,66 +Exchange Server 2016,CU23,Dec25SU,15.01.2507.063,15.01.2507,15.01,2507,63 +Exchange Server 2016,CU23,Oct25SU,15.01.2507.061,15.01.2507,15.01,2507,61 +Exchange Server 2016,CU23,Sep25HU,15.01.2507.059,15.01.2507,15.01,2507,59 +Exchange Server 2016,CU23,Aug25SU,15.01.2507.058,15.01.2507,15.01,2507,58 +Exchange Server 2016,CU23,May25HU,15.01.2507.057,15.01.2507,15.01,2507,57 +Exchange Server 2016,CU23,Apr25HU,15.01.2507.055,15.01.2507,15.01,2507,55 +Exchange Server 2016,CU23,Nov24SUv2,15.01.2507.044,15.01.2507,15.01,2507,44 +Exchange Server 2016,CU23,Nov24SU,15.01.2507.043,15.01.2507,15.01,2507,43 Exchange Server 2016,CU23,Apr24SU,15.01.2507.039,15.1.2507,15.01,2507,35 Exchange Server 2016,CU23,Mar24SU,15.01.2507.037,15.1.2507,15.01,2507,39 Exchange Server 2016,CU23,Nov23SU,15.01.2507.035,15.1.2507,15.01,2507,37 diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Operations/Watchlists/standardMRAOnline.csv b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Operations/Watchlists/standardMRAOnline.csv index ca5b5e30309..a52ff3a9cda 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Operations/Watchlists/standardMRAOnline.csv +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Operations/Watchlists/standardMRAOnline.csv @@ -11,10 +11,20 @@ Application Mail.Read-Organization Management-Delegating,Application Mail.Read,O Application Mail.ReadBasic-Organization Management-Delegating,Application Mail.ReadBasic,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Application Mail.ReadWrite-Organization Management-Delegating,Application Mail.ReadWrite,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Application Mail.Send-Organization Management-Delegating,Application Mail.Send,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxConfigItem.Read-Organization Management-Deleg,Application MailboxConfigItem.Read,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxConfigItem.ReadWrite-Organization Management-,Application MailboxConfigItem.ReadWrite,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxFolder.Read-Organization Management-Delegatin,Application MailboxFolder.Read,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxFolder.ReadWrite-Organization Management-Dele,Application MailboxFolder.ReadWrite,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxItem.Export-Organization Management-Delegatin,Application MailboxItem.Export,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxItem.ImportExport-Organization Management-Del,Application MailboxItem.ImportExport,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxItem.Read-Organization Management-Delegating,Application MailboxItem.Read,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxItem.ReadWrite-Organization Management-Delega,Application MailboxItem.ReadWrite,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Application MailboxSettings.Read-Organization Management-Delegat,Application MailboxSettings.Read,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Application MailboxSettings.ReadWrite-Organization Management-De,Application MailboxSettings.ReadWrite,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailTips.ReadBasic.All-Organization Management-Deleg,Application MailTips.ReadBasic.All,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application SMTP.SendAsApp-Organization Management-Delegating,Application SMTP.SendAsApp,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct ApplicationImpersonation-Organization Management-Delegating,ApplicationImpersonation,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct -ApplicationImpersonation-RIM-MailboxAdminscc5e64999db94ccba09208,ApplicationImpersonation,RIM-MailboxAdminscc5e64999db94ccba09208f0387dfd74,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +ApplicationImpersonation-RIM-MailboxAdmins27188811fdd1476d9098e5,ApplicationImpersonation,RIM-MailboxAdmins27188811fdd1476d9098e5e5885d0bd0,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct ArchiveApplication-Organization Management-Delegating,ArchiveApplication,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Audit Logs-Compliance Management,Audit Logs,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Audit Logs-Organization Management,Audit Logs,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct @@ -32,13 +42,13 @@ Compliance Admin-Organization Management-Delegating,Compliance Admin,Organizatio Data Loss Prevention-Compliance Management,Data Loss Prevention,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Data Loss Prevention-Organization Management,Data Loss Prevention,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Data Loss Prevention-Organization Management-Delegating,Data Loss Prevention,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Distribution Groups-Organization Management,Distribution Groups,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Distribution Groups-Organization Management-Delegating,Distribution Groups,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Distribution Groups-Organization Management,Distribution Groups,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Distribution Groups-Recipient Management,Distribution Groups,Recipient Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -E-Mail Address Policies-Organization Management,E-Mail Address Policies,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct E-Mail Address Policies-Organization Management-Delegating,E-Mail Address Policies,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Federated Sharing-Organization Management,Federated Sharing,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +E-Mail Address Policies-Organization Management,E-Mail Address Policies,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Federated Sharing-Organization Management-Delegating,Federated Sharing,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Federated Sharing-Organization Management,Federated Sharing,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Information Protection Admin-Information Protection,Information Protection Admin,Information Protection,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Information Protection Admin-Information Protection Admins,Information Protection Admin,Information Protection Admins,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Information Protection Admin-Organization Management,Information Protection Admin,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct @@ -62,12 +72,12 @@ Insider Risk Management Investigation-Insider Risk Management,Insider Risk Manag Insider Risk Management Investigation-Insider Risk Management In,Insider Risk Management Investigation,Insider Risk Management Investigators,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Insider Risk Management Investigation-Organization Management,Insider Risk Management Investigation,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Journaling-Compliance Management,Journaling,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Journaling-Organization Management,Journaling,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Journaling-Organization Management-Delegating,Journaling,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Journaling-Organization Management,Journaling,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Journaling-Records Management,Journaling,Records Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Legal Hold-Discovery Management,Legal Hold,Discovery Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,None,Direct -Legal Hold-Organization Management,Legal Hold,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,None,Direct Legal Hold-Organization Management-Delegating,Legal Hold,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,None,Direct +Legal Hold-Organization Management,Legal Hold,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,None,Direct LegalHoldApplication-Organization Management-Delegating,LegalHoldApplication,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Mail Enabled Public Folders-Organization Management,Mail Enabled Public Folders,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Mail Enabled Public Folders-Organization Management-Delegating,Mail Enabled Public Folders,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct @@ -89,8 +99,8 @@ Message Tracking-Organization Management,Message Tracking,Organization Managemen Message Tracking-Organization Management-Delegating,Message Tracking,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Message Tracking-Recipient Management,Message Tracking,Recipient Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Message Tracking-Records Management,Message Tracking,Records Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Migration-Organization Management,Migration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Migration-Organization Management-Delegating,Migration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Migration-Organization Management,Migration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Migration-Recipient Management,Migration,Recipient Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Move Mailboxes-Organization Management,Move Mailboxes,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Move Mailboxes-Organization Management-Delegating,Move Mailboxes,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct @@ -101,24 +111,24 @@ My Custom Apps-Organization Management-Delegating,My Custom Apps,Organization Ma My Marketplace Apps-Default Role Assignment Policy,My Marketplace Apps,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct My Marketplace Apps-Default Role Assignment Policy-1,My Marketplace Apps,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct My Marketplace Apps-Organization Management-Delegating,My Marketplace Apps,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -My ReadWriteMailbox Apps-Default Role Assignment Policy,My ReadWriteMailbox Apps,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct My ReadWriteMailbox Apps-Default Role Assignment Policy-1,My ReadWriteMailbox Apps,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct +My ReadWriteMailbox Apps-Default Role Assignment Policy,My ReadWriteMailbox Apps,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct My ReadWriteMailbox Apps-Organization Management-Delegating,My ReadWriteMailbox Apps,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -MyBaseOptions-Default Role Assignment Policy,MyBaseOptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyBaseOptions-Default Role Assignment Policy-1,MyBaseOptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct +MyBaseOptions-Default Role Assignment Policy,MyBaseOptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyBaseOptions-Organization Management-Delegating,MyBaseOptions,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyContactInformation-Default Role Assignment Policy,MyContactInformation,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyContactInformation-Default Role Assignment Policy-1,MyContactInformation,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyContactInformation-Organization Management-Delegating,MyContactInformation,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -MyDistributionGroupMembership-Default Role Assignment Policy,MyDistributionGroupMembership,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,None,MyGAL,None,Direct MyDistributionGroupMembership-Default Role Assignment Policy-1,MyDistributionGroupMembership,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,None,MyGAL,None,Direct +MyDistributionGroupMembership-Default Role Assignment Policy,MyDistributionGroupMembership,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,None,MyGAL,None,Direct MyDistributionGroupMembership-Organization Management-Delegating,MyDistributionGroupMembership,Organization Management,All Group Members,RoleGroup,,,MyGAL,None,MyGAL,None,Direct -MyDistributionGroups-Default Role Assignment Policy,MyDistributionGroups,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,OrganizationConfig,MyDistributionGroups,None,Direct MyDistributionGroups-Default Role Assignment Policy-1,MyDistributionGroups,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,OrganizationConfig,MyDistributionGroups,None,Direct +MyDistributionGroups-Default Role Assignment Policy,MyDistributionGroups,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,OrganizationConfig,MyDistributionGroups,None,Direct MyDistributionGroups-Organization Management-Delegating,MyDistributionGroups,Organization Management,All Group Members,RoleGroup,,,MyGAL,OrganizationConfig,MyDistributionGroups,None,Direct MyMailboxDelegation-Organization Management-Delegating,MyMailboxDelegation,Organization Management,All Group Members,RoleGroup,,,MyGAL,OrganizationConfig,MailboxICanDelegate,None,Direct -MyMailSubscriptions-Default Role Assignment Policy,MyMailSubscriptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyMailSubscriptions-Default Role Assignment Policy-1,MyMailSubscriptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct +MyMailSubscriptions-Default Role Assignment Policy,MyMailSubscriptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyMailSubscriptions-Organization Management-Delegating,MyMailSubscriptions,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyProfileInformation-Default Role Assignment Policy,MyProfileInformation,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyProfileInformation-Default Role Assignment Policy-1,MyProfileInformation,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct @@ -126,81 +136,86 @@ MyProfileInformation-Organization Management-Delegating,MyProfileInformation,Org MyRetentionPolicies-Default Role Assignment Policy,MyRetentionPolicies,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyRetentionPolicies-Default Role Assignment Policy-1,MyRetentionPolicies,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyRetentionPolicies-Organization Management-Delegating,MyRetentionPolicies,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -MyTextMessaging-Default Role Assignment Policy,MyTextMessaging,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyTextMessaging-Default Role Assignment Policy-1,MyTextMessaging,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct +MyTextMessaging-Default Role Assignment Policy,MyTextMessaging,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyTextMessaging-Organization Management-Delegating,MyTextMessaging,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -MyVoiceMail-Default Role Assignment Policy,MyVoiceMail,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyVoiceMail-Default Role Assignment Policy-1,MyVoiceMail,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct +MyVoiceMail-Default Role Assignment Policy,MyVoiceMail,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyVoiceMail-Organization Management-Delegating,MyVoiceMail,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct O365SupportViewConfig-O365 Support View Only,O365SupportViewConfig,O365 Support View Only,O365 Support View Only,PartnerLinkedRoleGroup,,,Organization,OrganizationConfig,None,None,Direct OfficeExtensionApplication-Organization Management-Delegating,OfficeExtensionApplication,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -Org Custom Apps-Organization Management,Org Custom Apps,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Org Custom Apps-Organization Management-Delegating,Org Custom Apps,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Org Custom Apps-Organization Management,Org Custom Apps,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Org Marketplace Apps-Organization Management,Org Marketplace Apps,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Org Marketplace Apps-Organization Management-Delegating,Org Marketplace Apps,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Organization Client Access-Organization Management,Organization Client Access,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Organization Client Access-Organization Management-Delegating,Organization Client Access,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Organization Configuration-Organization Management,Organization Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Organization Client Access-Organization Management,Organization Client Access,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Organization Configuration-Organization Management-Delegating,Organization Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Organization Configuration-Organization Management,Organization Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Organization Transport Settings-Organization Management,Organization Transport Settings,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Organization Transport Settings-Organization Management-Delegati,Organization Transport Settings,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +PlacesBuildingManagement-Organization Management,PlacesBuildingManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +PlacesBuildingManagement-Organization Management-Delegating,PlacesBuildingManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +PlacesDeskManagement-Organization Management,PlacesDeskManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +PlacesDeskManagement-Organization Management-Delegating,PlacesDeskManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Privacy Management Admin-Organization Management,Privacy Management Admin,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Privacy Management Admin-Privacy Management,Privacy Management Admin,Privacy Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Privacy Management Admin-Privacy Management Administrators,Privacy Management Admin,Privacy Management Administrators,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Privacy Management Investigation-Organization Management,Privacy Management Investigation,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Privacy Management Investigation-Privacy Management,Privacy Management Investigation,Privacy Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Privacy Management Investigation-Privacy Management Investigator,Privacy Management Investigation,Privacy Management Investigators,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct -Public Folders-Organization Management,Public Folders,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Public Folders-Organization Management-Delegating,Public Folders,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Public Folders-Organization Management,Public Folders,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Recipient Policies-Organization Management,Recipient Policies,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Recipient Policies-Organization Management-Delegating,Recipient Policies,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Recipient Policies-Recipient Management,Recipient Policies,Recipient Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Remote and Accepted Domains-Organization Management,Remote and Accepted Domains,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Remote and Accepted Domains-Organization Management-Delegating,Remote and Accepted Domains,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Remote and Accepted Domains-Organization Management,Remote and Accepted Domains,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Reset Password-Help Desk,Reset Password,Help Desk,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Reset Password-Organization Management,Reset Password,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Reset Password-Organization Management-Delegating,Reset Password,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Reset Password-Recipient Management,Reset Password,Recipient Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Retention Management-Compliance Management,Retention Management,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Retention Management-Organization Management,Retention Management,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Retention Management-Organization Management-Delegating,Retention Management,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Retention Management-Organization Management,Retention Management,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Retention Management-Records Management,Retention Management,Records Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Role Management-Organization Management,Role Management,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Role Management-Organization Management-Delegating,Role Management,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Admin-Organization Management,Security Admin,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Admin-Organization Management-Delegating,Security Admin,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Admin-Security Administrator,Security Admin,Security Administrator,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Security Group Creation and Membership-Organization Management,Security Group Creation and Membership,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Group Creation and Membership-Organization Management-D,Security Group Creation and Membership,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Security Reader-Organization Management,Security Reader,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Security Group Creation and Membership-Organization Management,Security Group Creation and Membership,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Reader-Organization Management-Delegating,Security Reader,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Security Reader-Organization Management,Security Reader,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Reader-Security Reader,Security Reader,Security Reader,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct SendMailApplication-Organization Management-Delegating,SendMailApplication,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct SensitivityLabelAdministrator-Organization Management-Delegating,SensitivityLabelAdministrator,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct SensitivityLabelAdministrator-Security Administrator,SensitivityLabelAdministrator,Security Administrator,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct TeamMailboxLifecycleApplication-Organization Management-Delegati,TeamMailboxLifecycleApplication,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -Tenant AllowBlockList Manager-Organization Management-Delegating,Tenant AllowBlockList Manager,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct -Tenant AllowBlockList Manager-Security Operator,Tenant AllowBlockList Manager,Security Operator,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Tenant AllowBlockList Manager-Organization Management-Delegating,Tenant AllowBlockList Manager,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Tenant AllowBlockList Manager-Security Operator,Tenant AllowBlockList Manager,Security Operator,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct TenantPlacesManagement-Organization Management,TenantPlacesManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct TenantPlacesManagement-Organization Management-Delegating,TenantPlacesManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +TenantPlacesManagement-Places Administrator,TenantPlacesManagement,Places Administrator,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Hygiene-Hygiene Management,Transport Hygiene,Hygiene Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Hygiene-Organization Management,Transport Hygiene,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Hygiene-Organization Management-Delegating,Transport Hygiene,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Rules-Compliance Management,Transport Rules,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Transport Rules-Organization Management,Transport Rules,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Rules-Organization Management-Delegating,Transport Rules,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Transport Rules-Organization Management,Transport Rules,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Rules-Records Management,Transport Rules,Records Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct User Options-Help Desk,User Options,Help Desk,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct User Options-Organization Management,User Options,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct User Options-Organization Management-Delegating,User Options,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct UserApplication-Organization Management-Delegating,UserApplication,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct View-Only Audit Logs-Compliance Management,View-Only Audit Logs,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct -View-Only Audit Logs-Organization Management,View-Only Audit Logs,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Audit Logs-Organization Management-Delegating,View-Only Audit Logs,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct +View-Only Audit Logs-Organization Management,View-Only Audit Logs,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Configuration-Compliance Management,View-Only Configuration,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Configuration-Hygiene Management,View-Only Configuration,Hygiene Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct -View-Only Configuration-Organization Management,View-Only Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Configuration-Organization Management-Delegating,View-Only Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct +View-Only Configuration-Organization Management,View-Only Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Configuration-View-Only Organization Management,View-Only Configuration,View-Only Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Recipients-Compliance Management,View-Only Recipients,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Recipients-Help Desk,View-Only Recipients,Help Desk,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Beta/CollectExchSecIns.zip b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Beta/CollectExchSecIns.zip new file mode 100644 index 00000000000..9eed7cd0e89 Binary files /dev/null and b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Beta/CollectExchSecIns.zip differ diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Beta/CollectExchSecInsVersionTracking.json b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Beta/CollectExchSecInsVersionTracking.json index a3a95c9cc81..543da74fd4a 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Beta/CollectExchSecInsVersionTracking.json +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Beta/CollectExchSecInsVersionTracking.json @@ -1,17 +1,27 @@ { - "LatestVersion":"7.6.0.1", - "LatestVersionDate":"2024-07-26T00:00:00.000Z", + "LatestVersion":"8.0.0.0", + "LatestVersionDate":"2025-08-29T00:00:00.000Z", "LatestVersionDownload":"https://aka.ms/ESI-ExchangeCollector-RawScript", - "ESICollectorRawRepository":"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Solutions/ESICollector/", + "ESICollectorRawRepository":"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Solutions/ESICollector", "VersionHistory":[ { + "Version":"8.0.0.0", + "InternalVersion":8000, + "VersionFileName":"CollectExchSecIns-v8.0.0.0.zip", + "Beta": false, + "BreakingChanges":[ + { + "Description":"The script now uses DCR (Data Collection Rules) for data collection. You need to configure DCR before running the script.", + "Target":"All" + } + ] + },{ "Version":"7.6.0.1", "InternalVersion":7601, "VersionFileName":"CollectExchSecIns-v7.6.0.1.zip", "Beta": false, "BreakingChanges":[] - }, - { + },{ "Version":"7.6.0.0", "InternalVersion":7600, "VersionFileName":"CollectExchSecIns-v7.6.0.0.zip", @@ -60,7 +70,7 @@ "VersionFileName":"CollectExchSecIns-v7.4.2.0.zip", "BreakingChanges":[ { - "Description":"The script now uses the new Exchange Online V2 module. You need to install it before running the script. See https://github.com/Azure/Azure-Sentinel/tree/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Solutions/ESICollector#from-732-to-742 for more information.", + "Description":"The script now uses the new Exchange Online V2 module. You need to install it before running the script. See https://github.com/nlepagnez/ESI-PublicContent/tree/main/Solutions/ESICollector#from-732-to-742 for more information.", "Target":"Online" } ] diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecIns-v8.0.0.0.zip b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecIns-v8.0.0.0.zip new file mode 100644 index 00000000000..9eed7cd0e89 Binary files /dev/null and b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecIns-v8.0.0.0.zip differ diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecIns.zip b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecIns.zip index d8e957ba8cb..9eed7cd0e89 100644 Binary files a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecIns.zip and b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecIns.zip differ diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecInsVersionTracking.json b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecInsVersionTracking.json index a3a95c9cc81..543da74fd4a 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecInsVersionTracking.json +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/CollectExchSecInsVersionTracking.json @@ -1,17 +1,27 @@ { - "LatestVersion":"7.6.0.1", - "LatestVersionDate":"2024-07-26T00:00:00.000Z", + "LatestVersion":"8.0.0.0", + "LatestVersionDate":"2025-08-29T00:00:00.000Z", "LatestVersionDownload":"https://aka.ms/ESI-ExchangeCollector-RawScript", - "ESICollectorRawRepository":"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Solutions/ESICollector/", + "ESICollectorRawRepository":"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Solutions/ESICollector", "VersionHistory":[ { + "Version":"8.0.0.0", + "InternalVersion":8000, + "VersionFileName":"CollectExchSecIns-v8.0.0.0.zip", + "Beta": false, + "BreakingChanges":[ + { + "Description":"The script now uses DCR (Data Collection Rules) for data collection. You need to configure DCR before running the script.", + "Target":"All" + } + ] + },{ "Version":"7.6.0.1", "InternalVersion":7601, "VersionFileName":"CollectExchSecIns-v7.6.0.1.zip", "Beta": false, "BreakingChanges":[] - }, - { + },{ "Version":"7.6.0.0", "InternalVersion":7600, "VersionFileName":"CollectExchSecIns-v7.6.0.0.zip", @@ -60,7 +70,7 @@ "VersionFileName":"CollectExchSecIns-v7.4.2.0.zip", "BreakingChanges":[ { - "Description":"The script now uses the new Exchange Online V2 module. You need to install it before running the script. See https://github.com/Azure/Azure-Sentinel/tree/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Solutions/ESICollector#from-732-to-742 for more information.", + "Description":"The script now uses the new Exchange Online V2 module. You need to install it before running the script. See https://github.com/nlepagnez/ESI-PublicContent/tree/main/Solutions/ESICollector#from-732-to-742 for more information.", "Target":"Online" } ] diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/OnlineDeployment/CollectExchSecIns.ps1 b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/OnlineDeployment/CollectExchSecIns.ps1 index ba1cac2ddd1..f23e59b7b33 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/OnlineDeployment/CollectExchSecIns.ps1 +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/OnlineDeployment/CollectExchSecIns.ps1 @@ -23,10 +23,19 @@ possibility of such damages .\CollectExchSecIns.ps1 .OUTPUTS - The output a csv file of collected data + Ingestion in Azure Sentinel .NOTES Developed by ksangui@microsoft.com and Nicolas Lepagnez (nilepagn@microsoft.com) + Version : 8.0.0.0 - Released : IN DEV - nilepagn + - Implement Log Ingestion API for Sentinel + - Create $Script:ESIDataPath to store data in a specific folder and become independant from CSV configuration + - Move ExportDomainsInformation to LogCollection Section in configuration. If set to true, the Domain Information will be exported in the Log Collection. Default Value is True as before. + - Change Github link for Configuration file to use the new repository + - Adding possibility to use github API insteafd of direct download for configuration file + - Adding runtime warning banner when the collector still uses the legacy Log Analytics HTTP Data Collector API. + See ESI-PublicContent/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md for the migration procedure. + Version : 7.6.0.1 - Released : 26/07/2024 - nilepagn - Adding Try-Catch on Get-AutomationVariable Test - Correct a bug on Get-LastVersion with Write-LogMessage @@ -142,48 +151,6 @@ possibility of such damages - Prepare the ability to retrieve Add-On files from Internet - Prepare the ability to check a checksum of AuditFunctions. - Version : 6.5 - Released : 27/09/2022 - nilepagn - - Correction of bug on retrieving Exchange Servers - - Adding ESIEnvironment Information to correlate Configuration with logs in Sentinel - - Version : 6.4 - Released : 22/09/2022 - nilepagn - - Filtering EDGE Servers that can't be analyzed - - Correcting a bug on Custom Select Fields - - Adding possibility to generate information for a specific Sentinel API Table. Add '//' in OutputStream of the function. Like "myfile.csv//SpecificSentinelTable" - - Version : 6.3 - Released : 19/09/2022 - nilepagn - - Correct bug on AD Requests on a multi-domain environment - - Add the processing of the JobStatus type "Error" during transformation - - Changes how Errors from jobs are displayed in logs : Display as warning to doesn't throw error - - Add a correct error processing when user domain doesn't have the homeMBD attribute - - Modify the end of script to correctly ends the logging - - Version : 6.2.2 - Released : 12/09/2022 - Ksangui - -Add Get-inboundConnecot and Get OutboungConnector for Online - - Version : 6.2.1 - Released : 10/09/2022 - nilepagn - - Possibility to display TargetServer on Select (It was a regression from 4.x version) - - Version : 6.2 - Released : ? - nilepagn - - Possibility to use Log Analytics API and CSV in same time. - - Version : 6.1.1 - Released : 24/08/2022 - nilepagn - - Correcting bug on multithreading. - - Version published on On-Premises testing environment and validated. - - Version : 6.1 - Released : 24/08/2022 - nilepagn - - Adding ESIEnvironment column in entries adding the possibility to audit multiple On-Premises and Online Exchange configuration - - Version : 6.0.1 - Released : 24/08/2022 - nilepagn - - Bug on Write-LogMessage during function loading. - - Version : 6.0 - Released : 24/08/2022 - nilepagn - - Merge of On-Premises version and Cloud Version of ESI Collector - - Deactivate the possibility to launch multi-threading in Azure Automation - - Add "ESIProcessingType":"Online" in Global Section of JSON File. The value can be "Online" or "On-Premises" - - Add "ProcessingCategory":"All" for Audit Functions. The value can be "All", "Online" or "On-Premises" - - Reorganization of functions in the code by category - ** For Previous version history, see Github page ** #> @@ -196,12 +163,14 @@ Param ( $EPS2010=$false, [switch] $NoDateTracing, [string] $InstanceName = "Default", + [switch] $ExchangeSimulationInjection, + [String] $SimulationInformation = "Internal", [switch] $GetVersion, [string] $ReceivedTenantName, [switch] $IsOutsideAzureAutomation ) -$ESICollectorCurrentVersion = "7.6.0.1" +$ESICollectorCurrentVersion = "8.0.0.0" if ($GetVersion) {return $ESICollectorCurrentVersion} $Script:SupportedConfigurationVersion = "2.4" @@ -571,6 +540,12 @@ $Script:SupportedConfigurationVersion = "2.4" $CapabilitiesList = @('OP', 'ADINFOS') ) + if ($Script:_InternalInjection) + { + Write-LogMessage -Message "Internal Injection, Capabilities are not loaded" -Level Warning + return + } + $script:CapabilityLoaded = @() foreach ($Capability in $CapabilitiesList) @@ -713,9 +688,9 @@ $Script:SupportedConfigurationVersion = "2.4" if ($Global:InstanceName -ne "Default") { $fileName = "DateTracking-$($Global:InstanceName).esi"} - if (Test-Path ((Split-Path $outputpath) + "\$fileName")) + if (Test-Path (($Script:ESIDataPath) + "\$fileName")) { - $ContentDate = Get-Content ((Split-Path $outputpath) + "\$fileName") + $ContentDate = Get-Content (($Script:ESIDataPath) + "\$fileName") } else { $ContentDate = $null @@ -923,9 +898,9 @@ $Script:SupportedConfigurationVersion = "2.4" else { if ($Global:InstanceName -ne "Default") { - $JSNToSave | Set-ESIContent ((Split-Path $outputpath) + "\DateTracking-$($Global:InstanceName).esi") + $JSNToSave | Set-ESIContent -Path (($Script:ESIDataPath) + "\DateTracking-$($Global:InstanceName).esi") } - else { $JSNToSave | Set-ESIContent ((Split-Path $outputpath) + "\DateTracking.esi") } + else { $JSNToSave | Set-ESIContent -Path (($Script:ESIDataPath) + "\DateTracking.esi") } } } } @@ -1272,6 +1247,43 @@ $Script:SupportedConfigurationVersion = "2.4" return $object } + function Connect-LogIngestionAPI + { + Write-LogMessage -Message ("Generate Log Ingestion API Token with Type $($Script:ESIProcessingType) ...") + Write-LogMessage -Message "Connect to Azure RM" + + if (-not $Global:AlreadyAzSentinelConnected) + { + # Ensures you do not inherit an AzContext in your runbook + Disable-AzContextAutosave -Scope Process + + if ($isRunbook -and $Script:SentinelLogCollector.UseManagedIdentity) + { + # Connect to Azure with system-assigned managed identity + $AzureContext = (Connect-AzAccount -Identity -ContextName "SentinelIngestion").context + $Global:AlreadyAzSentinelConnected = $true + } + else { + if ($Global:Interactive) + { + Write-LogMessage -Message "Az Connect with Interactive Logon" + $AzureContext = (Connect-AzAccount -ContextName "SentinelIngestion").context + } + else { + Write-LogMessage -Message "Az Connect with Interactive Logon" + $AzureContext = (Connect-AzAccount -ContextName "SentinelIngestion" -CertificateThumbprint $Script:SentinelLogCollector.TargetLogCertificateThumbprint -Tenant $Script:SentinelLogCollector.TargetLogTenantID -ApplicationId $Script:SentinelLogCollector.TargetLogAppID).context + } + $Global:AlreadyAzSentinelConnected = $true + } + } + else {$AzureContext = Get-AzContext -Name "SentinelIngestion"} + + #$AzureContext = Set-AzContext -SubscriptionName $AzureContext.Subscription -DefaultProfile $AzureContext + $context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.Contexts["SentinelIngestion"] + + $Script:SentinelLogIngestionToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://monitor.azure.com") + } + function Get-OutputOptions { Param( @@ -1284,6 +1296,7 @@ $Script:SupportedConfigurationVersion = "2.4" $OutputOptions | Add-Member Noteproperty -Name OutputFileName -value $null $OutputOptions | Add-Member Noteproperty -Name IsPartial -value $false $OutputOptions | Add-Member Noteproperty -Name AlreadyBackuped -value $false + $OutputOptions | Add-Member Noteproperty -Name LogTypeName -value $Script:SentinelLogCollector.LogTypeName if ($OutputName -match '_Page') { @@ -1304,7 +1317,7 @@ $Script:SupportedConfigurationVersion = "2.4" { if ([string]::IsNullOrEmpty($Script:InstanceConfiguration.OutputName)) { - $TargetOutputName = $script:outputpath -replace '.csv',"-$($Script:InstanceName).csv" + $TargetOutputName = $script:CSVOutputFile -replace '.csv',"-$($Script:InstanceName).csv" $OutputOptions.OutputFileName = $Script:InstanceName } else @@ -1319,7 +1332,7 @@ $Script:SupportedConfigurationVersion = "2.4" $OutputOptions.OutputSentinelAPI = $Script:InstanceConfiguration.OutputName } } - else { $OutputOptions.OutputFileName = $script:outputpath } + else { $OutputOptions.OutputFileName = $script:CSVOutputFile } } else { $OutputOptions.OutputFileName = $OutputName @@ -1347,7 +1360,15 @@ $Script:SupportedConfigurationVersion = "2.4" if ($Script:SentinelLogCollector.ActivateLogUpdloadToSentinel) { - Start-LogsInjestion -OutputName $OutputName -PartialData:$PartialData -PartialDataName $PartialDataName -PartialDataRawFormat:$PartialDataRawFormat -RawData $RawData + if ($Script:SentinelLogCollector.UseForwarder) + { + Write-LogMessage -Message "Forwarder mode activated, logs will written to target directory for forwarder pickup" + SaveLogs-To-ForwarderPickup -OutputName $OutputName -PartialData:$PartialData -PartialDataName $PartialDataName -PartialDataRawFormat:$PartialDataRawFormat -RawData $RawData -TargetPath $Script:SentinelLogCollector.ForwarderPickupPath + } + else { + Write-LogMessage -Message "Logs will be sent to Sentinel during the script execution" + Start-LogsInjestion -OutputName $OutputName -PartialData:$PartialData -PartialDataName $PartialDataName -PartialDataRawFormat:$PartialDataRawFormat -RawData $RawData + } } if (-not $Script:SentinelLogCollector.ActivateLogUpdloadToSentinel -or $Script:SentinelLogCollector.TogetherMode) @@ -1356,6 +1377,69 @@ $Script:SupportedConfigurationVersion = "2.4" } } + function Send-SentinelSegment { + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [array]$SegmentData, + + [Parameter(Mandatory=$true)] + [string]$LogType, + + [Parameter(Mandatory=$true)] + [double]$MaxSize, + + [Parameter(Mandatory=$false)] + [string]$SegmentLabel = "segment" + ) + + # always be sure that $segmentData is an array, even if it contains only one element + if ($SegmentData -isnot [array]) { + $SegmentData = @($SegmentData) + } + + $segmentJson = $SegmentData | ConvertTo-Json -Compress -Depth 10 + $segmentLength = [System.Text.Encoding]::UTF8.GetBytes($segmentJson).Length + + if ($segmentLength -gt $MaxSize) { + if ($SegmentData.Count -lt 2) { + Write-LogMessage "$SegmentLabel is larger than max size ($([Math]::Round($segmentLength/1MB, 2)) MB > $([Math]::Round($MaxSize/1MB, 2)) MB) and cannot be split further" -Level Error + return $false + } + + Write-LogMessage "$SegmentLabel is larger than max size ($([Math]::Round($segmentLength/1MB, 2)) MB). Retrying in 2 parts" -Level Warning + + $mid = [math]::Floor($SegmentData.Count / 2) + + if ($mid -lt 1) { + Write-LogMessage "Unable to split $SegmentLabel into 2 valid parts" -Level Error + return $false + } + + $firstHalf = @($SegmentData[0..($mid - 1)]) + $secondHalf = @($SegmentData[$mid..($SegmentData.Count - 1)]) + + $firstResult = Send-SentinelSegment -SegmentData $firstHalf -LogType $LogType -MaxSize $MaxSize -SegmentLabel "$SegmentLabel part 1/2" + $secondResult = Send-SentinelSegment -SegmentData $secondHalf -LogType $LogType -MaxSize $MaxSize -SegmentLabel "$SegmentLabel part 2/2" + + return ($firstResult -and $secondResult) + } + + Write-LogMessage "Sending $SegmentLabel ($([Math]::Round($segmentLength/1MB, 2)) MB) to Sentinel API $LogType" + + if ($Script:SentinelLogCollector.SentinelLogIngestionAPIActivated) { + return (Post-LogMonitorData -body $segmentJson -logType $LogType) + } + else { + $segmentBytes = [System.Text.Encoding]::UTF8.GetBytes($segmentJson) + return (Post-LogAnalyticsData ` + -customerId $Script:SentinelLogCollector.WorkspaceId ` + -sharedKey $Script:SentinelLogCollector.WorkspaceKey ` + -body $segmentBytes ` + -logType $LogType) + } + } + function Start-LogsInjestion { Param( @@ -1393,68 +1477,72 @@ $Script:SupportedConfigurationVersion = "2.4" throw("Input data cannot be converted into a JSON object. Please make sure that the input data is a standard PowerShell table") } - if ([String]::IsNullOrEmpty($OutputSentinelAPI)) {$OutputSentinelAPI = $Script:SentinelLogCollector.LogTypeName} + if ([String]::IsNullOrEmpty($OutputSentinelAPI)) + { + $OutputSentinelAPI = $Script:SentinelLogCollector.LogTypeName + } if ($Script:InstanceConfiguration.FileFilterType -match "Categorize" -and [String]::IsNullOrEmpty($Script:InstanceConfiguration.OutputName)) { $OutputSentinelAPI = $OutputSentinelAPI -replace 'ESI', "ESI-$($Script:InstanceConfiguration.Category)-" } + $maxSize = $Script:MaximalSentinelPacketSizeMb *1024*1024 + $ResultLength = [System.Text.Encoding]::UTF8.GetBytes($ResultInjsonFormat).Length - $contentDivision = [math]::Ceiling($ResultLength / ($Script:MaximalSentinelPacketSizeMb *1024*1024)) + $contentDivision = [math]::Ceiling($ResultLength / $maxSize) if ($contentDivision -le 1) { - Write-LogMessage -Message ("Upload payload size is less than $($Script:MaximalSentinelPacketSizeMb)Mb. It will be sent in 1 segment") - # Submit the data to the API endpoint - Post-LogAnalyticsData -customerId $Script:SentinelLogCollector.WorkspaceId ` - -sharedKey $Script:SentinelLogCollector.WorkspaceKey ` - -body ([System.Text.Encoding]::UTF8.GetBytes($ResultInjsonFormat)) ` - -logType $OutputSentinelAPI + Write-LogMessage -Message ("Upload payload size is less than $($Script:MaximalSentinelPacketSizeMb)Mb. It will be sent in 1 segment to $OutputSentinelAPI") + + try { + $result = Send-SentinelSegment -SegmentData $ResultInjsonFormat -LogType $OutputSentinelAPI -MaxSize $maxSize -SegmentLabel "segment 1/1" + + if (-not $result) { + Write-LogMessage -Message "Failed to send logs to Sentinel" -Level Error + } + } + catch + { + Write-LogMessage -Message ("Error sending to Sentinel. $_") -Level Error + } } else { - Write-LogMessage -Message ("Upload payload size is " + ($ResultLength/1024/1024).ToString("#.#") + "Mb, greater than $($Script:MaximalSentinelPacketSizeMb)Mb. It will be sent in $contentDivision segments") + Write-LogMessage -Message ("Upload payload size is " + ($ResultLength/1024/1024).ToString("#.#") + "Mb, greater than $($Script:MaximalSentinelPacketSizeMb)Mb. It will be sent in $contentDivision segments to $OutputSentinelAPI") $maxCount = [math]::Floor($ProcessedData.Count / $contentDivision) + $currentIndex = 0 - $maxSegmentCount = $maxCount - $CounterStart = 0 - $exitNextTime = $false - while ($exitNextTime -eq $false) - { - if ($maxSegmentCount -ge $ProcessedData.Count) - { - $maxSegmentCount = $ProcessedData.Count - $exitNextTime = $true + $allSucceeded = $true + + for ($i = 0; $i -lt $contentDivision; $i++) { + $endIndex = [math]::Min($currentIndex + $maxCount, $ProcessedData.Count) + + if ($i -eq ($contentDivision - 1)) { + $endIndex = $ProcessedData.Count } - Write-LogMessage -Message ("Sending Segment $CounterStart to $maxSegmentCount") - - $TempTable = @() - for ($Counter = $CounterStart; $Counter -lt $maxSegmentCount; $Counter++) + $segment = $ProcessedData[$currentIndex..($endIndex - 1)] + + Write-LogMessage "Sending segment $($i + 1)/$contentDivision (items $currentIndex to $($endIndex - 1)) for $OutputSentinelAPI" + try { - $TempTable += $ProcessedData[$Counter] - } - - $CounterStart = $maxSegmentCount - $maxSegmentCount += $maxCount - - $ResultInjsonFormat = $TempTable | ConvertTo-Json -Compress - - Write-LogMessage -Message ("Sending payload : $ResultInjsonFormat") - - try { - # Submit the data to the API endpoint - Post-LogAnalyticsData -customerId $Script:SentinelLogCollector.WorkspaceId ` - -sharedKey $Script:SentinelLogCollector.WorkspaceKey ` - -body ([System.Text.Encoding]::UTF8.GetBytes($ResultInjsonFormat)) ` - -logType $OutputSentinelAPI + $result = Send-SentinelSegment -SegmentData $segment -LogType $OutputSentinelAPI -MaxSize $maxSize -SegmentLabel "segment $($i + 1)/$contentDivision" + + if (-not $result) { + $allSucceeded = $false + Write-LogMessage "Failed to send segment $($i + 1) for $OutputSentinelAPI" -Level Error + } } - catch { - Write-LogMessage -Message ("Error sending to Sentinel. $_") -Level Error + catch + { + $allSucceeded = $false + Write-LogMessage ("Error sending segment $($i + 1) for $OutputSentinelAPI. $_") -Level Error } + $currentIndex = $endIndex } } } @@ -1500,7 +1588,7 @@ $Script:SupportedConfigurationVersion = "2.4" } } - $outputdirectorypath = Split-Path $script:outputpath + $outputdirectorypath = Split-Path $script:CSVOutputFile # If $OutputFileName does not end with .csv, add it if ($OutputFileName -notmatch ".csv$") @@ -1516,6 +1604,112 @@ $Script:SupportedConfigurationVersion = "2.4" $ProcessedData | Export-Csv -Path $OutputFileName -NoTypeInformation } + function SaveLogs-To-ForwarderPickup + { + Param( + $OutputName, + [switch] $PartialData, + $PartialDataName, + [switch] $PartialDataRawFormat, + $RawData, + [Parameter(Mandatory=$true)] + [string] $TargetPath + ) + + Write-LogMessage -Message "Forwarder Pickup - Saving logs to forwarder directory" + + # Validate and create target directory if needed + if (-not (Test-Path $TargetPath)) + { + try { + Write-LogMessage -Message "Creating forwarder pickup directory: $TargetPath" + New-Item -ItemType Directory -Path $TargetPath -Force | Out-Null + } + catch { + Write-LogMessage -Message "Failed to create forwarder pickup directory: $($_.Exception.Message)" -Level Error + throw "Unable to create forwarder pickup directory at $TargetPath" + } + } + + # Determine which data to process + if ($PartialDataRawFormat) + { + $ProcessedData = $RawData + } + elseif ($PartialData) + { + $ProcessedData = $script:Results[$OutputName][$PartialDataName] + } + else { + $ProcessedData = $script:Results[$OutputName] + } + + # Get output options and prepare filename + $OutputOptions = Get-OutputOptions -OutputName $OutputName + if ($OutputName -eq "Default") + { + $OutputOptions.OutputFileName = $OutputOptions.LogTypeName + } + else { $OutputFileName = $OutputOptions.OutputFileName } + + # If OutputFileName is a full/relative path, keep only the filename portion + # (the forwarder pickup directory is provided via $TargetPath and joined below) + if (-not [String]::IsNullOrEmpty($OutputFileName) -and ($OutputFileName -match '[\\/]')) + { + $OutputFileName = Split-Path -Path $OutputFileName -Leaf + } + + # Handle partial data naming + if ($PartialData -or $PartialDataRawFormat) + { + if ([String]::IsNullOrEmpty($PartialDataName)) + { + $Guid = [guid]::NewGuid().ToString() + $OutputFileName = $OutputFileName -replace ".csv|.json", "-$Guid.json" + } + else + { + $OutputFileName = $OutputFileName -replace ".csv|.json", "-$PartialDataName.json" + } + } + + # Ensure .json extension for forwarder pickup + if ($OutputFileName -notmatch ".json$") + { + $OutputFileName = $OutputFileName -replace ".csv$", ".json" + if ($OutputFileName -notmatch ".json$") + { + $OutputFileName = $OutputFileName + ".json" + } + } + + # Add timestamp if not present + if ((-not $ForceOutputWithoutDate -or $null -eq $ForceOutputWithoutDate) -and $OutputFileName -notmatch "-$DateSuffixForFile.json") + { + $OutputFileName = $OutputFileName -replace ".json", "-$DateSuffixForFile.json" + } + + # Build full path + $FullOutputPath = Join-Path -Path $TargetPath -ChildPath $OutputFileName + + try { + # Convert to JSON and save + Write-LogMessage -Message "Writing forwarder pickup file: $FullOutputPath" + + # For forwarder, we typically want an array of objects + $JsonOutput = $ProcessedData | ConvertTo-Json -Depth 10 -Compress + + # Write to file + $JsonOutput | Set-ESIContent -Path $FullOutputPath + + Write-LogMessage -Message "Successfully saved forwarder pickup file: $FullOutputPath (Size: $([Math]::Round(($JsonOutput.Length/1KB), 2)) KB)" + } + catch { + Write-LogMessage -Message "Failed to save forwarder pickup file: $($_.Exception.Message)" -Level Error + throw "Unable to save forwarder pickup file: $($_.Exception.Message)" + } + } + Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource) { $xHeaders = "x-ms-date:" + $date @@ -1563,7 +1757,7 @@ $Script:SupportedConfigurationVersion = "2.4" throw("Upload payload is too big and exceed the 32Mb limit for a single upload. Please reduce the payload size. Current payload size is: " + ($body.Length/1024/1024).ToString("#.#") + "Mb") } - Write-LogMessage -Message ("Upload payload size is " + ($body.Length/1024).ToString("#.#") + "Kb") + Write-LogMessage -Message ("Analytics API - Upload payload size is " + ($body.Length/1024).ToString("#.#") + "Kb - $logType") try { if ($Useproxy) @@ -1589,6 +1783,73 @@ $Script:SupportedConfigurationVersion = "2.4" else { throw ("Server returned an error response code:" + $response.StatusCode)} } + + Function Post-LogMonitorData + { + Param( + [string]$body, + [string]$logType + ) + + $method = "POST" + + $DceUri = $Script:SentinelLogCollector.DataCollectionEndpointURI + $DCRImmutableID = $Script:SentinelLogCollector.DCRImmutableId + $streamName = $logType + + # if StreamName doesn't begin with 'Custom-', add it + if (-not $streamName.StartsWith("Custom-")) { + $streamName = "Custom-$streamName" + } + + if ($null -eq $Script:SentinelLogIngestionToken) + { + Connect-LogIngestionAPI + } + + $bearerToken = $Script:SentinelLogIngestionToken.AccessToken + + $uri = "$DceUri/dataCollectionRules/$DCRImmutableID/streams/$($streamName)?api-version=2023-01-01" + + $headers = @{ + "Authorization" = "Bearer $bearerToken"; + "Content-Type"="application/json" + } + + #validate that payload data does not exceed limits + if ($body.Length -gt (0.9 *1024*1024)) + { + throw("Upload payload is too big and exceed the 1Mb limit for a single upload. Please reduce the payload size. Current payload size is: " + ($body.Length/1024/1024).ToString("#.#") + "Mb") + } + + Write-LogMessage -Message ("Upload payload size is " + ($body.Length/1024).ToString("#.#") + "Kb - Uri : $uri") + #Write-LogMessage -Message ("Body: $body") + + try { + if ($Useproxy) + { + $response = Invoke-WebRequest -Uri $uri -Method $method -Headers $headers -Body $body -UseBasicParsing -Proxy $Script:ProxyUrl + } + else { + $response = Invoke-WebRequest -Uri $uri -Method $method -Headers $headers -Body $body -UseBasicParsing + } + } + catch { + if ($_.Exception.Message.startswith('The remote name could not be resolved')) + { + throw ("Error - data could not be uploaded. Might be because workspace ID or private key are incorrect") + } + + throw ("Error - data could not be uploaded: " + $_.Exception.Message) + } + + # Present message according to the response code + if ($response.StatusCode -eq 200 -or $response.StatusCode -eq 204) + { Write-LogMessage "200 - Data was successfully uploaded" } + else + { throw ("Server returned an error response code:" + $response.StatusCode)} + } + #endregion Sentinel Upload Management #region Dynamic Cmdlet Management @@ -1673,6 +1934,25 @@ $Script:SupportedConfigurationVersion = "2.4" return $Object } + Function New-ExchangeSimulationInjection + { + Param( + [Parameter(Mandatory=$True)] [String] $InjectionInfo + ) + + + $Script:_InternalInjection = $true + $Script:Results["Default"] += New-Result -Section "InjectionMode" -PSCmdL "InjectionMode" -EntryDate $Script:DateSuffix -ScriptInstanceID $Script:ScriptInstanceID + + if ($InjectionInfo -match "Internal") + { + $Script:_InternalExchangeInformation = $Global:InjectionTest | ConvertFrom-Json + } + else { + $Script:_InternalExchangeInformation = Get-Content -Path $InjectionInfo | ConvertFrom-Json + } + } + #Function to construct the output file which depend on the section currently processing Function GetCmdletExec { @@ -1889,6 +2169,8 @@ $Script:SupportedConfigurationVersion = "2.4" if (-not [String]::IsNullOrEmpty($ServerProcessed)) { $Object | Add-Member Noteproperty -Name ProcessedByServer -value $ServerProcessed + }else { + $Object | Add-Member Noteproperty -Name ProcessedByServer -value $null } if ($EmptyCmdlet) @@ -1935,6 +2217,9 @@ $Script:SupportedConfigurationVersion = "2.4" { $Object | Add-Member Noteproperty -Name ProcessedByServer -value $ServerProcessed } + else { + $Object | Add-Member Noteproperty -Name ProcessedByServer -value $null + } # Compile other Attributes $Object | Add-Member Noteproperty -Name rawData -value ($Entry | ConvertTo-Json -Compress) @@ -2286,11 +2571,19 @@ $Script:SupportedConfigurationVersion = "2.4" Param( $NumberRunspace ) + $Script:RunspaceResults = [hashtable]::Synchronized(@{}) $Script:RunspaceResults.AvailableRunspaces = 0 $JobList = @() + + if ($Script:_InternalInjection) + { + Write-LogMessage -Message ("`tInternal Injection Mode - Runspace Creation - Ignored due to Injection Simulation") -NoOutput -Level Warning + return + } + Write-Host "Launching Runspace creation ..." for ($i = 0; $i -lt $NumberRunspace; $i++) @@ -3069,17 +3362,6 @@ $Script:SupportedConfigurationVersion = "2.4" $Script:GlobalParallelProcess = $false } - if ([string]::IsNullOrEmpty($jsonConfig.Output.DefaultOutputFile) -and -not $Global:isRunbook) {throw "No Output file in config, mandatory"} else {$Script:outputpath = $jsonConfig.Output.DefaultOutputFile} - if (-not [string]::IsNullOrEmpty($jsonConfig.Output.ExportDomainsInformation)) - { - $Script:ExportDomainsInformation = [Convert]::ToBoolean($jsonConfig.Output.ExportDomainsInformation) - } - else - { - $Script:ExportDomainsInformation = $false - } - - [int] $Script:ParralelWaitRunning if ($null -eq $jsonConfig.Advanced.ParralelWaitRunning) {[int] $Script:ParralelWaitRunning = 60} else {[int] $Script:ParralelWaitRunning = $jsonConfig.Advanced.ParralelWaitRunning} if ($null -eq $jsonConfig.Advanced.ParralelPingWaitRunning) {[int] $Script:ParralelPingWaitRunning = $Script:ParralelWaitRunning} else {[int] $Script:ParralelPingWaitRunning = $jsonConfig.Advanced.ParralelPingWaitRunning} @@ -3147,23 +3429,33 @@ $Script:SupportedConfigurationVersion = "2.4" } else { $Script:UpdateVersionCheckingDeactivated = $false } - - - if ($null -eq $jsonConfig.Advanced.MaximalSentinelPacketSizeMb) { - $script:MaximalSentinelPacketSizeMb = 31.9 - } - else { - if ($jsonConfig.Advanced.MaximalSentinelPacketSizeMb -ge 32) - { - Write-LogMessage -Message "Packet size $($jsonConfig.Advanced.MaximalSentinelPacketSizeMb) greater than 31.9Kb. Maximum size for Sentinel is 31.9Kb." - $script:MaximalSentinelPacketSizeMb = 31.9 + if (-not [string]::IsNullOrEmpty($jsonConfig.Advanced.ExplicitESIDataPath)) + { + $Script:ESIDataPath = $jsonConfig.Advanced.ExplicitESIDataPath + if (-not (Test-Path $Script:ESIDataPath)) + { + try { + New-Item -Path $Script:ESIDataPath -ItemType Directory -ErrorAction Stop + } + catch { + throw "Path $($Script:ESIDataPath) not found and impossible to create" + } } - else - { - [int] $Script:MaximalSentinelPacketSizeMb = $jsonConfig.Advanced.MaximalSentinelPacketSizeMb - 0.1 + } + else + { + $Script:ESIDataPath = $Script:scriptFolder + "\Data" + try { + if (-not (Test-Path $Script:ESIDataPath)) { New-Item -Path $Script:ESIDataPath -ItemType Directory -ErrorAction Stop } + } + catch { + throw "Path $($Script:ESIDataPath) not found and impossible to create" } } + + + if ($null -eq $jsonConfig.Advanced.PaginationErrorThreshold) { $script:PaginationErrorThreshold = 10 } @@ -3243,6 +3535,51 @@ $Script:SupportedConfigurationVersion = "2.4" $Script:MGGraphAzureRMAppId = "Unknown" } + if ($null -ne $jsonConfig.InternetAddonCollectionConfiguration) { + $Script:InternetAddonCollectionConfiguration = New-Object PSObject + + # Load properties UseGithubAPI, GithubRawUrlforOnPremises, GithubRawUrlforOnline, GithubAPIToken, GithubAPIConnectionType + if (-not [String]::IsNullOrEmpty($jsonConfig.InternetAddonCollectionConfiguration.UseGithubAPI)) { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name UseGithubAPI -value ([Convert]::ToBoolean($jsonConfig.InternetAddonCollectionConfiguration.UseGithubAPI)) + } + else { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name UseGithubAPI -value $false + } + if (-not [String]::IsNullOrEmpty($jsonConfig.InternetAddonCollectionConfiguration.GithubRawUrlforOnPremises)) { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnPremises -value $jsonConfig.InternetAddonCollectionConfiguration.GithubRawUrlforOnPremises + } + else { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnPremises -value "https://raw.githubusercontent.com/ESICollector/ESICollector-Addons/master" + } + if (-not [String]::IsNullOrEmpty($jsonConfig.InternetAddonCollectionConfiguration.GithubRawUrlforOnline)) { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnline -value $jsonConfig.InternetAddonCollectionConfiguration.GithubRawUrlforOnline + } + else { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnline -value "https://raw.githubusercontent.com/ESICollector/ESICollector-Addons/master" + } + if (-not [String]::IsNullOrEmpty($jsonConfig.InternetAddonCollectionConfiguration.GithubAPIToken)) { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIToken -value $jsonConfig.InternetAddonCollectionConfiguration.GithubAPIToken + } + else { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIToken -value "" + } + if (-not [String]::IsNullOrEmpty($jsonConfig.InternetAddonCollectionConfiguration.GithubAPIConnectionType)) { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIConnectionType -value $jsonConfig.InternetAddonCollectionConfiguration.GithubAPIConnectionType + } + else { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIConnectionType -value "NoAuth" + } + } + else { + $Script:InternetAddonCollectionConfiguration = New-Object PSObject + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name UseGithubAPI -value $false + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnPremises -value "https://raw.githubusercontent.com/ESICollector/ESICollector-Addons/master" + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnline -value "https://raw.githubusercontent.com/ESICollector/ESICollector-Addons/master" + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIToken -value "" + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIConnectionType -value "NoAuth" + + } + if ($null -ne $jsonConfig.InstanceConfiguration) { if ($null -ne $jsonConfig.InstanceConfiguration.$InstanceName) { @@ -3338,14 +3675,113 @@ $Script:SupportedConfigurationVersion = "2.4" $Script:SentinelLogCollector | Add-Member Noteproperty -Name WorkspaceKey -value $jsonConfig.LogCollection.WorkspaceKey $Script:SentinelLogCollector | Add-Member Noteproperty -Name LogTypeName -value $jsonConfig.LogCollection.LogTypeName $Script:SentinelLogCollector | Add-Member Noteproperty -Name TogetherMode -value ([Convert]::ToBoolean($jsonConfig.LogCollection.TogetherMode)) + $Script:SentinelLogCollector | Add-Member Noteproperty -Name SentinelLogIngestionAPIActivated -value $false + $Script:SentinelLogCollector | Add-Member Noteproperty -Name DataCollectionEndpointURI -value $jsonConfig.LogCollection.DataCollectionEndpointURI + $Script:SentinelLogCollector | Add-Member Noteproperty -Name DCRImmutableId -value $jsonConfig.LogCollection.DCRImmutableId + $Script:SentinelLogCollector | Add-Member Noteproperty -Name TargetLogTenantID -value $jsonConfig.LogCollection.TargetLogTenantID + $Script:SentinelLogCollector | Add-Member Noteproperty -Name TargetLogAppID -value $jsonConfig.LogCollection.TargetLogAppID + $Script:SentinelLogCollector | Add-Member Noteproperty -Name TargetLogCertificateThumbprint -value $jsonConfig.LogCollection.TargetLogCertificateThumbprint + $Script:SentinelLogCollector | Add-Member Noteproperty -Name UseManagedIdentity -value ([Convert]::ToBoolean($jsonConfig.LogCollection.UseManagedIdentity)) + $Script:SentinelLogCollector | Add-Member Noteproperty -Name TargetLogAppSecretReference -value $jsonConfig.LogCollection.TargetLogAppSecretReference + $script:SentinelLogCollector | Add-Member Noteproperty -Name UseForwarder -value ([Convert]::ToBoolean($jsonConfig.LogCollection.UseForwarder)) + $Script:SentinelLogCollector | Add-Member Noteproperty -Name ForwarderPickupPath -value $jsonConfig.LogCollection.ForwarderPickupPath if ($Script:SentinelLogCollector.ActivateLogUpdloadToSentinel) { - if ([string]::IsNullOrEmpty($Script:SentinelLogCollector.WorkspaceId) -or - [string]::IsNullOrEmpty($Script:SentinelLogCollector.WorkspaceKey) -or - [string]::IsNullOrEmpty($Script:SentinelLogCollector.LogTypeName)) + if ([string]::IsNullOrEmpty($Script:SentinelLogCollector.LogTypeName)) + { + throw "Sentinel Log Collector configuration is activated and LogTypeName is missing" + } + } + + if (-not [string]::IsNullOrEmpty($jsonConfig.LogCollection.SentinelLogIngestionAPIActivated)) + { + $Script:SentinelLogCollector.SentinelLogIngestionAPIActivated = [Convert]::ToBoolean($jsonConfig.LogCollection.SentinelLogIngestionAPIActivated) + } + else { $Script:SentinelLogCollector.SentinelLogIngestionAPIActivated = $false } + + + if ($Script:SentinelLogCollector.SentinelLogIngestionAPIActivated) + { + + #throw "Sentinel Log Collector configuration with new Log Ingestion API is currently not supported." + + if ([string]::IsNullOrEmpty($Script:SentinelLogCollector.DataCollectionEndpointURI) -or + [string]::IsNullOrEmpty($Script:SentinelLogCollector.DCRImmutableId) -or + $null -eq $Script:SentinelLogCollector.UseManagedIdentity -or ( + $Script:SentinelLogCollector.UseManagedIdentity -eq $false -and ( + [string]::IsNullOrEmpty($Script:SentinelLogCollector.TargetLogTenantID) -or + [string]::IsNullOrEmpty($Script:SentinelLogCollector.TargetLogAppID) -or + [string]::IsNullOrEmpty($Script:SentinelLogCollector.TargetLogCertificateThumbprint) + ) + )) { - throw "Sentinel Log Collector configuration is activated and contains wrong values." + throw "Sentinel Log Collector configuration with new Log Ingestion API Endpoint is activated but DCE URI or DCR Immutable Id or Authentication information are missing." + } + + if ($script:IsRunbook -and -not $Script:SentinelLogCollector.UseManagedIdentity) + { + try { + Get-AutomationVariable -Name $Script:SentinelLogCollector.TargetLogAppSecretReference -ErrorAction Stop | Out-Null + } + catch { + throw "Sentinel Log Collector configuration with new Log Ingestion API Endpoint is activated but Secret information is missing. Exception : $($_.Exception.Message)" + } + } + + } + else + { + if ($Script:SentinelLogCollector.ActivateLogUpdloadToSentinel) + { + if ([string]::IsNullOrEmpty($Script:SentinelLogCollector.WorkspaceId) -or + [string]::IsNullOrEmpty($Script:SentinelLogCollector.WorkspaceKey)) + { + throw "Sentinel Log Collector configuration is activated and contains wrong values." + } + + # ------------------------------------------------------------------ + # Legacy Log Analytics HTTP Data Collector API deprecation warning + # ------------------------------------------------------------------ + # Displayed on every execution while the collector still targets the + # legacy Log Analytics API. Clears once SentinelLogIngestionAPIActivated + # is set to true and the DCE/DCR configuration is provided. + # See migration guide: + # https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "================================================================================" + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "!! LEGACY LOG ANALYTICS HTTP DATA COLLECTOR API IS STILL IN USE" + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "!! This API is deprecated by Microsoft and will be retired." + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "!! Migrate to the Azure Monitor Log Ingestion API BEFORE end of support." + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "!! Set 'SentinelLogIngestionAPIActivated' to 'true' after deploying DCE/DCR." + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "!! Migration guide: https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI" + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "================================================================================" + } + } + + + if ($null -eq $jsonConfig.Advanced.MaximalSentinelPacketSizeMb) { + if ($Script:SentinelLogCollector.SentinelLogIngestionAPIActivated) { + Write-LogMessage -Message "Sentinel Log Ingestion API activated. Maximum size for Sentinel Log Ingestion is 1Mb." + $script:MaximalSentinelPacketSizeMb = 0.9 + } + else { + $script:MaximalSentinelPacketSizeMb = 31.9 + } + } + else { + if ($Script:SentinelLogCollector.SentinelLogIngestionAPIActivated -and $jsonConfig.Advanced.MaximalSentinelPacketSizeMb -ge 1) + { + Write-LogMessage -Message "Packet size $($jsonConfig.Advanced.MaximalSentinelPacketSizeMb) greater than 1Mb. Maximum size for Sentinel is 1 Mb." + $script:MaximalSentinelPacketSizeMb = 0.9 + } + elseif ($jsonConfig.Advanced.MaximalSentinelPacketSizeMb -ge 32) + { + Write-LogMessage -Message "Packet size $($jsonConfig.Advanced.MaximalSentinelPacketSizeMb) greater than 31.9Mb. Maximum size for Sentinel is 31.9Mb." + $script:MaximalSentinelPacketSizeMb = 31.9 + } + else + { + [int] $Script:MaximalSentinelPacketSizeMb = $jsonConfig.Advanced.MaximalSentinelPacketSizeMb - 0.1 } } @@ -3357,6 +3793,29 @@ $Script:SupportedConfigurationVersion = "2.4" { $Script:SentinelLogCollector.LogTypeName -replace "ESIExchangeConfig", "ESIExchangeOnlineConfig" } + + if ([string]::IsNullOrEmpty($jsonConfig.LogCollection.CSVOutputFile) ) {$Script:CSVOutputFile = "ExchSecIns.csv"} else + { + $Script:CSVOutputFile = $jsonConfig.LogCollection.CSVOutputFile + } + + # if CSVOutputFile is not a full path, add the default path + if (-not $Script:CSVOutputFile.Contains(":")) { $Script:CSVOutputFile = $Script:scriptFolder + "\" + $Script:CSVOutputFile } + + # check if the output path is valid and create it if needed + if (-not $Script:SentinelLogCollector.ActivateLogUpdloadToSentinel -or $Script:SentinelLogCollector.TogetherMode) + { + if (-not (Test-Path (Split-Path $Script:CSVOutputFile))) { New-Item -Path (Split-Path $Script:CSVOutputFile) -ItemType Directory -ErrorAction Stop } + } + + if (-not [string]::IsNullOrEmpty($jsonConfig.LogCollection.ExportDomainsInformation)) + { + $Script:ExportDomainsInformation = [Convert]::ToBoolean($jsonConfig.LogCollection.ExportDomainsInformation) + } + else + { + $Script:ExportDomainsInformation = $true + } } } @@ -3367,6 +3826,100 @@ $Script:SupportedConfigurationVersion = "2.4" } } + + function InvokeGithubGetFileContent + { + Param ( + $GithubSourcePath, + $FileName, + [switch] $Raw + ) + + if (-not $Script:InternetAddonCollectionConfiguration.UseGithubAPI) + { + Write-LogMessage -Message "UseGithubAPI is set to False, no Github API call will be made, Raw Content will be used" -NoOutput -Level Warning + + $uri = "$GithubSourcePath/$($FileName)" + + try { + if ($Useproxy) + { + $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing -Proxy $Script:ProxyUrl + } + else { + $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing + } + } + catch { + Write-LogMessage -Message "Impossible to retrieve file $($FileName) from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; + Throw "Impossible to load Audit Functions, Critical for collection. Error :" + $_ + } + + if ($Raw) + { + return $WebResult.Content + } + else + { + return $WebResult.Content | ConvertFrom-Json + } + } + else + { + # Use Github API to retrieve file content + $uri = "$GithubSourcePath/$($FileName)" + $headers = @{} + + $headers.Add("Accept", "application/vnd.github.raw+json") + $headers.Add("X-GitHub-Api-Version","2022-11-28") + + if (-not [string]::IsNullOrEmpty($Script:InternetAddonCollectionConfiguration.GithubAPIToken) -and + $Script:InternetAddonCollectionConfiguration.GithubAPIConnectionType -like "Bearer") + { + $headers.Add("Authorization", "Bearer $($Script:InternetAddonCollectionConfiguration.GithubAPIToken)") + } + + try { + if ($Useproxy) + { + $WebResult = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -UseBasicParsing -Proxy $Script:ProxyUrl + } + else { + $WebResult = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -UseBasicParsing + } + } + catch { + Write-LogMessage -Message "Impossible to retrieve file $($FileName) from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; + Throw "Impossible to load Audit Functions, Critical for collection. Error :" + $_ + } + + if ($null -eq $WebResult -or $null -eq $WebResult.Content) + { + Write-LogMessage -Message "Impossible to retrieve file $($FileName) from Online Github. Content is null" -NoOutput -Level Warning; + Throw "Impossible to load Audit Functions, Critical for collection. Error : Content is null" + } + + $Content = $WebResult.Content + + if ($WebResult.Encoding -eq "base64") + { + # Content is base64 encoded, decode it + $Content = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Contentt)) + } + + if ($Raw) + { + return $Content + } + else + { + return $Content | ConvertFrom-Json + } + } + + + } + function LoadAuditFunctionsFromInternetRepository { Param ( @@ -3381,17 +3934,18 @@ $Script:SupportedConfigurationVersion = "2.4" # Verify the cache directory exists $scriptFolder = $Script:scriptFolder $ScriptAddonCachePath = $scriptFolder + '\Config\Add-Ons\OnlineCache\' - $GithubSourcePath = "https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/ESICollector-Addons" + $GithubSourcePath = $Script:InternetAddonCollectionConfiguration.GithubRawUrlforOnPremises + $GithubSourceRelativePath = ""; if ($Beta) { - $GithubSourcePath += "/Beta" + $GithubSourceRelativePath += "/Beta" } if ($Script:InstanceConfiguration.FileFilterType -match "Categorize") { $ScriptAddonCachePath += "Categories\$($Script:InstanceConfiguration.Category)\" - $GithubSourcePath += "/Categories/$($Script:InstanceConfiguration.Category)/" + $GithubSourceRelativePath += "/Categories/$($Script:InstanceConfiguration.Category)/" } Push-Location ($scriptFolder); @@ -3440,14 +3994,8 @@ $Script:SupportedConfigurationVersion = "2.4" # Retrieve File Checksum list try { - if ($Useproxy) - { - $WebResult = invoke-WebRequest -Uri "$GithubSourcePath/ESIChecksumFiles.json" -UseBasicParsing -Proxy $Script:ProxyUrl - } - else - { - $WebResult = invoke-WebRequest -Uri "$GithubSourcePath/ESIChecksumFiles.json" -UseBasicParsing - } + $GithubSourcePathFileName = $GithubSourceRelativePath + "ESIChecksumFiles.json" + $WebResult = InvokeGithubGetFileContent -GithubSourcePath $GithubSourcePath -FileName $GithubSourcePathFileName -Raw } catch { Write-LogMessage -Message "Impossible to retrieve files from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; @@ -3465,7 +4013,7 @@ $Script:SupportedConfigurationVersion = "2.4" # If not empty, check each file from checksum list if (-not $CacheEmpty) { - $localHash = Get-FileHash -Path $ScriptAddonCachePath + "ESIChecksumFiles.json" -Algorithm SHA256 + $localHash = Get-FileHash -Path ($ScriptAddonCachePath + "ESIChecksumFiles.json") -Algorithm SHA256 $stringAsStream = [System.IO.MemoryStream]::new() $writer = [System.IO.StreamWriter]::new($stringAsStream) @@ -3491,16 +4039,10 @@ $Script:SupportedConfigurationVersion = "2.4" # Add all file in the list foreach ($OnlineFile in $OnlineFiles.Files) { - $uri = "$GithubSourcePath/$($OnlineFile.FileName)" + $uri = "$GithubSourceRelativePath/$($OnlineFile.FileName)" try { - if ($Useproxy) - { - $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing -Proxy $Script:ProxyUrl - } - else - { - $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing - } + + $WebResult = InvokeGithubGetFileContent -GithubSourcePath $GithubSourcePath -FileName $uri -Raw } catch { Write-LogMessage -Message "Impossible to retrieve file $($OnlineFile.FileName) from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; @@ -3518,6 +4060,10 @@ $Script:SupportedConfigurationVersion = "2.4" } } + # Load Audit Functions from Online Github repository + # This function is used in Runbook mode + # It retrieves the list of Audit Functions from the Online Github repository + function LoadAuditFunctionsForRunBook { Param ( @@ -3527,29 +4073,25 @@ $Script:SupportedConfigurationVersion = "2.4" # Process in Memory without storage # Retrieve File Checksum list - $GithubSourcePath = "https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/ESICollector-Addons" + $GithubSourcePath = $Script:InternetAddonCollectionConfiguration.GithubRawUrlforOnline + $GithubSourceRelativePath = ""; if ($Beta) { - $GithubSourcePath += "/Beta" + $GithubSourceRelativePath += "/Beta" } Write-LogMessage "Filter $($Script:InstanceConfiguration.FileFilterType) - Category $($Script:InstanceConfiguration.Category)" -NoOutput if ($Script:InstanceConfiguration.FileFilterType -match "Categorize") { - $GithubSourcePath += "/Categories/$($Script:InstanceConfiguration.Category)/" + $GithubSourceRelativePath += "/Categories/$($Script:InstanceConfiguration.Category)/" } + + $GithubSourcePathFileName = $GithubSourceRelativePath + "ESIChecksumFiles.json" try { - if ($Useproxy) - { - $WebResult = invoke-WebRequest -Uri "$GithubSourcePath/ESIChecksumFiles.json" -UseBasicParsing -Proxy $Script:ProxyUrl - } - else - { - $WebResult = invoke-WebRequest -Uri "$GithubSourcePath/ESIChecksumFiles.json" -UseBasicParsing - } + $OnlineFiles = InvokeGithubGetFileContent -GithubSourcePath $GithubSourcePath -FileName $GithubSourcePathFileName } catch { Write-LogMessage -Message "Impossible to retrieve files from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; @@ -3557,7 +4099,6 @@ $Script:SupportedConfigurationVersion = "2.4" } # Retrieve all list - $OnlineFiles = $WebResult.Content | ConvertFrom-Json $AuditFunctionList = @() foreach ($OnlineFile in $OnlineFiles.Files) @@ -3593,21 +4134,14 @@ $Script:SupportedConfigurationVersion = "2.4" if ($FileToIgnore) {continue;} - $uri = "$GithubSourcePath/$($OnlineFile.FileName)" + $urifilename = $GithubSourceRelativePath + $OnlineFile.FileName try { - if ($Useproxy) - { - $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing -Proxy $Script:ProxyUrl - } - else { - $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing - } + $OnlineAuditFunctionsFile = InvokeGithubGetFileContent -GithubSourcePath $GithubSourcePath -FileName $urifilename } catch { Write-LogMessage -Message "Impossible to retrieve file $($OnlineFile.FileName) from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; Throw "Impossible to load Audit Functions, Critical for collection. Error :" + $_ } - $OnlineAuditFunctionsFile = $WebResult.Content | ConvertFrom-Json Write-LogMessage -Message "Nb Audit Functions found :$($OnlineAuditFunctionsFile.AuditFunctions.count) for $($OnlineFile.FileName)" -NoOutput -Level Info; $AuditFunctionList += $OnlineAuditFunctionsFile.AuditFunctions } @@ -3887,6 +4421,13 @@ if (-not $NoDateTracing) Get-LastLaunchTime } +if ($ExchangeSimulationInjection) +{ + Write-LogMessage -Message "Injection mode activated, no real data will be collected" + + New-ExchangeSimulationInjection -InjectionInfo $SimulationInformation +} + Write-LogMessage -Message "Launching Capability analysis" Set-Capabilities -CapabilitiesList $Script:InstanceConfiguration.Capabilities @@ -3897,16 +4438,6 @@ if ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess) { [System.Collections.ArrayList] $script:RunningProcesses = @() -if (-not $Global:isRunbook) -{ - Write-Host ("Create/Validate Output file path") - if (-not (Test-Path (Split-Path $script:outputpath))) {mkdir (Split-Path $script:outputpath)} - if (-not $ForceOutputWithoutDate -or $null -eq $ForceOutputWithoutDate) - { - $script:outputpath = $script:outputpath -replace ".csv", "-$DateSuffixForFile.csv" - } -} - if (-not $Script:FunctionsListWithoutInternet) { $FunctionList = LoadAuditFunctionsFromInternetRepository -ProcessingType $Script:ESIProcessingType -Beta:$Script:BetaActivated @@ -3915,130 +4446,134 @@ else { $FunctionList = LoadAuditFunctions -AuditFunctionList $Script:JSonAuditFunctionList -ProcessingType $Script:ESIProcessingType -FromAddOnFolder:(-not $Script:FunctionsListInline) } -Write-LogMessage -Message ("Launch Data collection ...") -$inc = 1 - -Write-LogMessage -Message ("Launch Audit Function loop Collection ...") -foreach ($Entry in $FunctionList) +if (-not $Script:_InternalInjection) { - if ([string]::IsNullOrEmpty($Entry.Section)) - { - try { - Write-LogMessage -Message ("`tNo Section found for $($Entry.ToString()) / Collection aborted for this object due to security issue, continue to next object") -NoOutput -Level Error; - } - catch { - Write-LogMessage -Message ("`tNo Section found and Entry format can't be displayed for analysis / Collection aborted for this object due to security issue, continue to next object") -NoOutput -Level Error; - } - $inc++ - continue - } - - Write-LogMessage -Message ("`tLaunch collection $inc on $($FunctionList.count) for $($Entry.Section)") - $PaginationExecution = $false - if ($null -ne $Entry.PaginationInformation -and $Entry.PaginationInformation.PaginationActivated) { + Write-LogMessage -Message ("Launch Data collection ...") + $inc = 1 - Write-LogMessage -Message ("`tPagination information found for $($Entry.Section) $($Entry.PaginationInformation)") + Write-LogMessage -Message ("Launch Audit Function loop Collection ...") + foreach ($Entry in $FunctionList) + { + if ([string]::IsNullOrEmpty($Entry.Section)) + { + try { + Write-LogMessage -Message ("`tNo Section found for $($Entry.ToString()) / Collection aborted for this object due to security issue, continue to next object") -NoOutput -Level Error; + } + catch { + Write-LogMessage -Message ("`tNo Section found and Entry format can't be displayed for analysis / Collection aborted for this object due to security issue, continue to next object") -NoOutput -Level Error; + } + $inc++ + continue + } + + Write-LogMessage -Message ("`tLaunch collection $inc on $($FunctionList.count) for $($Entry.Section)") - $PaginationExecution = $true - $EntryOutStream = $Entry.OutputStream + "_Page" + $PaginationExecution = $false + if ($null -ne $Entry.PaginationInformation -and $Entry.PaginationInformation.PaginationActivated) { - if ($Entry.PaginationInformation.PartialDataUpload) - { - $EntryOutStream = $EntryOutStream + "_SentDuringExecution" - } + Write-LogMessage -Message ("`tPagination information found for $($Entry.Section) $($Entry.PaginationInformation)") - } - else { - Write-LogMessage -Message ("`tNo Pagination information found for $($Entry.Section)") - $EntryOutStream = $Entry.OutputStream - } + $PaginationExecution = $true + $EntryOutStream = $Entry.OutputStream + "_Page" - if ($Entry.DateStorageInformation.DateStorageActivated -and $Entry.DateStorageInformation.DateStorageMode -eq "DateFromAttribute") - { - $SaveDate = $True - $DateStorageAttribute = $Entry.DateStorageInformation.DateAttribute - } - else { - $SaveDate = $False - } + if ($Entry.PaginationInformation.PartialDataUpload) + { + $EntryOutStream = $EntryOutStream + "_SentDuringExecution" + } - if ($EntryOutStream -notin $Script:Results.Keys) { - Write-LogMessage -Message ("`tCreating Output Table for $($EntryOutStream)") - if ($PaginationExecution) { - $Script:Results[$EntryOutStream] = @{} } else { - $Script:Results[$EntryOutStream] = @() + Write-LogMessage -Message ("`tNo Pagination information found for $($Entry.Section)") + $EntryOutStream = $Entry.OutputStream } - } - $EntryCmdlet = $Entry.PSCmdL + if ($Entry.DateStorageInformation.DateStorageActivated -and $Entry.DateStorageInformation.DateStorageMode -eq "DateFromAttribute") + { + $SaveDate = $True + $DateStorageAttribute = $Entry.DateStorageInformation.DateAttribute + } + else { + $SaveDate = $False + } - if ($EntryCmdlet -match "#LastDateOfSection#" -and $Entry.DateStorageInformation.DateStorageActivated) - { - $EntryCmdlet = $EntryCmdlet -replace "#LastDateOfSection#", $Entry.DateStorageInformation.LastDateTracking - } + if ($EntryOutStream -notin $Script:Results.Keys) { + Write-LogMessage -Message ("`tCreating Output Table for $($EntryOutStream)") + if ($PaginationExecution) { + $Script:Results[$EntryOutStream] = @{} + } + else { + $Script:Results[$EntryOutStream] = @() + } + } - if ($Entry.ProcessPerServer) - { - if ($script:CapabilityLoaded -notcontains "OP") { - Write-LogMessage -Message ("`tImpossible to launch a Per Server action without OP capability") -Level Warning - $ErrorMessage = "`tImpossible to launch a Per Server action without OP capability" - - $Script:Results[$EntryOutStream] += New-Result -Section $Entry.Section -PSCmdL $EntryCmdlet -ErrorText $ErrorMessage -EntryDate $Script:DateSuffix -ScriptInstanceID $Script:ScriptInstanceID - continue; + $EntryCmdlet = $Entry.PSCmdL + + if ($EntryCmdlet -match "#LastDateOfSection#" -and $Entry.DateStorageInformation.DateStorageActivated) + { + $EntryCmdlet = $EntryCmdlet -replace "#LastDateOfSection#", $Entry.DateStorageInformation.LastDateTracking } - foreach ($ExchangeServer in $script:ExchangeServerList.ListSRVUp) + if ($Entry.ProcessPerServer) { - if (($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess) -and $Entry.NoRunpace -eq $false -and -not $PaginationExecution) { - processParallel -Entry $Entry -TargetServer $ExchangeServer -EntryCmdlet $EntryCmdlet -EntryOutStream $EntryOutStream -EntrySuboutputPage $EntrySuboutputPage -IsPaginated:$PaginationExecution + if ($script:CapabilityLoaded -notcontains "OP") { + Write-LogMessage -Message ("`tImpossible to launch a Per Server action without OP capability") -Level Warning + $ErrorMessage = "`tImpossible to launch a Per Server action without OP capability" + + $Script:Results[$EntryOutStream] += New-Result -Section $Entry.Section -PSCmdL $EntryCmdlet -ErrorText $ErrorMessage -EntryDate $Script:DateSuffix -ScriptInstanceID $Script:ScriptInstanceID + continue; + } + + foreach ($ExchangeServer in $script:ExchangeServerList.ListSRVUp) + { + if (($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess) -and $Entry.NoRunpace -eq $false -and -not $PaginationExecution) { + processParallel -Entry $Entry -TargetServer $ExchangeServer -EntryCmdlet $EntryCmdlet -EntryOutStream $EntryOutStream -EntrySuboutputPage $EntrySuboutputPage -IsPaginated:$PaginationExecution + } + else { + if ($PaginationExecution -and ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess)) + { + Write-LogMessage -Message ("`tParallel process for pagination not supported, fallback to sequential process for the section $($Entry.Section)") -Level Warning + } + + $Script:Results[$EntryOutStream] += GetCmdletExec -Section $Entry.Section -PSCmdL $EntryCmdlet -Select $Entry.Select -TransformationFunction $Entry.TransformationFunction -TargetServer $ExchangeServer -TransformationForeach:$Entry.TransformationForeach -SaveDate:$SaveDate -SaveDateAttribute $DateStorageAttribute -Entry $Entry -PaginationExecution:$PaginationExecution + } + } + } + else + { + if ($Script:GlobalParallelProcess -and $Entry.NoRunpace -eq $false -and -not $PaginationExecution) { + processParallel -Entry $Entry -EntryCmdlet $EntryCmdlet -EntryOutStream $EntryOutStream -EntrySuboutputPage $EntrySuboutputPage -IsPaginated:$PaginationExecution } else { if ($PaginationExecution -and ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess)) { Write-LogMessage -Message ("`tParallel process for pagination not supported, fallback to sequential process for the section $($Entry.Section)") -Level Warning } - - $Script:Results[$EntryOutStream] += GetCmdletExec -Section $Entry.Section -PSCmdL $EntryCmdlet -Select $Entry.Select -TransformationFunction $Entry.TransformationFunction -TargetServer $ExchangeServer -TransformationForeach:$Entry.TransformationForeach -SaveDate:$SaveDate -SaveDateAttribute $DateStorageAttribute -Entry $Entry -PaginationExecution:$PaginationExecution - } - } - } - else - { - if ($Script:GlobalParallelProcess -and $Entry.NoRunpace -eq $false -and -not $PaginationExecution) { - processParallel -Entry $Entry -EntryCmdlet $EntryCmdlet -EntryOutStream $EntryOutStream -EntrySuboutputPage $EntrySuboutputPage -IsPaginated:$PaginationExecution - } - else { - if ($PaginationExecution -and ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess)) - { - Write-LogMessage -Message ("`tParallel process for pagination not supported, fallback to sequential process for the section $($Entry.Section)") -Level Warning - } - $Script:Results[$EntryOutStream] += GetCmdletExec -Section $Entry.Section -PSCmdL $EntryCmdlet -Select $Entry.Select -TransformationFunction $Entry.TransformationFunction -TransformationForeach:$Entry.TransformationForeach -SaveDate:$SaveDate -SaveDateAttribute $DateStorageAttribute -Entry $Entry -PaginationExecution:$PaginationExecution + $Script:Results[$EntryOutStream] += GetCmdletExec -Section $Entry.Section -PSCmdL $EntryCmdlet -Select $Entry.Select -TransformationFunction $Entry.TransformationFunction -TransformationForeach:$Entry.TransformationForeach -SaveDate:$SaveDate -SaveDateAttribute $DateStorageAttribute -Entry $Entry -PaginationExecution:$PaginationExecution + } } - } - if ($Entry.DateStorageInformation.DateStorageActivated -and -not $SaveDate) - { - switch ($Entry.DateStorageInformation.DateStorageMode) + if ($Entry.DateStorageInformation.DateStorageActivated -and -not $SaveDate) { - "LastDate" { - Set-CurrentLaunchTime -SpecificFunction -FunctionName $Entry.Section -DateSuffix (Get-Date -Format "yyyy-MM-dd HH:mm:ss K") - } - "StartDateScript" { - Set-CurrentLaunchTime -SpecificFunction -FunctionName $Entry.Section -DateSuffix $Script:DateSuffix + switch ($Entry.DateStorageInformation.DateStorageMode) + { + "LastDate" { + Set-CurrentLaunchTime -SpecificFunction -FunctionName $Entry.Section -DateSuffix (Get-Date -Format "yyyy-MM-dd HH:mm:ss K") + } + "StartDateScript" { + Set-CurrentLaunchTime -SpecificFunction -FunctionName $Entry.Section -DateSuffix $Script:DateSuffix + } } } - } - $inc++ -} + $inc++ + } -if ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess) { - WaitAndProcess + if ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess) { + WaitAndProcess + } } Write-LogMessage -Message ("Launch CSV Creation / Sentinel Payload uploading ...") diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/OnlineDeployment/README.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/OnlineDeployment/README.md index 3bc25e4fd77..1144042211c 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/OnlineDeployment/README.md +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/OnlineDeployment/README.md @@ -41,6 +41,20 @@ **.NOTES** Developed by ksangui@microsoft.com and Nicolas Lepagnez + + + Version : 8.0.0.0 - Released : IN DEV - nilepagn + - Implement Log Ingestion API for Sentinel (DCE/DCR-based ingestion replacing the legacy Log Analytics HTTP Data Collector API). + - New LogCollection settings : SentinelLogIngestionAPIActivated, DataCollectionEndpointURI, DCRImmutableId, UseManagedIdentity, TargetLogTenantID, TargetLogAppID, TargetLogCertificateThumbprint, TargetLogAppSecretReference. + - Both APIs are supported simultaneously, controlled by the SentinelLogIngestionAPIActivated toggle, to enable a phased migration. + - Adding runtime warning banner when the collector still uses the legacy Log Analytics HTTP Data Collector API. + See [ESI-PublicContent/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md](https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI) for the migration procedure. + - New Identity sub-property columns exposed by the DCR transformKql : Identity_Depth_d, Identity_DistinguishedName_s, Identity_DomainId_s, Identity_IsDeleted_b, Identity_IsRelativeDn_b, Identity_Name_s, Identity_ObjectGuid_g, Identity_Parent_s, Identity_PartitionFQDN_s, Identity_PartitionGuid_g, Identity_Rdn_s. + - Create $Script:ESIDataPath to store data in a specific folder and become independant from CSV configuration. + - Move ExportDomainsInformation to LogCollection Section in configuration. If set to true, the Domain Information will be exported in the Log Collection. Default Value is True as before. + - Change GitHub link for Configuration file to use the new repository. + - Adding possibility to use GitHub API instead of direct download for configuration file. + Version : 7.6.0.1 - Released : 26/07/2024 - nilepagn - Adding Try-Catch on Get-AutomationVariable Test - Correct a bug on Get-LastVersion with Write-LogMessage diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Parameters.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Parameters.md index 7d985d4582f..2e141883702 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Parameters.md +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/Parameters.md @@ -1,6 +1,6 @@ # ExchSecIns Configuration -Actual Parameter version : 2.5 +Actual Parameter version : 3.0 ## Table of Contents @@ -8,18 +8,21 @@ Actual Parameter version : 2.5 - [Table of Contents](#table-of-contents) - [Parameters](#parameters) - [Global](#global) - - [Output](#output) - [Advanced](#advanced) - [LogCollection](#logcollection) + - [InternetAddonCollectionConfiguration](#internetaddoncollectionconfiguration) - [MGGraphAPIConnection](#mggraphapiconnection) - [InstanceConfiguration](#instanceconfiguration) - [AuditFunctionsFiles](#auditfunctionsfiles) - [AuditFunctionProtectedArea](#auditfunctionprotectedarea) - [Description](#description) - [UDSLogProcessor](#udslogprocessor) + - [Azure Monitor Log Ingestion API parameters](#azure-monitor-log-ingestion-api-parameters) + - [InternetAddonCollectionConfiguration](#internetaddoncollectionconfiguration-1) - [InstanceConfiguration](#instanceconfiguration-1) - [AuditFunctionsFiles](#auditfunctionsfiles-1) - [other parameters](#other-parameters) + - [Migration from configuration version 2.5 to 3.0](#migration-from-configuration-version-25-to-30) ## Parameters @@ -27,83 +30,107 @@ Parameters can be found in the "CollectExchSecConfiguration.json" file for On-Pr ### Global -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| ParallelTimeoutMinutes | Int | Maximum time in minutes to wait for a parallel job to finish | 5 | False | -| MaxParallelRunningJobs | Int | Maximum number of parallel jobs running at the same time | 8 | False | -| GlobalParallelProcessing | Boolean | Activate the collection of information by using paralleling mechanism. Recommanded | true | False | -| PerServerParallelProcessing | Boolean | Activate the collection of information concerning a specific server by using paralleling mechanism. Recommanded | true | False | -| DefaultDurationTracking | Int | Default duration tracking in days | 30 | False | -| ESIProcessingType | String | Type of processing, online or offline | Online | False | -| EnvironmentIdentification | String | Identification of the environment. Could be any text, the name of the tenant or AD domain | MyOwnEnvironment | False | +| Parameter | Type | Description | Default | Required | +|-----------------------------|---------|--------------------------------------------------------------------------------------------|------------------|----------| +| ParallelTimeoutMinutes | Int | Maximum time in minutes to wait for a parallel job to finish | 5 | False | +| MaxParallelRunningJobs | Int | Maximum number of parallel jobs running at the same time | 8 | False | +| GlobalParallelProcessing | Boolean | Activate the collection of information by using paralleling mechanism. Recommanded | true | False | +| PerServerParallelProcessing | Boolean | Activate the collection of information concerning a specific server by using paralleling | true | False | +| DefaultDurationTracking | Int | Default duration tracking in days | 30 | False | +| ESIProcessingType | String | Type of processing, online or offline | Online | False | +| EnvironmentIdentification | String | Identification of the environment. Could be any text, the name of the tenant or AD domain | MyOwnEnvironment | False | -### Output - -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| DefaultOutputFile | String | Default output file where data will be written if log collection by API is not activated. | C:\ExchSecIns\data\ExchSecIns.csv | False | -| ExportDomainsInformation | Boolean | Export AD Domain Information in Sentinel Table | True | False | +> [!NOTE] +> The `Output` section has been removed in configuration version 3.0. `ExportDomainsInformation` and the output file setting are now part of the [LogCollection](#logcollection) section. ### Advanced -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| ParralelWaitRunning | Int | Time in seconds to wait for parallel processing before considering a timeout | 10 | False | -| ParralelPingWaitRunning | Int | Time in seconds to wait for parallel ping processing before considering a timeout | 10 | False | -| OnlyExplicitActivation | Boolean | Only the explicit activation of the functions are processed. In this mode, each function needs to be taggued for processig | false | False | -| ExchangeServerBinPath | String | Path of the Exchange Server Binaries. Could be changed if Exchange is not installed in the default folder path | c:\Program Files\Microsoft\Exchange Server\V15\bin | False | -| BypassServerAvailabilityTest | Boolean | Bypass the server availability test. If this feature is activated, the collector will try to work with all servers including inaccessible servers. | false | False | -| ExplicitExchangeServerList | Array | List of explicit Exchange servers. If the previous parameter is activated, it could be good to build a static list of server to use | [] | False | -| FunctionsListInline | Boolean | Functions list inline. The functions will be read in the main config file. This option is more for retrocompatibility | false | False | -| FunctionsListWithoutInternet | Boolean | Functions list without internet. If this option is activated, the collector will use the local files instead of files in the Github repository | false | False | -| Beta | Boolean | Activating Beta feature, collecting Beta version of functions to execute. | false | False | -| Useproxy | Boolean | Use Proxy boolean if you need it. The next option need to be filled. | false | False | -| ProxyUrl | String | Proxy URL | http://proxy.dom.net:8080 | False | -| MaximalSentinelPacketSizeMb | Int | Max Packet size for Sentinel in Mb | 32 | False | -| PaginationErrorThreshold | Int | Pagination Error Threshold when an executed function use a pagination | 5 | False | -| UpdateVersionCheckingDeactivated | Boolean | Deactivate the version checking | false | False | -| DeactivateUDSLogs | Boolean | Deactivate the log summary (Called USD Logs) at the end of the script. | false | False | -| LogVerboseActivated | Boolean | Log Verbose Activated | true | False | -| UDSLogProcessor | Array | UDS Log Processor definition. By Default USD logs are displayed at the end. It could stored in a file or an Azure Storage account if needed. See Below description. | [{Activated:true, StorageType:Output}] | False | +| Parameter | Type | Description | Default | Required | +|----------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|----------| +| ParralelWaitRunning | Int | Time in seconds to wait for parallel processing before considering a timeout | 10 | False | +| ParralelPingWaitRunning | Int | Time in seconds to wait for parallel ping processing before considering a timeout | 10 | False | +| OnlyExplicitActivation | Boolean | Only the explicit activation of the functions are processed. In this mode, each function needs to be taggued for processig | false | False | +| ExchangeServerBinPath | String | Path of the Exchange Server Binaries. Could be changed if Exchange is not installed in the default folder path | c:\Program Files\Microsoft\Exchange Server\V15\bin | False | +| BypassServerAvailabilityTest | Boolean | Bypass the server availability test. If activated, the collector will try to work with all servers including inaccessible servers. | false | False | +| ExplicitExchangeServerList | Array | List of explicit Exchange servers. If the previous parameter is activated, it could be good to build a static list of servers to use | [] | False | +| FunctionsListInline | Boolean | Functions list inline. The functions will be read in the main config file. This option is more for retrocompatibility | false | False | +| FunctionsListWithoutInternet | Boolean | Functions list without internet. If activated, the collector will use the local files instead of files in the GitHub repository | false | False | +| Beta | Boolean | Activating Beta feature, collecting Beta version of functions to execute. | false | False | +| Useproxy | Boolean | Use Proxy boolean if you need it. The next option needs to be filled. | false | False | +| ProxyUrl | String | Proxy URL | http://proxy.dom.net:8080 | False | +| MaximalSentinelPacketSizeMb | Int | Max Packet size for Sentinel in Mb. **Recommended value with the Log Ingestion API is `0.9`** (payload limit of 1 MB per POST). | 32 | False | +| PaginationErrorThreshold | Int | Pagination Error Threshold when an executed function uses a pagination | 5 | False | +| UpdateVersionCheckingDeactivated | Boolean | Deactivate the version checking | false | False | +| ExplicitESIDataPath | String | Explicit path where the collector stores its data (CSV, tracking, cache). If empty, the default location is computed automatically from the script context. | (empty) | False | +| DeactivateUDSLogs | Boolean | Deactivate the log summary (called UDS Logs) at the end of the script. | false | False | +| LogVerboseActivated | Boolean | Log Verbose Activated | true | False | +| UDSLogProcessor | Array | UDS Log Processor definition. By default UDS logs are displayed at the end. They can be stored in a file or an Azure Storage account if needed. | [{Activated:true, StorageType:Output}] | False | ### LogCollection -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| ActivateLogUpdloadToSentinel | Boolean | Activate the log upload to Sentinel. If not activated, results are stored in a file. | true | False | -| WorkspaceId | String | Workspace Id | e15121b8-fc25-4ec2-8d21-44532bfd219a | False | -| WorkspaceKey | String | Workspace Key | WKey | False | -| LogTypeName | String | Name of the Table to store data. ESIExchangeConfig is the default table used by Sentinel Solution | ESIExchangeConfig | False | -| TogetherMode | Boolean | Together Mode can be activated to store results in a file in addition to sentinel upload | false | False | +| Parameter | Type | Description | Default | Required | +|----------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------|-------------------------------------------------| +| ActivateLogUpdloadToSentinel | Boolean | Activate the log upload to Sentinel. If not activated, results are stored in a file only. | true | False | +| WorkspaceId | String | Workspace ID. **Used only when `SentinelLogIngestionAPIActivated` is `false`** (legacy Log Analytics HTTP Data Collector API). | (guid) | Legacy API only | +| WorkspaceKey | String | Workspace Key. **Used only when `SentinelLogIngestionAPIActivated` is `false`** (legacy Log Analytics HTTP Data Collector API). | (key) | Legacy API only | +| LogTypeName | String | Name of the target table (legacy API) or stream suffix (new API — `Custom-` is sent to the DCR). | ESIExchangeConfig | False | +| TogetherMode | Boolean | If true, results are stored in a file **in addition to** the Sentinel upload. | false | False | +| SentinelLogIngestionAPIActivated | Boolean | Activate the **Azure Monitor Log Ingestion API** (DCE / DCR / Entra ID identity). When `true`, the legacy `WorkspaceId` / `WorkspaceKey` are ignored. | false | False | +| DataCollectionEndpointURI | String | URI of the Data Collection Endpoint (DCE) produced by the ARM template `azuredeploy_ESI_LogIngestionAPI.json`. | (empty) | Yes, if new API activated | +| DCRImmutableId | String | Immutable ID of the target Data Collection Rule (DCR). One of the outputs of the ARM template (`OnPremises`, `Online`, or `MessageTracking`). | (empty) | Yes, if new API activated | +| UseManagedIdentity | Boolean | If `true`, the collector uses the system-assigned managed identity (recommended for Azure Automation). If `false`, uses a certificate-based Entra ID service principal. | false | False | +| TargetLogTenantID | String | Tenant ID hosting the Entra ID application used for ingestion. | (empty) | Yes, if `UseManagedIdentity` = `false` | +| TargetLogAppID | String | Application (client) ID of the Entra ID application used for ingestion. | (empty) | Yes, if `UseManagedIdentity` = `false` | +| TargetLogCertificateThumbprint | String | Thumbprint of the certificate authenticating the Entra ID application (local certificate store). | (empty) | Yes, if `UseManagedIdentity` = `false` | +| TargetLogAppSecretReference | String | Name of the Automation variable referencing the certificate (Azure Automation, certificate mode). | (empty) | Azure Automation with certificate mode | +| CSVOutputFile | String | Default output file when `ActivateLogUpdloadToSentinel` is `false` or when `TogetherMode` is `true`. Replaces the previous `Output.DefaultOutputFile`. | ExchSecIns.csv | False | +| ExportDomainsInformation | Boolean | Export AD Domain Information in Sentinel Table. Moved from the removed `Output` section. | True | False | + +> [!IMPORTANT] +> The Log Ingestion API replaces the legacy Log Analytics HTTP Data Collector API. If `SentinelLogIngestionAPIActivated` is `false`, the collector emits a runtime warning banner at each execution. Full migration guide: [Migrate from the Log Analytics HTTP Data Collector API to the Log Ingestion API](../../Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md). + +### InternetAddonCollectionConfiguration + +Controls how the collector downloads Add-On files from GitHub. It replaces the historical implicit download through the raw content endpoint by giving the choice between the raw endpoint (unauthenticated) and the GitHub REST API (authenticated for higher rate limits or private repositories). + +| Parameter | Type | Description | Default | Required | +|-------------------------------|---------|--------------------------------------------------------------------------------------------------------------------------|----------------------------|--------------------------------------------------------------| +| UseGithubAPI | Boolean | If `true`, download Add-Ons through the GitHub REST API. If `false`, use the raw content endpoint (no authentication). | false | False | +| GithubRawUrlforOnPremises | String | Base URL of the raw endpoint for On-Premises Add-Ons. | (Azure-Sentinel raw URL) | Used when `UseGithubAPI` = `false` | +| GithubRawUrlforOnline | String | Base URL of the raw endpoint for Exchange Online Add-Ons. | (Azure-Sentinel raw URL) | Used when `UseGithubAPI` = `false` | +| GithubAPIToken | String | GitHub Personal Access Token used when `GithubAPIConnectionType` = `Token` and the token is embedded in the config file. | (empty) | With `GithubAPIConnectionType` = `Token` | +| GithubAPITokenVariableName | String | Name of the environment variable holding the GitHub token when using `EnvironmentVariable` mode. | (empty) | With `GithubAPIConnectionType` = `EnvironmentVariable` | +| GithubAPITokenSecretReference | String | Name of the Azure Automation variable referencing the GitHub token when running as a runbook. | (empty) | With `GithubAPIConnectionType` = `AutomationVariable` | +| GithubAPIConnectionType | String | Authentication mode for the GitHub API. Allowed values: `NoAuth`, `Token`, `EnvironmentVariable`, `AutomationVariable`. | NoAuth | Yes, if `UseGithubAPI` = `true` | ### MGGraphAPIConnection -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| MGGraphAzureRMCertificate | String | MGGraph Azure RM Certificate | | False | -| MGGraphAzureRMAppId | String | MGGraph Azure RM App Id | | False | +| Parameter | Type | Description | Default | Required | +|---------------------------|--------|------------------------------|---------|----------| +| MGGraphAzureRMCertificate | String | MGGraph Azure RM Certificate | | False | +| MGGraphAzureRMAppId | String | MGGraph Azure RM App Id | | False | ### InstanceConfiguration -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| Default | Object | Default configuration, see details below. | {All:true, Capabilities:OP\|OL\|MGGRAPH\|ADINFOS} | False | -| IIS-IoCs | Object | IIS IoCs configuration, see details below. | {All:true, Category:IIS-IoCs, Capabilities:IIS, OutputName:ESIIISIoCs} | False | -| ExchangeOnlineMessageTracking | Object | Exchange Online Message Tracking configuration, see details below. | {All:true, Category:OnlineMessageTracking, Capabilities:OL, OutputName:ExchangeOnlineMessageTracking} | False | -| InstanceExample | Object | Instance Example configuration, see details below. | {SelectedAddons:[Filename1, Filename2], FileteredAddons:[Filename1, Filename2]} | False | +| Parameter | Type | Description | Default | Required | +|-------------------------------|--------|-------------------------------------------------|------------------------------------------------------------------------------------------------------|----------| +| Default | Object | Default configuration, see details below. | {All:true, Capabilities:OP\|OL\|MGGRAPH\|ADINFOS} | False | +| IIS-IoCs | Object | IIS IoCs configuration, see details below. | {All:true, Category:IIS-IoCs, Capabilities:IIS, OutputName:ESIIISIoCs} | False | +| ExchangeOnlineMessageTracking | Object | Exchange Online Message Tracking configuration. | {All:true, Category:OnlineMessageTracking, Capabilities:OL, OutputName:ExchangeOnlineMessageTracking} | False | +| InstanceExample | Object | Instance Example configuration. | {SelectedAddons:[Filename1, Filename2], FileteredAddons:[Filename1, Filename2]} | False | ### AuditFunctionsFiles -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| Filename | String | Filename | FiletoIgnore | False | -| Deactivated | Boolean | Deactivated | false | False | +| Parameter | Type | Description | Default | Required | +|-------------|---------|-------------|--------------|----------| +| Filename | String | Filename | FiletoIgnore | False | +| Deactivated | Boolean | Deactivated | false | False | ### AuditFunctionProtectedArea -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| ContentCheckSum | String | Content CheckSum | | False | +| Parameter | Type | Description | Default | Required | +|-----------------|--------|------------------|---------|----------| +| ContentCheckSum | String | Content CheckSum | | False | ## Description @@ -112,7 +139,7 @@ Below are specific parameters and their description. ### UDSLogProcessor -The USDLogProcessor allows to describe the way the USD logs are displayed or stored. It could be stored in a file or an Azure Storage account if needed or only displayed. +The UDSLogProcessor allows to describe the way the UDS logs are displayed or stored. It could be stored in a file or an Azure Storage account if needed or only displayed. The UDSLogProcessor is an array of object. Each object contains the following parameters: - Activated: Boolean. If true, the log will be processed. @@ -127,9 +154,50 @@ The UDSLogProcessor is an array of object. Each object contains the following pa - ApplicationID: String. The application id. If the StorageType is AzureStorageAccount and ConnexionType is Certificate, this parameter is required. - CertificateThumbprint: String. The certificate thumbprint. If the StorageType is AzureStorageAccount and ConnexionType is Certificate, this parameter is required. +### Azure Monitor Log Ingestion API parameters + +Starting with configuration version 3.0, the collector natively supports the Azure Monitor **Log Ingestion API** in addition to the legacy Log Analytics HTTP Data Collector API. The switch between the two APIs is controlled by `SentinelLogIngestionAPIActivated` in the `LogCollection` section. Both APIs are supported simultaneously to enable a phased migration. + +Prerequisites when `SentinelLogIngestionAPIActivated` is `true`: + +- A **Data Collection Endpoint (DCE)** and one or several **Data Collection Rules (DCR)** must be deployed. The ARM template [azuredeploy_ESI_LogIngestionAPI.json](/Deployments/azuredeploy_ESI_LogIngestionAPI.json) provisions everything needed (DCE + 3 tables + 3 DCRs, each optional). +- An **identity** must exist for the collector: + - **System-assigned Managed Identity** on the Automation Account (`UseManagedIdentity = true`), or + - **Entra ID application** with a **certificate** in the local certificate store (`UseManagedIdentity = false`). +- The identity must hold the **Monitoring Metrics Publisher** role on the target DCR. + +Parameter selection matrix: + +| Scenario | Required parameters | +|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Legacy Log Analytics API | `SentinelLogIngestionAPIActivated = false`, `WorkspaceId`, `WorkspaceKey`, `LogTypeName` | +| New Log Ingestion API — Azure Automation with Managed Identity | `SentinelLogIngestionAPIActivated = true`, `DataCollectionEndpointURI`, `DCRImmutableId`, `UseManagedIdentity = true` | +| New Log Ingestion API — Entra ID application with local certificate | `SentinelLogIngestionAPIActivated = true`, `DataCollectionEndpointURI`, `DCRImmutableId`, `UseManagedIdentity = false`, `TargetLogTenantID`, `TargetLogAppID`, `TargetLogCertificateThumbprint` | +| New Log Ingestion API — Azure Automation with certificate | Same as above **plus** `TargetLogAppSecretReference` (Automation variable name storing the certificate reference) | + +Recommended companion setting: set `MaximalSentinelPacketSizeMb` to `0.9` when the Log Ingestion API is activated (payload limit is 1 MB per POST). + +Full end-to-end setup: [README_AzureMonitorSetup.md](../../Documentations/README_AzureMonitorSetup.md). Migration procedure from the legacy API: [Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md](../../Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md). + +### InternetAddonCollectionConfiguration + +The `InternetAddonCollectionConfiguration` section controls how the collector retrieves Add-On configuration files (JSON) from GitHub. + +Two modes are supported: + +- **Raw content endpoint** (`UseGithubAPI = false`) — unauthenticated download from `raw.githubusercontent.com`. Recommended for public repositories and low-frequency runs. Requires the `GithubRawUrlforOnPremises` and `GithubRawUrlforOnline` base URLs to point at the target branch. +- **GitHub REST API** (`UseGithubAPI = true`) — authenticated download from the GitHub API. Provides higher rate limits and supports private repositories. Authentication mode is chosen through `GithubAPIConnectionType`: + - `NoAuth` — anonymous API call. Subject to strict rate limits. + - `Token` — reads the PAT from `GithubAPIToken` (embedded in the config). + - `EnvironmentVariable` — reads the PAT from the environment variable whose name is `GithubAPITokenVariableName`. + - `AutomationVariable` — reads the PAT from the Azure Automation variable whose name is `GithubAPITokenSecretReference` (recommended for Runbook deployments). + +> [!TIP] +> For Runbook deployments accessing a private repository, use `UseGithubAPI = true` + `GithubAPIConnectionType = AutomationVariable` and store the PAT as an **encrypted** Automation variable. + ### InstanceConfiguration -The InstanceConfiguration allows to configure multiple instances to collect different data. 3 main instances are available: Default, IIS-IoCs and ExchangeOnlineMessageTracking. It's possible to configure more instances by using the InstanceExample example where InstanceExample is the name of the instance to configure. +The InstanceConfiguration allows to configure multiple instances to collect different data. 3 main instances are available: Default, IIS-IoCs and ExchangeOnlineMessageTracking. It's possible to configure more instances by using the InstanceExample example where InstanceExample is the name of the instance to configure. The InstanceConfiguration is an object. It contains the following parameters: - Default: Object. Default configuration, mandatory. It contains the following parameters: @@ -169,3 +237,17 @@ The AuditFunctionsFiles is an array of object. It's used to ignore a specific se The parameter AuditFunctionProtectedArea is not used for the moment. They are reserved for future use. The parameter AuditFunctions is not used anymore, only present for backward comptability. + +## Migration from configuration version 2.5 to 3.0 + +Configuration version 3.0 introduces the following structural changes: + +- The whole **`Output` section has been removed**. + - `Output.DefaultOutputFile` → `LogCollection.CSVOutputFile` + - `Output.ExportDomainsInformation` → `LogCollection.ExportDomainsInformation` +- New parameters in **`LogCollection`** to enable the Azure Monitor Log Ingestion API — see [LogCollection](#logcollection) and [Azure Monitor Log Ingestion API parameters](#azure-monitor-log-ingestion-api-parameters). +- New **`InternetAddonCollectionConfiguration`** section to control Add-On downloads through the GitHub raw endpoint or the authenticated GitHub REST API. +- New optional parameter **`ExplicitESIDataPath`** in `Advanced` to override the default data folder used by the collector. +- The default of **`MaximalSentinelPacketSizeMb`** should be lowered to `0.9` when the new API is used. + +Legacy configurations continue to work — the collector keeps supporting the legacy Log Analytics HTTP Data Collector API and displays a runtime warning banner at each execution until the migration is completed. See the dedicated migration guide: [Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md](../../Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md). diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/README.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/README.md index 213978ad0c7..c6dd59c3eb2 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/README.md +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/# - General Content/Solutions/ESICollector/README.md @@ -10,10 +10,52 @@ Parameters are described in the Configuration file. Explanation of the parameter ## Versioning -## Actual Version : 7.6.0.1 +## Actual Version : 8.0.0.0 ## Upgrade paths +### From 7.6.0.1 to 8.0.0.0 + +> [!IMPORTANT] +> Version 8.0.0.0 introduces native support for the **Azure Monitor Log Ingestion API** (DCE/DCR based) that replaces the legacy Log Analytics HTTP Data Collector API. The collector still supports both APIs, controlled by the `SentinelLogIngestionAPIActivated` toggle, so the upgrade can be performed in two phases (script upgrade first, cutover later). +> +> Full migration guide : [Migrate from the Log Analytics HTTP Data Collector API to the Log Ingestion API](https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI). + +#### **Configuration File** + +The configuration schema is backward compatible. Legacy configurations keep working with no change and will trigger a runtime warning banner to remind operators to migrate. + +New or updated settings: + +- **`LogCollection` section** — new keys required only if you switch to the Log Ingestion API: + - `SentinelLogIngestionAPIActivated` : `true` to activate the new API. Default `false`. + - `DataCollectionEndpointURI` : DCE URI produced by the ARM template (`azuredeploy_ESI_LogIngestionAPI.json`). + - `DCRImmutableId` : Immutable ID of the target DCR (Online, OnPremises, or MessageTracking). + - `UseManagedIdentity` : `true` for Azure Automation with system-assigned managed identity. + - `TargetLogTenantID` / `TargetLogAppID` / `TargetLogCertificateThumbprint` : required when `UseManagedIdentity` is `false` (certificate-based service principal). + - `TargetLogAppSecretReference` : Automation variable name referencing the certificate (Azure Automation, certificate mode). +- **`ExportDomainsInformation`** has moved from the `Global` section to the `LogCollection` section. Default value stays `true`. +- **`Advanced` section**: + - `MaximalSentinelPacketSizeMb` default lowered to `0.9` when the Log Ingestion API is used (payload limit of 1 MB per POST). + - New GitHub download settings for configuration retrieval via the GitHub API instead of raw download. + +Update the file manually or use the **WinformConfig editor** (`ExchSecIns/WinformConfig/SetupCollectExchSecConfiguration.ps1`), which validates the payload and hides the legacy `WorkspaceId` / `WorkspaceKey` fields once the Log Ingestion API is activated. + +#### **ESI Collector Script** + +Replace the old script version with the new one. Additional tasks depending on your target API: + +- **Staying on the legacy API temporarily** : nothing else to do. The collector will display a runtime warning banner at each execution until the migration is completed. +- **Switching to the Log Ingestion API** : + 1. Deploy the ARM template `Deployments/azuredeploy_ESI_LogIngestionAPI.json` (in Zip) or `Data Connectors/azuredeploy_ESI_LogIngestionAPI.json` (In Sentinel Solution) to provision the DCE, tables (`ESIAPIExchangeOnPremConfig_CL`, `ESIAPIExchangeOnlineConfig_CL`, `ExchangeOnlineMessageTracking_CL`) and DCRs. + 2. Assign **Monitoring Metrics Publisher** on each target DCR to the identity used by the collector (managed identity or Entra ID service principal). + 3. Update the configuration keys listed above. + 4. Trigger a manual run and verify the new `_CL` tables are populated. + +#### **Data model changes** + +The new tables include the `Identity` sub-property columns extracted at ingestion by the DCR `transformKql` : `Identity_Depth_d`, `Identity_DistinguishedName_s`, `Identity_DomainId_s`, `Identity_IsDeleted_b`, `Identity_IsRelativeDn_b`, `Identity_Name_s`, `Identity_ObjectGuid_g`, `Identity_Parent_s`, `Identity_PartitionFQDN_s`, `Identity_PartitionGuid_g`, `Identity_Rdn_s`. The original `Identity_s` string column is preserved. Analytic rules, hunting queries and workbooks that already rely on `Identity_s` remain valid. + ### From 7.6.0.0 to 7.6.0.1 #### **Configuration File** diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data Connectors/ESI-ExchangeOnPremisesCollector.json b/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data Connectors/ESI-ExchangeOnPremisesCollector.json index ab20473cb4c..e5337cf2f17 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data Connectors/ESI-ExchangeOnPremisesCollector.json +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data Connectors/ESI-ExchangeOnPremisesCollector.json @@ -58,6 +58,10 @@ } ], "customs": [ + { + "name": "Azure Log Analytics HTTP Data Collector API will be deprecated", + "description": "Azure Log Analytics HTTP Data Collector API will be deprecated, Update to Log Monitor API is REQUIRED. [Learn more](https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI)" + }, { "name": "Service Account with Organization Management role", "description": "The service Account that launch the script as scheduled task needs to be Organization Management to be able to retrieve all the needed security Information." @@ -70,7 +74,23 @@ }, "instructionSteps": [ { - "title": "1. Install the ESI Collector Script on a server with Exchange Admin PowerShell console", + "description": ">**NOTE - UPDATE**", + "instructions": [ + { + "parameters": { + "text": "

NOTE - UPDATE:

We recommend to Update the Collector to Version 8.0.0.0 or higher AND switching to new Log Monitor API.
The Collector Script Update procedure could be found here : ESI On-Premises Collector Update, Update to Log Monitor API procedure could be found here : Migrate From Log Analytics API To Log Ingestion API.
The new version of the Collector is using DCR (Data Collection Rules) for data collection. You need to configure DCR before running the script.", + "visible": true, + "inline": false + }, + "type": "InfoMessage" + } + ] + }, + { + "description": "**1 - Deploy DCE/DCR for Azure Monitor using Azure Resource Manager (ARM) Template**\n\n1. Click the **Deploy to Azure** button below. \n\n\t[![Deploy To Azure](https://aka.ms/deploytoazurebutton)](https://aka.ms/sentinel-ESI-LogMonitor-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the required fields'. \n>4. Mark the checkbox labeled **I agree to the terms and conditions stated above**. \n5. Click **Purchase** to deploy." + }, + { + "title": "2. Install the ESI Collector Script on a server with Exchange Admin PowerShell console", "description": "This is the script that will collect Exchange Information to push content in Microsoft Sentinel.\n ", "instructions": [ { @@ -100,7 +120,7 @@ ] }, { - "title": "2. Configure the ESI Collector Script", + "title": "3. Configure the ESI Collector Script", "description": "Be sure to be local administrator of the server.\nIn 'Run as Administrator' mode, launch the 'setup.ps1' script to configure the collector.\n Fill the Log Analytics (Microsoft Sentinel) Workspace information.\n Fill the Environment name or leave empty. By default, choose 'Def' as Default analysis. The other choices are for specific usage.", "instructions": [ { @@ -124,7 +144,7 @@ ] }, { - "title": "3. Schedule the ESI Collector Script (If not done by the Install Script due to lack of permission or ignored during installation)", + "title": "4. Schedule the ESI Collector Script (If not done by the Install Script due to lack of permission or ignored during installation)", "description": "The script needs to be scheduled to send Exchange configuration to Microsoft Sentinel.\n We recommend to schedule the script once a day.\n The account used to launch the Script needs to be member of the group Organization Management" }, { @@ -168,7 +188,7 @@ ], "metadata": { "id": "ed950fd7-e457-4a59-88f0-b9c949aa280d", - "version": "1.2.2", + "version": "2.0.0", "kind": "dataConnector", "source": { "kind": "solution", diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data Connectors/azuredeploy_ESI_LogIngestionAPI.json b/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data Connectors/azuredeploy_ESI_LogIngestionAPI.json new file mode 100644 index 00000000000..f44928d60a9 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data Connectors/azuredeploy_ESI_LogIngestionAPI.json @@ -0,0 +1,970 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "deployTables": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Master switch: deploy the custom Log Analytics tables. Set to false to deploy only the Data Collection Endpoint/Rules (assumes tables already exist)." + } + }, + "deployDataCollection": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Master switch: deploy the Data Collection Endpoint and Data Collection Rules. Set to false to deploy only the custom tables." + } + }, + "deployOnlineConfigTable": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Deploy the Online Configuration (ESIExchangeOnlineConfig_CL and its associated data flow)" + } + } + , + "deployOnPremConfigTable": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Deploy the On-Premises Configuration (ESIExchangeConfig_CL and its associated data flow)" + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace (has to be in same subscription, resource group, and region as the Data Collection Endpoint)" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources : Data Collection Endpoint and Log Analytics workspace (has to be in same subscription, resource group, and region as the Data Collection Endpoint)" + } + }, + "dataCollectionEndpointName": { + "type": "string", + "defaultValue": "DCE-ESI-LogIngestion", + "metadata": { + "description": "Name of the Data Collection Endpoint" + } + }, + "dataCollectionRuleOnlineConfigName": { + "type": "string", + "defaultValue": "DCR-ESI-OnlineConfig", + "metadata": { + "description": "Name of the Data Collection Rule for Online Config table" + } + }, + "dataCollectionRuleOnPremisesConfigName": { + "type": "string", + "defaultValue": "DCR-ESI-OnPremisesConfig", + "metadata": { + "description": "Name of the Data Collection Rule for On-Premises Config table" + } + }, + "dataCollectionRuleMessageTrackingName": { + "type": "string", + "defaultValue": "DCR-ESI-MessageTracking", + "metadata": { + "description": "Name of the Data Collection Rule for Message Tracking table" + } + }, + "retentionInDays": { + "type": "int", + "defaultValue": 90, + "minValue": 30, + "maxValue": 730, + "metadata": { + "description": "Retention period in days for the custom tables" + } + }, + "deployMessageTrackingTable": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Deploy the Message Tracking table (ExchangeOnlineMessageTracking_CL and its associated data flow)" + } + } + }, + "variables": { + "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]", + "dataCollectionEndpointId": "[resourceId('Microsoft.Insights/dataCollectionEndpoints', parameters('dataCollectionEndpointName'))]", + "configTableName": "ESIAPIExchangeOnlineConfig_CL", + "onPremConfigTableName": "ESIAPIExchangeOnPremisesConfig_CL", + "messageTrackingTableName": "ExchangeOnlineMessageTracking_CL", + "onPremConfigoutputStream": "Custom-ESIAPIExchangeOnPremisesConfig_CL", + "onlineConfigoutputStream": "Custom-ESIAPIExchangeOnlineConfig_CL" + }, + "resources": [ + { + "condition": "[parameters('deployDataCollection')]", + "type": "Microsoft.Insights/dataCollectionEndpoints", + "apiVersion": "2024-03-11", + "name": "[parameters('dataCollectionEndpointName')]", + "location": "[parameters('location')]", + "properties": { + "networkAcls": { + "publicNetworkAccess": "Enabled" + } + } + }, + { + "condition": "[and(parameters('deployTables'), parameters('deployOnPremConfigTable'))]", + "type": "Microsoft.OperationalInsights/workspaces/tables", + "apiVersion": "2022-10-01", + "name": "[concat(parameters('workspaceName'), '/', variables('onPremConfigTableName'))]", + "properties": { + "totalRetentionInDays": "[parameters('retentionInDays')]", + "plan": "Analytics", + "schema": { + "name": "[variables('onPremConfigTableName')]", + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime", + "description": "The timestamp when the log was generated" + }, + { + "name": "EntryDate_s", + "type": "string", + "description": "Date of the configuration entry" + }, + { + "name": "GenerationInstanceID_g", + "type": "string", + "description": "Unique identifier for the generation instance" + }, + { + "name": "ESIEnvironment_s", + "type": "string", + "description": "Exchange environment identifier" + }, + { + "name": "Section_s", + "type": "string", + "description": "Configuration section name" + }, + { + "name": "ExecutionResult_s", + "type": "string", + "description": "Execution result" + }, + { + "name": "Identity_s", + "type": "string", + "description": "Identity" + }, + { + "name": "Identity_Depth_d", + "type": "real", + "description": "Identity depth in the directory hierarchy" + }, + { + "name": "Identity_DistinguishedName_s", + "type": "string", + "description": "Identity distinguished name (LDAP DN)" + }, + { + "name": "Identity_DomainId_s", + "type": "string", + "description": "Identity domain identifier (serialized when object)" + }, + { + "name": "Identity_IsDeleted_b", + "type": "boolean", + "description": "Indicates whether the identity is deleted" + }, + { + "name": "Identity_IsRelativeDn_b", + "type": "boolean", + "description": "Indicates whether the DN is relative" + }, + { + "name": "Identity_Name_s", + "type": "string", + "description": "Identity name" + }, + { + "name": "Identity_ObjectGuid_g", + "type": "string", + "description": "Identity object GUID" + }, + { + "name": "Identity_Parent_s", + "type": "string", + "description": "Identity parent (serialized when object)" + }, + { + "name": "Identity_PartitionFQDN_s", + "type": "string", + "description": "Identity partition FQDN" + }, + { + "name": "Identity_PartitionGuid_g", + "type": "string", + "description": "Identity partition GUID" + }, + { + "name": "Identity_Rdn_s", + "type": "string", + "description": "Identity relative distinguished name (RDN, serialized when object)" + }, + { + "name": "IdentityString_s", + "type": "string", + "description": "Identity string" + }, + { + "name": "RawData_s", + "type": "string", + "description": "Raw configuration data in JSON format" + }, + { + "name": "Name_s", + "type": "string", + "description": "Name" + }, + { + "name": "ProcessedByServer_s", + "type": "string", + "description": "Processed by server" + }, + { + "name": "PSCmdL_s", + "type": "string", + "description": "PowerShell cmdlet" + }, + { + "name": "WhenChanged_t", + "type": "datetime", + "description": "When changed timestamp" + }, + { + "name": "WhenCreated_t", + "type": "datetime", + "description": "When created timestamp" + } + ] + }, + "retentionInDays": "[parameters('retentionInDays')]" + } + }, + { + "condition": "[and(parameters('deployTables'), parameters('deployOnlineConfigTable'))]", + "type": "Microsoft.OperationalInsights/workspaces/tables", + "apiVersion": "2022-10-01", + "name": "[concat(parameters('workspaceName'), '/', variables('configTableName'))]", + "properties": { + "totalRetentionInDays": "[parameters('retentionInDays')]", + "plan": "Analytics", + "schema": { + "name": "[variables('configTableName')]", + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime", + "description": "The timestamp when the log was generated" + }, + { + "name": "EntryDate_s", + "type": "string", + "description": "Date of the configuration entry" + }, + { + "name": "GenerationInstanceID_g", + "type": "string", + "description": "Unique identifier for the generation instance" + }, + { + "name": "ESIEnvironment_s", + "type": "string", + "description": "Exchange environment identifier" + }, + { + "name": "Section_s", + "type": "string", + "description": "Configuration section name" + }, + { + "name": "ExecutionResult_s", + "type": "string", + "description": "Execution result" + }, + { + "name": "Identity_s", + "type": "string", + "description": "Identity" + }, + { + "name": "Identity_Depth_d", + "type": "real", + "description": "Identity depth in the directory hierarchy" + }, + { + "name": "Identity_DistinguishedName_s", + "type": "string", + "description": "Identity distinguished name (LDAP DN)" + }, + { + "name": "Identity_DomainId_s", + "type": "string", + "description": "Identity domain identifier (serialized when object)" + }, + { + "name": "Identity_IsDeleted_b", + "type": "boolean", + "description": "Indicates whether the identity is deleted" + }, + { + "name": "Identity_IsRelativeDn_b", + "type": "boolean", + "description": "Indicates whether the DN is relative" + }, + { + "name": "Identity_Name_s", + "type": "string", + "description": "Identity name" + }, + { + "name": "Identity_ObjectGuid_g", + "type": "string", + "description": "Identity object GUID" + }, + { + "name": "Identity_Parent_s", + "type": "string", + "description": "Identity parent (serialized when object)" + }, + { + "name": "Identity_PartitionFQDN_s", + "type": "string", + "description": "Identity partition FQDN" + }, + { + "name": "Identity_PartitionGuid_g", + "type": "string", + "description": "Identity partition GUID" + }, + { + "name": "Identity_Rdn_s", + "type": "string", + "description": "Identity relative distinguished name (RDN, serialized when object)" + }, + { + "name": "IdentityString_s", + "type": "string", + "description": "Identity string" + }, + { + "name": "RawData_s", + "type": "string", + "description": "Raw configuration data in JSON format" + }, + { + "name": "Name_s", + "type": "string", + "description": "Name" + }, + { + "name": "ProcessedByServer_s", + "type": "string", + "description": "Processed by server" + }, + { + "name": "PSCmdL_s", + "type": "string", + "description": "PowerShell cmdlet" + }, + { + "name": "WhenChanged_t", + "type": "datetime", + "description": "When changed timestamp" + }, + { + "name": "WhenCreated_t", + "type": "datetime", + "description": "When created timestamp" + } + ] + }, + "retentionInDays": "[parameters('retentionInDays')]" + } + }, + { + "condition": "[and(parameters('deployTables'), parameters('deployMessageTrackingTable'))]", + "type": "Microsoft.OperationalInsights/workspaces/tables", + "apiVersion": "2022-10-01", + "name": "[concat(parameters('workspaceName'), '/', variables('messageTrackingTableName'))]", + "properties": { + "totalRetentionInDays": "[parameters('retentionInDays')]", + "plan": "Analytics", + "schema": { + "name": "[variables('messageTrackingTableName')]", + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime", + "description": "The timestamp when the log was generated" + }, + { + "name": "schemaVersion_s", + "type": "string", + "description": "Schema version of the log entry" + }, + { + "name": "clientIp_s", + "type": "string", + "description": "Client IP address" + }, + { + "name": "clientHostname_s", + "type": "string", + "description": "Client hostname" + }, + { + "name": "serverIp_s", + "type": "string", + "description": "Server IP address" + }, + { + "name": "senderHostname_s", + "type": "string", + "description": "Sender hostname" + }, + { + "name": "sourceContext_s", + "type": "string", + "description": "Source context information" + }, + { + "name": "connectorId_s", + "type": "string", + "description": "Connector identifier" + }, + { + "name": "source_s", + "type": "string", + "description": "Message source" + }, + { + "name": "eventId_s", + "type": "string", + "description": "Event identifier" + }, + { + "name": "internalMessageId_s", + "type": "string", + "description": "Internal message identifier" + }, + { + "name": "messageId_s", + "type": "string", + "description": "Message identifier" + }, + { + "name": "networkMessageId_s", + "type": "string", + "description": "Network message identifier" + }, + { + "name": "recipientAddress_s", + "type": "string", + "description": "Recipient email address" + }, + { + "name": "recipientStatus_s", + "type": "string", + "description": "Recipient delivery status" + }, + { + "name": "totalBytes_l", + "type": "long", + "description": "Total message size in bytes" + }, + { + "name": "recipientCount_i", + "type": "int", + "description": "Number of recipients" + }, + { + "name": "relatedRecipientAddress_s", + "type": "string", + "description": "Related recipient address" + }, + { + "name": "reference_s", + "type": "string", + "description": "Message reference" + }, + { + "name": "messageSubject_s", + "type": "string", + "description": "Email subject line" + }, + { + "name": "senderAddress_s", + "type": "string", + "description": "Sender email address" + }, + { + "name": "returnPath_s", + "type": "string", + "description": "Return path address" + }, + { + "name": "directionality_s", + "type": "string", + "description": "Message directionality (Originating/Incoming)" + }, + { + "name": "messageInfo_s", + "type": "string", + "description": "Additional message information" + }, + { + "name": "originalClientIp_s", + "type": "string", + "description": "Original client IP address" + }, + { + "name": "originalServerIp_s", + "type": "string", + "description": "Original server IP address" + }, + { + "name": "customData_s", + "type": "string", + "description": "Custom metadata" + }, + { + "name": "transportTrafficType_s", + "type": "string", + "description": "Transport traffic type" + }, + { + "name": "FilePath_s", + "type": "string", + "description": "File path of the log entry" + }, + { + "name": "logId_s", + "type": "string", + "description": "Log identifier" + }, + { + "name": "messageTrackingTenantId_s", + "type": "string", + "description": "Tenant identifier for the message tracking log" + } + ] + }, + "retentionInDays": "[parameters('retentionInDays')]" + } + }, + { + "condition": "[and(parameters('deployDataCollection'), parameters('deployOnPremConfigTable'))]", + "type": "Microsoft.Insights/dataCollectionRules", + "apiVersion": "2024-03-11", + "name": "[parameters('dataCollectionRuleOnPremisesConfigName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[variables('dataCollectionEndpointId')]", + "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), variables('onPremConfigTableName'))]" + ], + "properties": { + "dataCollectionEndpointId": "[variables('dataCollectionEndpointId')]", + "streamDeclarations": { + "Custom-ESIExchangeConfig": { + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime" + }, + { + "name": "EntryDate", + "type": "string" + }, + { + "name": "GenerationInstanceID", + "type": "string" + }, + { + "name": "ESIEnvironment", + "type": "string" + }, + { + "name": "Section", + "type": "string" + }, + { + "name": "ExecutionResult", + "type": "string" + }, + { + "name": "Identity", + "type": "string" + }, + { + "name": "IdentityString", + "type": "string" + }, + { + "name": "rawData", + "type": "string" + }, + { + "name": "Name", + "type": "string" + }, + { + "name": "ProcessedByServer", + "type": "string" + }, + { + "name": "PSCmdL", + "type": "string" + }, + { + "name": "WhenChanged", + "type": "datetime" + }, + { + "name": "WhenCreated", + "type": "datetime" + } + ] + } + }, + "destinations": { + "logAnalytics": [ + { + "workspaceResourceId": "[variables('workspaceResourceId')]", + "name": "ESIWorkspace" + } + ] + }, + "dataFlows": [ + { + "streams": [ + "Custom-ESIExchangeConfig" + ], + "destinations": [ + "ESIWorkspace" + ], + "transformKql": "source | extend TimeGenerated = now() | extend _id = parse_json(Identity) | extend Identity_Depth_d = toreal(_id.Depth), Identity_DistinguishedName_s = tostring(_id.DistinguishedName), Identity_DomainId_s = tostring(_id.DomainId), Identity_IsDeleted_b = tobool(_id.IsDeleted), Identity_IsRelativeDn_b = tobool(_id.IsRelativeDn), Identity_Name_s = tostring(_id.Name), Identity_ObjectGuid_g = tostring(_id.ObjectGuid), Identity_Parent_s = tostring(_id.Parent), Identity_PartitionFQDN_s = tostring(_id.PartitionFQDN), Identity_PartitionGuid_g = tostring(_id.PartitionGuid), Identity_Rdn_s = tostring(_id.Rdn) | project-away _id | project-rename EntryDate_s = EntryDate, GenerationInstanceID_g = GenerationInstanceID, ESIEnvironment_s = ESIEnvironment, Section_s = Section, ExecutionResult_s = ExecutionResult, Identity_s = Identity, IdentityString_s = IdentityString, RawData_s = rawData, Name_s = Name, ProcessedByServer_s = ProcessedByServer, PSCmdL_s = PSCmdL, WhenChanged_t = WhenChanged, WhenCreated_t = WhenCreated", + "outputStream": "[variables('onPremConfigoutputStream')]" + } + ] + } + }, + { + "condition": "[and(parameters('deployDataCollection'), parameters('deployOnlineConfigTable'))]", + "type": "Microsoft.Insights/dataCollectionRules", + "apiVersion": "2024-03-11", + "name": "[parameters('dataCollectionRuleOnlineConfigName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[variables('dataCollectionEndpointId')]", + "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), variables('configTableName'))]" + ], + "properties": { + "dataCollectionEndpointId": "[variables('dataCollectionEndpointId')]", + "streamDeclarations": { + "Custom-ESIExchangeOnlineConfig": { + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime" + }, + { + "name": "EntryDate", + "type": "string" + }, + { + "name": "GenerationInstanceID", + "type": "string" + }, + { + "name": "ESIEnvironment", + "type": "string" + }, + { + "name": "Section", + "type": "string" + }, + { + "name": "ExecutionResult", + "type": "string" + }, + { + "name": "Identity", + "type": "string" + }, + { + "name": "IdentityString", + "type": "string" + }, + { + "name": "rawData", + "type": "string" + }, + { + "name": "Name", + "type": "string" + }, + { + "name": "ProcessedByServer", + "type": "string" + }, + { + "name": "PSCmdL", + "type": "string" + }, + { + "name": "WhenChanged", + "type": "datetime" + }, + { + "name": "WhenCreated", + "type": "datetime" + } + ] + } + }, + "destinations": { + "logAnalytics": [ + { + "workspaceResourceId": "[variables('workspaceResourceId')]", + "name": "ESIWorkspace" + } + ] + }, + "dataFlows": [ + { + "streams": [ + "Custom-ESIExchangeOnlineConfig" + ], + "destinations": [ + "ESIWorkspace" + ], + "transformKql": "source | extend TimeGenerated = now() | extend _id = parse_json(Identity) | extend Identity_Depth_d = toreal(_id.Depth), Identity_DistinguishedName_s = tostring(_id.DistinguishedName), Identity_DomainId_s = tostring(_id.DomainId), Identity_IsDeleted_b = tobool(_id.IsDeleted), Identity_IsRelativeDn_b = tobool(_id.IsRelativeDn), Identity_Name_s = tostring(_id.Name), Identity_ObjectGuid_g = tostring(_id.ObjectGuid), Identity_Parent_s = tostring(_id.Parent), Identity_PartitionFQDN_s = tostring(_id.PartitionFQDN), Identity_PartitionGuid_g = tostring(_id.PartitionGuid), Identity_Rdn_s = tostring(_id.Rdn) | project-away _id | project-rename EntryDate_s = EntryDate, GenerationInstanceID_g = GenerationInstanceID, ESIEnvironment_s = ESIEnvironment, Section_s = Section, ExecutionResult_s = ExecutionResult, Identity_s = Identity, IdentityString_s = IdentityString, RawData_s = rawData, Name_s = Name, ProcessedByServer_s = ProcessedByServer, PSCmdL_s = PSCmdL, WhenChanged_t = WhenChanged, WhenCreated_t = WhenCreated", + "outputStream": "[variables('onlineConfigoutputStream')]" + } + ] + } + }, + { + "condition": "[and(parameters('deployDataCollection'), parameters('deployMessageTrackingTable'))]", + "type": "Microsoft.Insights/dataCollectionRules", + "apiVersion": "2024-03-11", + "name": "[parameters('dataCollectionRuleMessageTrackingName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[variables('dataCollectionEndpointId')]", + "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), variables('messageTrackingTableName'))]" + ], + "properties": { + "dataCollectionEndpointId": "[variables('dataCollectionEndpointId')]", + "streamDeclarations": { + "Custom-ExchangeOnlineMessageTracking": { + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime" + }, + { + "name": "schemaVersion", + "type": "string" + }, + { + "name": "clientIp", + "type": "string" + }, + { + "name": "clientHostname", + "type": "string" + }, + { + "name": "serverIp", + "type": "string" + }, + { + "name": "senderHostname", + "type": "string" + }, + { + "name": "sourceContext", + "type": "string" + }, + { + "name": "connectorId", + "type": "string" + }, + { + "name": "source", + "type": "string" + }, + { + "name": "eventId", + "type": "string" + }, + { + "name": "internalMessageId", + "type": "string" + }, + { + "name": "messageId", + "type": "string" + }, + { + "name": "networkMessageId", + "type": "string" + }, + { + "name": "recipientAddress", + "type": "string" + }, + { + "name": "recipientStatus", + "type": "string" + }, + { + "name": "totalBytes", + "type": "long" + }, + { + "name": "recipientCount", + "type": "int" + }, + { + "name": "relatedRecipientAddress", + "type": "string" + }, + { + "name": "reference", + "type": "string" + }, + { + "name": "messageSubject", + "type": "string" + }, + { + "name": "senderAddress", + "type": "string" + }, + { + "name": "returnPath", + "type": "string" + }, + { + "name": "directionality", + "type": "string" + }, + { + "name": "messageInfo", + "type": "string" + }, + { + "name": "originalClientIp", + "type": "string" + }, + { + "name": "originalServerIp", + "type": "string" + }, + { + "name": "customData", + "type": "string" + }, + { + "name": "transportTrafficType", + "type": "string" + }, + { + "name": "FilePath", + "type": "string" + }, + { + "name": "logId", + "type": "string" + }, + { + "name": "messageTrackingTenantId", + "type": "string" + } + ] + } + }, + "destinations": { + "logAnalytics": [ + { + "workspaceResourceId": "[variables('workspaceResourceId')]", + "name": "ESIWorkspace" + } + ] + }, + "dataFlows": [ + { + "streams": [ + "Custom-ExchangeOnlineMessageTracking" + ], + "destinations": [ + "ESIWorkspace" + ], + "transformKql": "source | extend TimeGenerated = now() | project-rename schemaVersion_s = schemaVersion, clientIp_s = clientIp, clientHostname_s = clientHostname, serverIp_s = serverIp, senderHostname_s = senderHostname, sourceContext_s = sourceContext, connectorId_s = connectorId, source_s = source, eventId_s = eventId, internalMessageId_s = internalMessageId, messageId_s = messageId, networkMessageId_s = networkMessageId, recipientAddress_s = recipientAddress, recipientStatus_s = recipientStatus, totalBytes_l = totalBytes, recipientCount_i = recipientCount, relatedRecipientAddress_s = relatedRecipientAddress, reference_s = reference, messageSubject_s = messageSubject, senderAddress_s = senderAddress, returnPath_s = returnPath, directionality_s = directionality, messageInfo_s = messageInfo, originalClientIp_s = originalClientIp, originalServerIp_s = originalServerIp, customData_s = customData, transportTrafficType_s = transportTrafficType, FilePath_s = FilePath, logId_s = logId, messageTrackingTenantId_s = messageTrackingTenantId", + "outputStream": "Custom-ExchangeOnlineMessageTracking_CL" + } + ] + } + } + ], + "outputs": { + "dataCollectionEndpointId": { + "type": "string", + "value": "[if(parameters('deployDataCollection'), variables('dataCollectionEndpointId'), 'Not deployed')]" + }, + "dataCollectionEndpointUri": { + "type": "string", + "value": "[if(parameters('deployDataCollection'), reference(variables('dataCollectionEndpointId'), '2024-03-11').logsIngestion.endpoint, 'Not deployed')]" + }, + "dataCollectionRuleOnPremisesConfigId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployOnPremConfigTable')), resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleOnPremisesConfigName')), 'Not deployed')]" + }, + "dataCollectionRuleOnPremisesConfigImmutableId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployOnPremConfigTable')), reference(resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleOnPremisesConfigName')), '2024-03-11').immutableId, 'Not deployed')]" + }, + "dataCollectionRuleOnlineConfigId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployOnlineConfigTable')), resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleOnlineConfigName')), 'Not deployed')]" + }, + "dataCollectionRuleOnlineConfigImmutableId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployOnlineConfigTable')), reference(resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleOnlineConfigName')), '2024-03-11').immutableId, 'Not deployed')]" + }, + "dataCollectionRuleMessageTrackingId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployMessageTrackingTable')), resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleMessageTrackingName')), 'Not deployed')]" + }, + "dataCollectionRuleMessageTrackingImmutableId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployMessageTrackingTable')), reference(resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleMessageTrackingName')), '2024-03-11').immutableId, 'Not deployed')]" + }, + "configTableName": { + "type": "string", + "value": "[if(and(parameters('deployTables'), parameters('deployOnlineConfigTable')), variables('configTableName'), 'Not deployed')]" + }, + "onPremConfigTableName": { + "type": "string", + "value": "[if(and(parameters('deployTables'), parameters('deployOnPremConfigTable')), variables('onPremConfigTableName'), 'Not deployed')]" + }, + "messageTrackingTableName": { + "type": "string", + "value": "[if(and(parameters('deployTables'), parameters('deployMessageTrackingTable')), variables('messageTrackingTableName'), 'Not deployed')]" + } + } +} diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data/Solution_MicrosoftExchangeSecurity.json b/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data/Solution_MicrosoftExchangeSecurity.json index b0662607b13..a4c28483a30 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data/Solution_MicrosoftExchangeSecurity.json +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/Data/Solution_MicrosoftExchangeSecurity.json @@ -35,8 +35,8 @@ "Watchlists/ExchangeVIP.json" ], "BasePath": "C:\\Git Repositories\\Azure-Sentinel\\Solutions\\Microsoft Exchange Security - Exchange On-Premises\\", - "Version": "3.3.2", + "Version": "4.0.0", "Metadata": "SolutionMetadata.json", "TemplateSpec": true, "Is1Pconnector": false -} \ No newline at end of file +} diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/Package/4.0.0.zip b/Solutions/Microsoft Exchange Security - Exchange On-Premises/Package/4.0.0.zip new file mode 100644 index 00000000000..a8b79c5f6f2 Binary files /dev/null and b/Solutions/Microsoft Exchange Security - Exchange On-Premises/Package/4.0.0.zip differ diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/Package/mainTemplate.json b/Solutions/Microsoft Exchange Security - Exchange On-Premises/Package/mainTemplate.json index 063f5616329..86bf3632f0f 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/Package/mainTemplate.json +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/Package/mainTemplate.json @@ -81,7 +81,7 @@ "email": "support@microsoft.com", "_email": "[variables('email')]", "_solutionName": "Microsoft Exchange Security - Exchange On-Premises", - "_solutionVersion": "3.3.2", + "_solutionVersion": "4.0.0", "solutionId": "microsoftsentinelcommunity.azure-sentinel-solution-exchangesecurityinsights", "_solutionId": "[variables('solutionId')]", "uiConfigId1": "ESI-ExchangeAdminAuditLogEvents", @@ -100,7 +100,7 @@ "dataConnectorId2": "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/dataConnectors', variables('_dataConnectorContentId2'))]", "_dataConnectorId2": "[variables('dataConnectorId2')]", "dataConnectorTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-dc-',uniquestring(variables('_dataConnectorContentId2'))))]", - "dataConnectorVersion2": "1.2.2", + "dataConnectorVersion2": "2.0.0", "_dataConnectorcontentProductId2": "[concat(take(variables('_solutionId'),50),'-','dc','-', uniqueString(concat(variables('_solutionId'),'-','DataConnector','-',variables('_dataConnectorContentId2'),'-', variables('dataConnectorVersion2'))))]", "uiConfigId3": "ESI-Opt1ExchangeAdminAuditLogsByEventLogs", "_uiConfigId3": "[variables('uiConfigId3')]", @@ -246,7 +246,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 3.3.2", + "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('dataConnectorVersion1')]", @@ -1786,7 +1786,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 3.3.2", + "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('dataConnectorVersion2')]", @@ -1860,6 +1860,10 @@ } ], "customs": [ + { + "name": "Azure Log Analytics HTTP Data Collector API will be deprecated", + "description": "Azure Log Analytics HTTP Data Collector API will be deprecated, Update to Log Monitor API is REQUIRED. [Learn more](https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI)" + }, { "name": "Service Account with Organization Management role", "description": "The service Account that launch the script as scheduled task needs to be Organization Management to be able to retrieve all the needed security Information." @@ -1871,6 +1875,22 @@ ] }, "instructionSteps": [ + { + "description": ">**NOTE - UPDATE**", + "instructions": [ + { + "parameters": { + "text": "

NOTE - UPDATE:

We recommend to Update the Collector to Version 8.0.0.0 or higher AND switching to new Log Monitor API.
The Collector Script Update procedure could be found here : ESI On-Premises Collector Update, Update to Log Monitor API procedure could be found here : Migrate From Log Analytics API To Log Ingestion API.
The new version of the Collector is using DCR (Data Collection Rules) for data collection. You need to configure DCR before running the script.", + "visible": true, + "inline": false + }, + "type": "InfoMessage" + } + ] + }, + { + "description": "**1 - Deploy DCE/DCR for Azure Monitor using Azure Resource Manager (ARM) Template**\n\n1. Click the **Deploy to Azure** button below. \n\n\t[![Deploy To Azure](https://aka.ms/deploytoazurebutton)](https://aka.ms/sentinel-ESI-LogMonitor-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the required fields'. \n>4. Mark the checkbox labeled **I agree to the terms and conditions stated above**. \n5. Click **Purchase** to deploy." + }, { "description": "This is the script that will collect Exchange Information to push content in Microsoft Sentinel.\n ", "instructions": [ @@ -1899,7 +1919,7 @@ "type": "InstructionStepsGroup" } ], - "title": "1. Install the ESI Collector Script on a server with Exchange Admin PowerShell console" + "title": "2. Install the ESI Collector Script on a server with Exchange Admin PowerShell console" }, { "description": "Be sure to be local administrator of the server.\nIn 'Run as Administrator' mode, launch the 'setup.ps1' script to configure the collector.\n Fill the Log Analytics (Microsoft Sentinel) Workspace information.\n Fill the Environment name or leave empty. By default, choose 'Def' as Default analysis. The other choices are for specific usage.", @@ -1923,11 +1943,11 @@ "type": "CopyableLabel" } ], - "title": "2. Configure the ESI Collector Script" + "title": "3. Configure the ESI Collector Script" }, { "description": "The script needs to be scheduled to send Exchange configuration to Microsoft Sentinel.\n We recommend to schedule the script once a day.\n The account used to launch the Script needs to be member of the group Organization Management", - "title": "3. Schedule the ESI Collector Script (If not done by the Install Script due to lack of permission or ignored during installation)" + "title": "4. Schedule the ESI Collector Script (If not done by the Install Script due to lack of permission or ignored during installation)" }, { "description": ">**NOTE:** This data connector depends on a parser based on a Kusto Function to work as expected. Parsers are automatically deployed with the solution. Follow the steps to create the Kusto Functions alias : [**ExchangeAdminAuditLogs**](https://aka.ms/sentinel-ESI-ExchangeCollector-ExchangeAdminAuditLogs-parser)", @@ -1969,7 +1989,7 @@ ], "metadata": { "id": "ed950fd7-e457-4a59-88f0-b9c949aa280d", - "version": "1.2.2", + "version": "2.0.0", "kind": "dataConnector", "source": { "kind": "solution", @@ -2122,6 +2142,10 @@ } ], "customs": [ + { + "name": "Azure Log Analytics HTTP Data Collector API will be deprecated", + "description": "Azure Log Analytics HTTP Data Collector API will be deprecated, Update to Log Monitor API is REQUIRED. [Learn more](https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI)" + }, { "name": "Service Account with Organization Management role", "description": "The service Account that launch the script as scheduled task needs to be Organization Management to be able to retrieve all the needed security Information." @@ -2133,6 +2157,22 @@ ] }, "instructionSteps": [ + { + "description": ">**NOTE - UPDATE**", + "instructions": [ + { + "parameters": { + "text": "

NOTE - UPDATE:

We recommend to Update the Collector to Version 8.0.0.0 or highier AND switching to new Log Monitor API.
The Collector Script Update procedure could be found here : ESI Online Collector Update", + "visible": true, + "inline": false + }, + "type": "InfoMessage" + } + ] + }, + { + "description": "**1 - Deploy DCE/DCR for Azure Monitor using Azure Resource Manager (ARM) Template**\n\n1. Click the **Deploy to Azure** button below. \n\n\t[![Deploy To Azure](https://aka.ms/deploytoazurebutton)](https://aka.ms/sentinel-ESI-LogMonitor-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the required fields'. \n>4. Mark the checkbox labeled **I agree to the terms and conditions stated above**. \n5. Click **Purchase** to deploy." + }, { "description": "This is the script that will collect Exchange Information to push content in Microsoft Sentinel.\n ", "instructions": [ @@ -2161,7 +2201,7 @@ "type": "InstructionStepsGroup" } ], - "title": "1. Install the ESI Collector Script on a server with Exchange Admin PowerShell console" + "title": "2. Install the ESI Collector Script on a server with Exchange Admin PowerShell console" }, { "description": "Be sure to be local administrator of the server.\nIn 'Run as Administrator' mode, launch the 'setup.ps1' script to configure the collector.\n Fill the Log Analytics (Microsoft Sentinel) Workspace information.\n Fill the Environment name or leave empty. By default, choose 'Def' as Default analysis. The other choices are for specific usage.", @@ -2185,11 +2225,11 @@ "type": "CopyableLabel" } ], - "title": "2. Configure the ESI Collector Script" + "title": "3. Configure the ESI Collector Script" }, { "description": "The script needs to be scheduled to send Exchange configuration to Microsoft Sentinel.\n We recommend to schedule the script once a day.\n The account used to launch the Script needs to be member of the group Organization Management", - "title": "3. Schedule the ESI Collector Script (If not done by the Install Script due to lack of permission or ignored during installation)" + "title": "4. Schedule the ESI Collector Script (If not done by the Install Script due to lack of permission or ignored during installation)" }, { "description": ">**NOTE:** This data connector depends on a parser based on a Kusto Function to work as expected. Parsers are automatically deployed with the solution. Follow the steps to create the Kusto Functions alias : [**ExchangeAdminAuditLogs**](https://aka.ms/sentinel-ESI-ExchangeCollector-ExchangeAdminAuditLogs-parser)", @@ -2242,7 +2282,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 3.3.2", + "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('dataConnectorVersion3')]", @@ -2724,7 +2764,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 3.3.2", + "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('dataConnectorVersion4')]", @@ -3176,7 +3216,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 3.3.2", + "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('dataConnectorVersion5')]", @@ -3564,7 +3604,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 3.3.2", + "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('dataConnectorVersion6')]", @@ -4018,7 +4058,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 3.3.2", + "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('dataConnectorVersion7')]", @@ -4506,7 +4546,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 3.3.2", + "description": "Microsoft Exchange Security - Exchange On-Premises data connector with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('dataConnectorVersion8')]", @@ -4994,7 +5034,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "ExchangeAdminAuditLogs Data Parser with template version 3.3.2", + "description": "ExchangeAdminAuditLogs Data Parser with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject1').parserVersion1]", @@ -5003,7 +5043,7 @@ "resources": [ { "name": "[variables('parserObject1')._parserName1]", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "type": "Microsoft.OperationalInsights/workspaces/savedSearches", "location": "[parameters('workspace-location')]", "properties": { @@ -5067,7 +5107,7 @@ }, { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "name": "[variables('parserObject1')._parserName1]", "location": "[parameters('workspace-location')]", "properties": { @@ -5124,7 +5164,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "ExchangeConfiguration Data Parser with template version 3.3.2", + "description": "ExchangeConfiguration Data Parser with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject2').parserVersion2]", @@ -5133,7 +5173,7 @@ "resources": [ { "name": "[variables('parserObject2')._parserName2]", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "type": "Microsoft.OperationalInsights/workspaces/savedSearches", "location": "[parameters('workspace-location')]", "properties": { @@ -5197,7 +5237,7 @@ }, { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "name": "[variables('parserObject2')._parserName2]", "location": "[parameters('workspace-location')]", "properties": { @@ -5254,7 +5294,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "ExchangeEnvironmentList Data Parser with template version 3.3.2", + "description": "ExchangeEnvironmentList Data Parser with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject3').parserVersion3]", @@ -5263,7 +5303,7 @@ "resources": [ { "name": "[variables('parserObject3')._parserName3]", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "type": "Microsoft.OperationalInsights/workspaces/savedSearches", "location": "[parameters('workspace-location')]", "properties": { @@ -5327,7 +5367,7 @@ }, { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "name": "[variables('parserObject3')._parserName3]", "location": "[parameters('workspace-location')]", "properties": { @@ -5384,7 +5424,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "MESCheckVIP Data Parser with template version 3.3.2", + "description": "MESCheckVIP Data Parser with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject4').parserVersion4]", @@ -5393,7 +5433,7 @@ "resources": [ { "name": "[variables('parserObject4')._parserName4]", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "type": "Microsoft.OperationalInsights/workspaces/savedSearches", "location": "[parameters('workspace-location')]", "properties": { @@ -5457,7 +5497,7 @@ }, { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "name": "[variables('parserObject4')._parserName4]", "location": "[parameters('workspace-location')]", "properties": { @@ -5514,7 +5554,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "MESCompareDataOnPMRA Data Parser with template version 3.3.2", + "description": "MESCompareDataOnPMRA Data Parser with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject5').parserVersion5]", @@ -5523,7 +5563,7 @@ "resources": [ { "name": "[variables('parserObject5')._parserName5]", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "type": "Microsoft.OperationalInsights/workspaces/savedSearches", "location": "[parameters('workspace-location')]", "properties": { @@ -5587,7 +5627,7 @@ }, { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "name": "[variables('parserObject5')._parserName5]", "location": "[parameters('workspace-location')]", "properties": { @@ -5644,7 +5684,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Least Privilege with RBAC Workbook with template version 3.3.2", + "description": "Microsoft Exchange Least Privilege with RBAC Workbook with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion1')]", @@ -5735,7 +5775,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Search AdminAuditLog Workbook with template version 3.3.2", + "description": "Microsoft Exchange Search AdminAuditLog Workbook with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion2')]", @@ -5826,7 +5866,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Admin Activity Workbook with template version 3.3.2", + "description": "Microsoft Exchange Admin Activity Workbook with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion3')]", @@ -5917,7 +5957,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security Review Workbook with template version 3.3.2", + "description": "Microsoft Exchange Security Review Workbook with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion4')]", @@ -6008,7 +6048,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "CriticalCmdletsUsageDetection_AnalyticalRules Analytics Rule with template version 3.3.2", + "description": "CriticalCmdletsUsageDetection_AnalyticalRules Analytics Rule with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('analyticRuleObject1').analyticRuleVersion1]", @@ -6036,10 +6076,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "ESI-ExchangeAdminAuditLogEvents", "dataTypes": [ "Event" - ], - "connectorId": "ESI-ExchangeAdminAuditLogEvents" + ] } ], "tactics": [ @@ -6099,8 +6139,8 @@ } ], "alertDetailsOverride": { - "alertSeverityColumnName": "Level", "alertDescriptionFormat": "Alert from Microsoft Exchange Security as {{CmdletName}} with parameters {{CmdletParameters}} was executed on {{TargetObject}}", + "alertSeverityColumnName": "Level", "alertDisplayNameFormat": "{{CmdletName}} executed on {{TargetObject}}" } } @@ -6155,7 +6195,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "ServerOrientedWithUserOrientedAdministration_AnalyticalRules Analytics Rule with template version 3.3.2", + "description": "ServerOrientedWithUserOrientedAdministration_AnalyticalRules Analytics Rule with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('analyticRuleObject2').analyticRuleVersion2]", @@ -6183,10 +6223,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "ESI-ExchangeAdminAuditLogEvents", "dataTypes": [ "Event" - ], - "connectorId": "ESI-ExchangeAdminAuditLogEvents" + ] } ], "tactics": [ @@ -6329,7 +6369,7 @@ "apiVersion": "2023-04-01-preview", "location": "[parameters('workspace-location')]", "properties": { - "version": "3.3.2", + "version": "4.0.0", "kind": "Solution", "contentSchemaVersion": "3.0.0", "displayName": "Microsoft Exchange Security - Exchange On-Premises", @@ -6456,12 +6496,12 @@ { "kind": "Watchlist", "contentId": "[variables('_Exchange Services Monitoring')]", - "version": "3.3.2" + "version": "4.0.0" }, { "kind": "Watchlist", "contentId": "[variables('_Exchange VIP')]", - "version": "3.3.2" + "version": "4.0.0" } ] }, diff --git a/Solutions/Microsoft Exchange Security - Exchange On-Premises/ReleaseNotes.md b/Solutions/Microsoft Exchange Security - Exchange On-Premises/ReleaseNotes.md index a7a2acef3e7..233cedbbf5b 100644 --- a/Solutions/Microsoft Exchange Security - Exchange On-Premises/ReleaseNotes.md +++ b/Solutions/Microsoft Exchange Security - Exchange On-Premises/ReleaseNotes.md @@ -1,5 +1,6 @@ | **Version** | **Date Modified (DD-MM-YYYY)** | **Change History** | |-------------|--------------------------------|---------------------------------------------| +| 4.0.0 | 29-07-2026 | Migrate from Log Analytics API to Azure Monitor API | | 3.3.2 | 26-03-2025 | Update documentation link to new repository | | 3.3.0 | 26-08-2024 | Add Compare in Exchange Security Review. Create DataConnectors for Azure Monitor Agent. Correct bugs | | 3.2.0 | 09-04-2024 | Explode "ExchangeAdminAuditLogEvents" dataconnector to multiple simplier dataconnectors | diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md new file mode 100644 index 00000000000..cc4786b2d57 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md @@ -0,0 +1,252 @@ +# Migrate from the Log Analytics HTTP Data Collector API to the Log Ingestion API + +This guide describes how to migrate an existing Exchange Security Insights (ESI) deployment from the **legacy Log Analytics HTTP Data Collector API** (workspace ID + shared key) to the **Azure Monitor Log Ingestion API** (DCE + DCR + Entra ID identity). + +> [!IMPORTANT] +> Microsoft has announced the retirement of the Log Analytics HTTP Data Collector API. All ESI deployments must switch to the Log Ingestion API before the end of support. New tables use `_CL` schemas with typed columns and are managed through Data Collection Rules (DCR). + +## Why migrate + +- The legacy API relies on the workspace shared key, which is a long-lived secret. The Log Ingestion API uses **Entra ID identities** (service principal with certificate or system-assigned **managed identity**). +- The Log Ingestion API supports **DCR-based transforms** — the ESI templates now use `parse_json` to extract typed sub-columns (for example the new `Identity_*` columns) directly at ingestion. +- Legacy tables are auto-provisioned with generic string columns; DCR-based tables have explicit typed schemas, enabling better queries, cost control, and retention management. +- The legacy API endpoint is **deprecated** and will stop accepting new data at end of support. + +## Migration overview + +The migration is a three-step process. You can perform all three steps back-to-back or spread them over multiple maintenance windows because the collector supports both APIs during the transition (`SentinelLogIngestionAPIActivated` toggle). + +```text +Step 1 Step 2 Step 3 +Upgrade script Deploy Azure infrastructure Update collector configuration +CollectExchSecIns.ps1 -> DCE + DCRs + tables (ARM template) -> JSON / WinformConfig +(v8.0.0.0 or higher) + role assignments +``` + +| Step | Duration | Impact on production | +|------|----------------|-------------------------------------------------------| +| 1 | Short | None until the config file is switched (Step 3). | +| 2 | Short | None. New tables run in parallel with legacy tables. | +| 3 | Short | Ingestion path switches to the Log Ingestion API. | + +> [!TIP] +> Perform steps 1 and 2 in advance. Step 3 is the actual cutover and can be scheduled during a low-traffic window. + +## Prerequisites + +- Owner or Contributor on the target resource group. +- Access to the Log Analytics workspace hosting the current ESI tables. +- Ability to update the Automation Account `GlobalConfiguration` variable or the on-premises configuration file. +- (Recommended) Access to the [WinformConfig editor](./Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md) to edit `CollectExchSecConfiguration.json` interactively. + +## Step 1 — Upgrade the collector to v8.0.0.0 or higher + +The **v8.0.0.0** release of `CollectExchSecIns.ps1` adds native support for the Log Ingestion API through the `SentinelLogIngestionAPIActivated` switch, DCE/DCR endpoint properties, certificate-based authentication, and managed identity for Azure Automation. + +> [!IMPORTANT] +> Earlier versions of the collector only speak the legacy Log Analytics API. They cannot post to a DCE endpoint even if the configuration is updated. Upgrade the script **before** switching the configuration. + +### Azure Automation runbook + +1. Download the latest `CollectExchSecIns.ps1` (v8.0.0.0 or higher) from [the ESI script location](https://aka.ms/ESI-ExchangeCollector-Script). +2. Open the Automation Account, navigate to **Runbooks**, and select your ESI runbook. +3. Click **Edit** and replace the script content with the new version. +4. **Publish** the runbook. +5. Verify the runbook runs end-to-end with the existing (legacy) configuration. The behavior at this stage is unchanged — the collector still uses the legacy API because `SentinelLogIngestionAPIActivated` is still `false`. + +### On-premises / hybrid collector (server or scheduled task) + +1. Download the latest `CollectExchSecIns.ps1` (v8.0.0.0 or higher). +2. Replace the existing script file on the collector host (typical path: `C:\ESI\` or the location referenced by the scheduled task). +3. Run one manual execution to confirm the upgrade succeeded. Nothing else should change. + +Once running v8.0.0.0, the collector emits a runtime warning banner whenever it still uses the legacy Log Analytics API, reminding operators to complete the migration. This banner disappears after Step 3. + +## Step 2 — Deploy the Azure resources (DCE, DCRs, tables) + +The ARM template `azuredeploy_ESI_LogIngestionAPI.json` provisions everything you need: + +- 1 Data Collection Endpoint (DCE) +- Up to 3 custom Log Analytics tables (each optional): + - `ESIAPIExchangeOnPremConfig_CL` — Exchange On-Premises configuration. + - `ESIAPIExchangeOnlineConfig_CL` — Exchange Online configuration. + - `ExchangeOnlineMessageTracking_CL` — Message tracking. +- Up to 3 Data Collection Rules (one per table). + +> [!NOTE] +> The new tables live **alongside** the legacy `ESIExchangeConfig_CL` / `ExchangeOnlineMessageTracking_CL` tables. You can keep the legacy tables for historical queries or delete them after the migration. + +### 2.1 Deploy the template + +Deploy with Azure CLI, PowerShell, or the Azure Portal. Full instructions and parameter reference are in [README_LogIngestionAPI.md](./README_LogIngestionAPI.md). + +Minimal PowerShell deployment (deploy everything, defaults): + +```powershell +New-AzResourceGroupDeployment ` + -ResourceGroupName "rg-sentinel-esi" ` + -TemplateFile ".\ExchSecIns\Deployments\azuredeploy_ESI_LogIngestionAPI.json" ` + -workspaceName "law-sentinel-prod" +``` + +Deploy only the parts you need using the master switches: + +- Online-only tenant: set `deployOnPremConfigTable=false`. +- On-premises only: set `deployOnlineConfigTable=false` and `deployMessageTrackingTable=false`. +- Reuse existing tables: set `deployTables=false`. + +### 2.2 Collect the deployment outputs + +Record these values — they go into the collector configuration in Step 3: + +- `dataCollectionEndpointUri` +- `dataCollectionRuleOnPremisesConfigImmutableId` (if deployed) +- `dataCollectionRuleOnlineConfigImmutableId` (if deployed) +- `dataCollectionRuleMessageTrackingImmutableId` (if deployed) + +### 2.3 Set up the ingestion identity + +Choose one of the two authentication modes: + +- **Entra ID application + certificate** — recommended for on-premises and server scenarios. Follow the full procedure in [README_AzureMonitorSetup.md](./README_AzureMonitorSetup.md#step-1-create-a-self-signed-certificate). +- **System-assigned managed identity** — recommended for Azure Automation runbooks. Enable the system-assigned identity on the Automation Account. + +### 2.4 Assign the ingestion role + +Grant **Monitoring Metrics Publisher** to the identity on **each DCR** it will send data to. + +```powershell +$sp = Get-AzADServicePrincipal -ApplicationId "YOUR_APP_ID" # or the Automation Account MI + +New-AzRoleAssignment ` + -ObjectId $sp.Id ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions//resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" +# Repeat for DCR-ESI-OnPremisesConfig and DCR-ESI-MessageTracking as needed. +``` + +> [!WARNING] +> Without this role assignment, the collector receives `403 Forbidden` from the Log Ingestion API. Verify the role is present before Step 3. + +## Step 3 — Update the collector configuration + +The configuration change is the actual cutover. Once `SentinelLogIngestionAPIActivated` is `true`, the collector stops calling the legacy API on its next execution and starts using the DCE/DCR endpoint. + +### Option A — Update the configuration through the WinformConfig editor (recommended) + +The WinformConfig GUI walks you through the relevant fields, hides legacy fields (`WorkspaceId`, `WorkspaceKey`) once `SentinelLogIngestionAPIActivated` is set, and validates the payload before saving. + +1. From the ESI repository, open a PowerShell console and launch the editor: + + ```powershell + pwsh -ExecutionPolicy Bypass ` + -File .\ExchSecIns\WinformConfig\SetupCollectExchSecConfiguration.ps1 ` + -ConfigPath .\ExchSecIns\CollectExchSecConfiguration.json + ``` + +2. In the **Log Collection** section: + - Set `SentinelLogIngestionAPIActivated` to `True`. + - Fill `DataCollectionEndpointURI` with the DCE URI from Step 2.2. + - Fill `DCRImmutableId` with the DCR immutable ID matching the table you target (Online, OnPremises, or MessageTracking). + - Choose the authentication mode: + - **Managed identity** (Azure Automation): set `UseManagedIdentity` to `True`. + - **Certificate**: set `UseManagedIdentity` to `False`, then fill `TargetLogTenantID`, `TargetLogAppID`, and `TargetLogCertificateThumbprint`. + - `WorkspaceId` and `WorkspaceKey` become hidden and can stay as-is (they are ignored once `SentinelLogIngestionAPIActivated` is `True`). +3. Review the **Raw JSON preview** and the **diff summary**. +4. Save the file. If the collector runs from Azure Automation, upload the new JSON to the `GlobalConfiguration` variable. + +### Option B — Update the configuration file manually + +Edit `CollectExchSecConfiguration.json` and update the `LogCollection` section: + +```json +{ + "LogCollection": { + "ActivateLogUpdloadToSentinel": "true", + "SentinelLogIngestionAPIActivated": "true", + "DataCollectionEndpointURI": "https://..ingest.monitor.azure.com", + "DCRImmutableId": "dcr-00000000000000000000000000000000", + "UseManagedIdentity": "false", + "TargetLogTenantID": "", + "TargetLogAppID": "", + "TargetLogCertificateThumbprint": "", + "TargetLogAppSecretReference": "", + "LogTypeName": "ESIExchangeConfig" + } +} +``` + +Key rules: + +- `SentinelLogIngestionAPIActivated` **must** be `"true"` (string). +- `DCRImmutableId` must correspond to the DCR routing to the target table (choose `OnPremisesConfigImmutableId`, `OnlineConfigImmutableId`, or `MessageTrackingImmutableId` from the deployment outputs). +- `LogTypeName` maps to the stream name (`Custom-` is sent to the DCR). Use `ESIExchangeConfig`, `ESIExchangeOnlineConfig`, or `ExchangeOnlineMessageTracking`. +- For Azure Automation with certificate auth, import the certificate into the Automation Account certificate store and populate `TargetLogAppSecretReference` with the automation variable name that stores the certificate reference. +- `WorkspaceId` / `WorkspaceKey` are no longer used; leave them empty or remove them for clarity. + +### 3.1 Push the configuration + +- **Azure Automation**: open the Automation Account, navigate to **Variables**, edit `GlobalConfiguration`, and paste the updated JSON. Publish any linked runbook if required. +- **Server-based collector**: replace the local `CollectExchSecConfiguration.json` used by the scheduled task or manual invocation. + +### 3.2 Validate the cutover + +1. Trigger a manual run of the collector. +2. Confirm the runtime log no longer displays the **legacy API warning banner** (see below). +3. Query the new tables in Log Analytics: + + ```kql + ESIAPIExchangeOnlineConfig_CL + | where TimeGenerated > ago(1h) + | summarize count() by Section_s + ``` + +4. Optional: query the legacy tables to confirm ingestion has stopped, then plan their decommissioning. + +## Runtime warning banner (legacy API still in use) + +Starting with v8.0.0.0, the collector displays a highly visible warning at the start of every execution when it detects the legacy Log Analytics API is still in use. Sample output: + +```text +================================================================================ +!! WARNING: Legacy Log Analytics HTTP Data Collector API is still in use. +!! The API is deprecated by Microsoft and will be retired. +!! Please migrate to the Azure Monitor Log Ingestion API before end of support. +!! Migration guide: ESI-PublicContent/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md +================================================================================ +``` + +The banner clears after Step 3 is applied and `SentinelLogIngestionAPIActivated` is `true` in the effective configuration. + +## Post-migration housekeeping + +- Remove the workspace shared key from any password vault / secure store used for the legacy configuration. +- Update any monitoring / alerts to point to the new `_CL` tables. +- Update Sentinel analytic rules, hunting queries, and workbooks that reference the legacy tables. New sub-columns such as `Identity_DistinguishedName_s` or `Identity_ObjectGuid_g` unlock queries that were not possible with the legacy schema. +- After a full retention cycle, plan the decommissioning of the legacy tables through the Log Analytics workspace UI. + +## Rollback + +If a critical issue is discovered, the transition is reversible: + +1. Set `SentinelLogIngestionAPIActivated` back to `"false"` in `CollectExchSecConfiguration.json` (or via the WinformConfig editor). +2. Reinstate the legacy `WorkspaceId` / `WorkspaceKey` values. +3. Trigger the collector. + +The collector will resume publishing to the legacy tables. The DCE, DCRs, and new tables remain in place for a later retry. + +## Troubleshooting + +| Symptom | Likely cause | Action | +|------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|----------------------------------------------------------------------------------------------| +| The runtime banner still appears after step 3 | Old configuration cached, or the runbook still uses the previous JSON | Re-check `SentinelLogIngestionAPIActivated`, republish the runbook. | +| HTTP 403 from the Log Ingestion API | Missing role assignment on the DCR | Assign **Monitoring Metrics Publisher** on the correct DCR. | +| HTTP 400 `InvalidPayload` | Column type mismatch or stream name mismatch | Verify `DCRImmutableId` and `LogTypeName` match the target DCR / stream. | +| No data in the new tables | Wrong immutable ID (targets a different DCR) | Reconcile immutable IDs from the deployment outputs. | +| Payload rejected `PayloadTooLarge` | Segment > 1 MB | Lower `Advanced.MaximalSentinelPacketSizeMb` (default is `0.9` when the new API is enabled). | + +## Related documentation + +- ARM template and parameters: [README_LogIngestionAPI.md](./README_LogIngestionAPI.md) +- End-to-end Azure Monitor setup (Entra ID app, certificate, RBAC): [README_AzureMonitorSetup.md](./README_AzureMonitorSetup.md) +- WinformConfig editor: [ExchSecIns/WinformConfig/Readme.md](./WinformConfigReadme.md) diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/QUICKSTART-Forwarder.md b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/QUICKSTART-Forwarder.md new file mode 100644 index 00000000000..713775e585e --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/QUICKSTART-Forwarder.md @@ -0,0 +1,272 @@ +# 🚀 Quick Start Guide - Forwarder Pickup Processor + +## Quick Installation in 5 Minutes + +### 1️⃣ Prerequisites (2 minutes) + +```powershell +# Install Azure modules (if using Log Ingestion API) +Install-Module -Name Az.Accounts, Az.Monitor -Force -Scope CurrentUser + +# Verify installation +Get-Module -Name Az.Accounts -ListAvailable +``` + +### 2️⃣ Configuration (1 minute) + +```powershell +# Navigate to scripts folder +cd "C:\ESI\Scripts" + +# Copy configuration file +Copy-Item "Config\ForwarderPickupConfig.json" "Config\ForwarderPickupConfig-BACKUP.json" + +# Edit configuration +notepad "Config\ForwarderPickupConfig.json" +``` + +**Minimum configuration to modify:** + +```json +{ + "SentinelConnection": { + "UseLogIngestionAPI": true, + "DataCollectionEndpointURI": "https://VOTRE-DCE.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-VOTRE-DCR-ID", + "TenantID": "VOTRE-TENANT-ID", + "ApplicationId": "VOTRE-APP-ID", + "CertificateThumbprint": "VOTRE-CERT-THUMBPRINT" + } +} +``` + +### 3️⃣ Configuration Test (30 seconds) + +```powershell +# Test configuration +.\Test-ForwarderSetup.ps1 + +# Test with test file creation +.\Test-ForwarderSetup.ps1 -CreateTestFile + +# Test Azure connection +.\Test-ForwarderSetup.ps1 -TestConnection +``` + +### 4️⃣ Scheduled Task Installation (1 minute) + +```powershell +# Simple installation (15 minute interval) +.\Install-ForwarderScheduledTask.ps1 + +# With SYSTEM account (for Managed Identity) +.\Install-ForwarderScheduledTask.ps1 -UseSystemAccount + +# With service account +.\Install-ForwarderScheduledTask.ps1 -ServiceAccount "DOMAIN\svc-esi" -IntervalMinutes 10 + +# Customized +.\Install-ForwarderScheduledTask.ps1 ` + -ScriptPath "C:\ESI\Scripts\ForwarderPickupProcessor.ps1" ` + -ConfigPath "C:\ESI\Scripts\Config\ForwarderPickupConfig.json" ` + -IntervalMinutes 5 +``` + +### 5️⃣ Manual Test (30 seconds) + +```powershell +# Execute manually once +.\ForwarderPickupProcessor.ps1 + +# Check logs +Get-Content "C:\ESI\Logs\ForwarderProcessor_*.log" | Select-Object -Last 50 +``` + +--- + +## ✅ Deployment Checklist + +- [ ] PowerShell modules installed +- [ ] JSON configuration edited with your values +- [ ] Configuration test successful (0 errors) +- [ ] Folders created (Pickup, Archive, Error, Logs) +- [ ] Certificate installed (if certificate authentication) +- [ ] Permissions configured on DCR/Workspace +- [ ] CollectExchSecIns.ps1 configured in forwarder mode +- [ ] Scheduled task created +- [ ] Manual test successful +- [ ] Data visible in Sentinel + +--- + +## 🔍 Quick Verification + +### Check that forwarder is working: + +```powershell +# Count pending files +(Get-ChildItem "C:\ESI\ForwarderPickup" -Filter "*.json").Count + +# Count archived files +(Get-ChildItem "C:\ESI\Archive" -Filter "*.json").Count + +# Last task execution +Get-ScheduledTask -TaskName "ESI Forwarder Processor" | Get-ScheduledTaskInfo +``` + +### Check in Sentinel: + +```kql +// View data from last 24 hours +ESIExchangeConfig_CL +| where TimeGenerated > ago(24h) +| summarize Count=count() by bin(TimeGenerated, 1h) +| render timechart +``` + +--- + +## 🆘 Quick Troubleshooting + +### Problem: Files remain in Pickup + +```powershell +# Check logs +Get-Content "C:\ESI\Logs\ForwarderProcessor_*.log" | Select-String "Error|Failed" + +# Check error files +Get-ChildItem "C:\ESI\Error" + +# Execute manually with verbose +.\ForwarderPickupProcessor.ps1 -Verbose +``` + +### Problem: Authentication error + +```powershell +# Check certificate +Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq "YOUR-THUMBPRINT"} + +# Test Azure connection +Connect-AzAccount -CertificateThumbprint "THUMBPRINT" -Tenant "TENANT" -ApplicationId "APPID" +``` + +### Problem: Data doesn't appear in Sentinel + +```powershell +# Wait 10-15 minutes for first ingestion +# Check DCR +Get-AzDataCollectionRule -Name "YourDCR" + +# Check DCR streams +$dcr = Get-AzDataCollectionRule -Name "YourDCR" +$dcr.DataFlows +``` + +--- + +## 📱 Useful Commands + +```powershell +# Start task manually +Start-ScheduledTask -TaskName "ESI Forwarder Processor" + +# View task status +Get-ScheduledTask -TaskName "ESI Forwarder Processor" | Format-List * + +# Stop task +Stop-ScheduledTask -TaskName "ESI Forwarder Processor" + +# Disable temporarily +Disable-ScheduledTask -TaskName "ESI Forwarder Processor" + +# Re-enable +Enable-ScheduledTask -TaskName "ESI Forwarder Processor" + +# Remove task +Unregister-ScheduledTask -TaskName "ESI Forwarder Processor" -Confirm:$false +``` + +--- + +## 🎯 Recommended Production Configuration + +```json +{ + "PickupFolder": "C:\\ESI\\ForwarderPickup", + "ArchiveFolder": "D:\\ESI\\Archive", + "ErrorFolder": "D:\\ESI\\Error", + "LogFolder": "D:\\ESI\\Logs", + "DeleteAfterProcessing": false, + "MaxFilesPerRun": 100, + "FilePattern": "*.json", + "SentinelConnection": { + "UseManagedIdentity": true, + "UseLogIngestionAPI": true, + "DataCollectionEndpointURI": "https://prod-dce.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-xxxxx", + "MaxSegmentSizeMb": 0.9 + } +} +``` + +**Scheduled task:** Every 10-15 minutes +**Archive retention:** 30 days minimum +**Monitoring:** Alerts if ErrorFolder > 10 files + +--- + +## 📞 Support + +- **Logs** : `C:\ESI\Logs\ForwarderProcessor_*.log` +- **Erreurs** : `C:\ESI\Error\` +- **Documentation** : `README-ForwarderPickup.md` +- **Contact** : nilepagn@microsoft.com + +--- + +## 💡 Tips + +### Automatically purge old archives: + +```powershell +# Add to scheduled task or create separate task +Get-ChildItem "C:\ESI\Archive" -Filter "*.json" | + Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)} | + Remove-Item -Force +``` + +### Monitoring with PowerShell: + +```powershell +# Monitoring script to run every hour +$pickupCount = (Get-ChildItem "C:\ESI\ForwarderPickup" -Filter "*.json").Count +$errorCount = (Get-ChildItem "C:\ESI\Error" -Filter "*.json").Count + +if ($pickupCount -gt 50) { + Write-Warning "Too many pending files: $pickupCount" +} + +if ($errorCount -gt 10) { + Write-Error "Too many errors: $errorCount files" +} +``` + +### Quick statistics: + +```powershell +# Number of files processed today +$today = Get-Date -Format "yyyy-MM-dd" +$processed = (Get-ChildItem "C:\ESI\Archive" | + Where-Object {$_.LastWriteTime.ToString("yyyy-MM-dd") -eq $today}).Count +Write-Host "Files processed today: $processed" + +# Total size +$totalSize = (Get-ChildItem "C:\ESI\Archive" -Recurse | + Measure-Object -Property Length -Sum).Sum / 1MB +Write-Host "Data processed: $([Math]::Round($totalSize, 2)) MB" +``` + +--- + +**🎉 You're ready! The forwarder is now operational.** diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README-ForwarderPickup.md b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README-ForwarderPickup.md new file mode 100644 index 00000000000..9a61e7232c3 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README-ForwarderPickup.md @@ -0,0 +1,437 @@ +# Forwarder Pickup Processor + +## 📋 Overview + +The **Forwarder Pickup Processor** is a standalone PowerShell script that processes JSON files generated by `CollectExchSecIns.ps1` (forwarder mode) and injects them into Microsoft Sentinel. + +### Main Features + +- ✅ **Support for both APIs**: Log Analytics API (classic) and Log Ingestion API (DCR-based) +- ✅ **Batch processing**: Processes multiple files in a single execution +- ✅ **Automatic segmentation**: Splits large files into segments compliant with API limits +- ✅ **Robust error handling**: Archives successful files, isolates error files +- ✅ **Complete logging**: Detailed trace of all operations +- ✅ **Flexible authentication**: Managed Identity, Service Principal, or Interactive +- ✅ **Proxy support**: Proxy configuration for secure environments +- ✅ **Intelligent table name extraction**: Deduces log type from filename + +--- + +## 🚀 Installation and Configuration + +### 1. Prerequisites + +#### Required PowerShell modules: +```powershell +# Azure Az module (for Log Ingestion API) +Install-Module -Name Az.Accounts -Force -Scope CurrentUser +Install-Module -Name Az.Monitor -Force -Scope CurrentUser + +# Verify installation +Get-Module -Name Az.Accounts -ListAvailable +``` + +#### Required Azure permissions: + +**For Log Ingestion API (DCR-based):** +- Role: **Monitoring Metrics Publisher** on the Data Collection Rule (DCR) +- Service Principal or Managed Identity configured + +**For Log Analytics API (classic):** +- Workspace ID and Primary/Secondary Key from Log Analytics Workspace + +### 2. JSON File Configuration + +Edit `Config\ForwarderPickupConfig.json`: + +```json +{ + "PickupFolder": "C:\\ESI\\ForwarderPickup", + "ArchiveFolder": "C:\\ESI\\Archive", + "ErrorFolder": "C:\\ESI\\Error", + "LogFolder": "C:\\ESI\\Logs", + "DeleteAfterProcessing": false, + "MaxFilesPerRun": 0, + "FilePattern": "*.json", + "SentinelConnection": { + "UseManagedIdentity": false, + "UseLogIngestionAPI": true, + "DataCollectionEndpointURI": "https://your-dce.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "TenantID": "your-tenant-id", + "ApplicationId": "your-app-id", + "CertificateThumbprint": "THUMBPRINT", + "DefaultLogType": "ESIExchangeConfig", + "MaxSegmentSizeMb": 0.9 + } +} +``` + +### 3. CollectExchSecIns.ps1 Configuration + +In your `CollectExchSecConfiguration.json` configuration, enable forwarder mode: + +```json +{ + "SentinelLogCollector": { + "ActivateLogUpdloadToSentinel": true, + "UseForwarder": true, + "ForwarderPickupPath": "C:\\ESI\\ForwarderPickup", + "TogetherMode": false + } +} +``` + +--- + +## 📖 Usage + +### Manual Execution + +#### With default configuration: +```powershell +.\ForwarderPickupProcessor.ps1 +``` + +#### With command-line parameters: +```powershell +.\ForwarderPickupProcessor.ps1 ` + -PickupFolder "C:\ESI\ForwarderPickup" ` + -ArchiveFolder "C:\ESI\Archive" ` + -MaxFilesPerRun 50 +``` + +#### Delete files after processing: +```powershell +.\ForwarderPickupProcessor.ps1 -DeleteAfterProcessing +``` + +#### With proxy: +```powershell +.\ForwarderPickupProcessor.ps1 ` + -UseProxy ` + -ProxyUrl "http://proxy.contoso.com:8080" +``` + +### Scheduling + +#### With Windows Task Scheduler: + +1. **Create task**: +```powershell +$action = New-ScheduledTaskAction ` + -Execute "PowerShell.exe" ` + -Argument "-NoProfile -ExecutionPolicy Bypass -File `"C:\ESI\ForwarderPickupProcessor.ps1`"" + +$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 15) -RepetitionDuration ([TimeSpan]::MaxValue) + +$principal = New-ScheduledTaskPrincipal -UserId "DOMAIN\ServiceAccount" -LogonType Password + +$settings = New-ScheduledTaskSettingsSet ` + -AllowStartIfOnBatteries ` + -DontStopIfGoingOnBatteries ` + -StartWhenAvailable ` + -RunOnlyIfNetworkAvailable + +Register-ScheduledTask ` + -TaskName "ESI Forwarder Processor" ` + -Action $action ` + -Trigger $trigger ` + -Principal $principal ` + -Settings $settings ` + -Description "Processes pickup files and sends to Sentinel every 15 minutes" +``` + +2. **With Managed Identity (Azure VM)**: +```powershell +# Task runs under SYSTEM account which has access to Managed Identity +$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount +``` + +#### With Azure Automation (Runbook): + +```powershell +# Import script to Azure Automation +$AutomationAccountName = "YourAutomationAccount" +$ResourceGroupName = "YourResourceGroup" +$RunbookName = "ForwarderPickupProcessor" + +Import-AzAutomationRunbook ` + -Name $RunbookName ` + -Path ".\ForwarderPickupProcessor.ps1" ` + -ResourceGroupName $ResourceGroupName ` + -AutomationAccountName $AutomationAccountName ` + -Type PowerShell + +# Publish runbook +Publish-AzAutomationRunbook ` + -Name $RunbookName ` + -ResourceGroupName $ResourceGroupName ` + -AutomationAccountName $AutomationAccountName + +# Create schedule +$Schedule = New-AzAutomationSchedule ` + -Name "Every15Minutes" ` + -StartTime (Get-Date).AddMinutes(5) ` + -DayInterval 1 ` + -ResourceGroupName $ResourceGroupName ` + -AutomationAccountName $AutomationAccountName ` + -TimeZone "Romance Standard Time" + +# Associate with runbook +Register-AzAutomationScheduledRunbook ` + -Name $RunbookName ` + -ScheduleName "Every15Minutes" ` + -ResourceGroupName $ResourceGroupName ` + -AutomationAccountName $AutomationAccountName +``` + +--- + +## 🔧 Script Parameters + +| Parameter | Type | Description | Default | +|-----------|------|-------------|---------| +|-----------|------|-------------|--------| +| `ConfigurationFile` | string | Path to JSON configuration file | `.\Config\ForwarderPickupConfig.json` | +| `PickupFolder` | string | Folder containing files to process | Value from config file | +| `ArchiveFolder` | string | Archive folder for successful files | Value from config file | +| `ErrorFolder` | string | Folder for error files | Value from config file | +| `MaxFilesPerRun` | int | Max files per execution (0=unlimited) | 0 | +| `DeleteAfterProcessing` | switch | Delete instead of archive | false | +| `UseProxy` | switch | Enable proxy usage | false | +| `ProxyUrl` | string | Proxy URL | "" | + +--- + +## 📁 Folder Structure + +``` +C:\ESI\ +├── ForwarderPickup\ # JSON files deposited by CollectExchSecIns.ps1 +│ ├── ESIExchangeConfig-2026-03-24-10-30-00.json +│ └── ESIExchangeConfig-Page_1-2026-03-24-10-30-00.json +├── Archive\ # Successfully processed files +│ └── ESIExchangeConfig-2026-03-24-10-30-00.json +├── Error\ # Error files +│ └── ESIExchangeConfig-corrupted.json +└── Logs\ # Forwarder execution logs + └── ForwarderProcessor_20260324_103000.log +``` + +--- + +## 🏷️ Table Name Extraction + +The script intelligently extracts the Sentinel table name from the filename: + +| Filename | Sentinel Table | +|----------------|----------------| +|----------------|----------------| +| `ESIExchangeConfig-2026-03-24.json` | `ESIExchangeConfig_CL` | +| `ESIExchangeConfig-Page_1-2026-03-24.json` | `ESIExchangeConfig_CL` | +| `MyCustomTable-abc123.json` | `MyCustomTable_CL` | +| `data.json` | `ESIExchangeConfig_CL` (default) | + +> **Note**: The `_CL` suffix (Custom Log) is automatically added by Azure + +--- + +## 🔐 Authentication Configuration + +### 1. Managed Identity (recommended for Azure VM/Automation) + +```json +{ + "SentinelConnection": { + "UseManagedIdentity": true, + "UseLogIngestionAPI": true, + "DataCollectionEndpointURI": "https://xxx.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-xxx" + } +} +``` + +**Role assignment**: +```powershell +# Get Managed Identity Object ID +$vmName = "YourVMName" +$rgName = "YourResourceGroup" +$vm = Get-AzVM -Name $vmName -ResourceGroupName $rgName +$principalId = $vm.Identity.PrincipalId + +# Assign role on DCR +$dcrResourceId = "/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.Insights/dataCollectionRules/{dcr-name}" +New-AzRoleAssignment ` + -ObjectId $principalId ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope $dcrResourceId +``` + +### 2. Service Principal with certificate + +```json +{ + "SentinelConnection": { + "UseManagedIdentity": false, + "UseLogIngestionAPI": true, + "TenantID": "your-tenant-id", + "ApplicationId": "your-app-id", + "CertificateThumbprint": "ABC123...", + "DataCollectionEndpointURI": "https://xxx.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-xxx" + } +} +``` + +**Certificate installation**: +```powershell +# Import certificate to local store +$certPath = "C:\Certs\SentinelApp.pfx" +$certPassword = ConvertTo-SecureString "YourPassword" -AsPlainText -Force +Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\LocalMachine\My -Password $certPassword +``` + +### 3. Log Analytics API classic + +```json +{ + "SentinelConnection": { + "UseLogIngestionAPI": false, + "WorkspaceId": "12345678-1234-1234-1234-123456789012", + "WorkspaceKey": "YourPrimaryOrSecondaryKey==", + "MaxSegmentSizeMb": 31.9 + } +} +``` + +--- + +## 📊 Monitoring and Diagnostics + +### Execution Logs + +Logs are stored in the configured folder (`LogFolder`): + +``` +[2026-03-24 10:30:00] [Info] === Forwarder Pickup Processor v1.0.0 === +[2026-03-24 10:30:00] [Info] Started at: 2026-03-24 10:30:00 +[2026-03-24 10:30:01] [Info] Found 5 file(s) to process +[2026-03-24 10:30:05] [Success] Data successfully uploaded to Log Ingestion API +[2026-03-24 10:30:05] [Success] Successfully processed file: ESIExchangeConfig.json +[2026-03-24 10:30:10] [Success] === Processing Summary === +[2026-03-24 10:30:10] [Success] Total files processed: 5 +[2026-03-24 10:30:10] [Success] Failed files: 0 +[2026-03-24 10:30:10] [Success] Total data processed: 12.5 MB +``` + +### Verification in Sentinel + +```kql +// Check ingested data +ESIExchangeConfig_CL +| where TimeGenerated > ago(1h) +| summarize count() by bin(TimeGenerated, 5m) +| render timechart + +// Check data by environment +ESIExchangeConfig_CL +| where TimeGenerated > ago(24h) +| summarize RecordCount=count(), LastSeen=max(TimeGenerated) by ESIEnvironment +``` + +--- + +## ⚠️ Troubleshooting + +### Error: "Failed to connect to Azure" + +**Cause**: Authentication problem + +**Solution**: +- Check that certificate is installed and accessible +- Check Managed Identity permissions +- Test manually: `Connect-AzAccount` + +### Error: "Upload payload is too big" + +**Cause**: File too large for a segment + +**Solution**: +- Script automatically segments +- Check `MaxSegmentSizeMb` in config (0.9 for DCR, 31.9 for Log Analytics) + +### Files remain in Pickup folder + +**Cause**: Unhandled errors or script not scheduled + +**Solution**: +- Check logs in `LogFolder` +- Check files in `ErrorFolder` +- Execute manually to diagnose + +### Data doesn't appear in Sentinel + +**Cause**: Table doesn't exist or DCR misconfigured + +**Solution**: +- For DCR: Check that stream is configured in DCR +- For Log Analytics: Table creates automatically (wait 10-15 min) +- Check permissions on DCR/Workspace + +--- + +## 🔄 Complete Workflow + +```mermaid +graph TD + A[CollectExchSecIns.ps1] -->|Forwarder Mode| B[JSON Files in Pickup] + B --> C[ForwarderPickupProcessor.ps1] + C --> D{Read file} + D --> E{Extract LogType} + E --> F{Size > Limit?} + F -->|No| G[Send single segment] + F -->|Yes| H[Split into segments] + H --> I[Send each segment] + G --> J{Success?} + I --> J + J -->|Yes| K[Archive file] + J -->|No| L[Move to Error] + K --> M[Next file] + L --> M + M --> N{More files?} + N -->|Yes| D + N -->|No| O[Display summary] +``` + +--- + +## 📝 Best Practices + +1. **Scheduling**: Execute every 5-15 minutes for regular flow +2. **Cleanup**: Archive and purge old files periodically +3. **Monitoring**: Monitor logs and Error folder +4. **Testing**: Test first with `MaxFilesPerRun=1` before production +5. **Security**: Use Managed Identity when possible +6. **Performance**: Adjust `MaxSegmentSizeMb` according to API used + +--- + +## 📞 Support + +For questions or issues: +- Check logs in `LogFolder` folder +- Consult Microsoft Sentinel documentation +- Contact ESI team: nilepagn@microsoft.com + +--- + +## 📜 Version History + +### v1.0.0 (2026-03-24) +- ✨ Initial version +- ✅ Log Analytics API and Log Ingestion API support +- ✅ Automatic segment management +- ✅ Archiving and error handling +- ✅ Complete logging +- ✅ Managed Identity and Service Principal support diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README.md b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README.md index d968fd364a0..20f588e5025 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README.md +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README.md @@ -3,19 +3,40 @@ This folder contains documentations related to the deployment of solutions Microsoft Exchange Security for Exchange On-Premises and Microsoft Exchange Security for Exchange Online : ## Overview of Deployement -[Deployment Overview](./Deployment-Overview.md) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/Deployment-Overview.md ## Deployment Microsoft Exchange Security for Exchange On-Premises -[Deployment for On-Premises Solution](./Deployment-MES-OnPremises.md) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/Deployment-MES-OnPremises.md ## Deployment Microsoft Exchange Security for Exchange Online -[Deployment for Online Solution](./Deployment-MES-Online.md) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/Deployment-MES-Online.md ## Collectors Information -[Collector Information](./ESICollector.md) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/ESICollector.md + +## Deploy the Azure Monitor Log Ingestion API (DCE / DCR / custom tables) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/README_LogIngestionAPI.md + +## Azure Monitor End-to-End Setup (Entra ID application, certificate, RBAC) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/README_AzureMonitorSetup.md + +## Migrate from the Log Analytics HTTP Data Collector API to the Log Ingestion API +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md + +## WinformConfig editor for CollectExchSecConfiguration.json +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/WinformConfigReadme.md + +## Forwarder / Pickup deployment +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/README-ForwarderPickup.md + +## Forwarder quick start +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/QUICKSTART-Forwarder.md ## How to deploy Woorkbooks -[Solution Workbooks information](./WorkbookDeployement.md) +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/WorkbookDeployement.md + +## Workbook delegation +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/WorkbookDelegation.md ## VIP Management -[VIP Management](./VIPManagement.md) \ No newline at end of file +https://github.com/nlepagnez/ESI-PublicContent/blob/main/Documentations/VIPManagement.md \ No newline at end of file diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README_AzureMonitorSetup.md b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README_AzureMonitorSetup.md new file mode 100644 index 00000000000..4765da77ad0 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README_AzureMonitorSetup.md @@ -0,0 +1,544 @@ +# Azure Monitor Log Ingestion API Setup for Exchange Security Insights + +This guide covers the full setup of Azure Monitor components required by the ESI Collector to ingest Exchange security data into Microsoft Sentinel using the Log Ingestion API. + +> [!IMPORTANT] +> The Log Ingestion API replaces the legacy Log Analytics HTTP Data Collector API (deprecated in 2025). All new deployments should use the Azure Monitor Ingestion API. + +## Architecture Overview + +The ingestion pipeline consists of these Azure components: + +```text +ESI Collector Script + │ + ▼ + Entra ID App Registration ──(certificate auth)──► Azure AD Token + │ + ▼ + Data Collection Endpoint (DCE) + │ + ▼ + Data Collection Rule (DCR) ──(transform + route)──► Log Analytics Custom Tables + │ + ▼ + Microsoft Sentinel Workspace +``` + +| Component | Purpose | +|-----------|---------| +| Entra ID Application | Authenticates the collector to the ingestion endpoint | +| Self-signed Certificate | Credential for the application (preferred over secrets) | +| Data Collection Endpoint (DCE) | HTTPS endpoint receiving log payloads | +| Data Collection Rule (DCR) | Defines schema, transforms, and routing to tables | +| Custom Log Analytics Tables | Store the collected Exchange configuration data | + +## Prerequisites + +Before you begin, verify these requirements: + +- An Azure subscription with Contributor access on the target resource group +- A Log Analytics workspace (where Microsoft Sentinel is enabled) +- PowerShell 7.0+ with the following modules installed: + - `Az.Accounts` + - `Az.Monitor` + - `Az.Resources` + - `Microsoft.Graph` (if creating the Entra ID Application via script) +- Azure CLI (alternative for deployment and permission management) +- OpenSSL or PowerShell `New-SelfSignedCertificate` for certificate generation + +## Step 1: Create a Self-Signed Certificate + +The ESI Collector authenticates to Azure using a certificate-based credential. You can use an existing PKI certificate or generate a self-signed one. + +### Option A: PowerShell (Windows) + +```powershell +# Create a self-signed certificate valid for 2 years +$cert = New-SelfSignedCertificate ` + -Subject "CN=ESI-Collector-Auth" ` + -CertStoreLocation "Cert:\CurrentUser\My" ` + -KeyExportPolicy Exportable ` + -KeySpec Signature ` + -KeyLength 2048 ` + -HashAlgorithm SHA256 ` + -NotAfter (Get-Date).AddYears(2) + +# Display the thumbprint (you need it for configuration) +Write-Host "Certificate Thumbprint: $($cert.Thumbprint)" + +# Export the public key (.cer) for uploading to Entra ID Application +Export-Certificate -Cert $cert -FilePath ".\ESI-Collector-Auth.cer" -Type CERT + +# (Optional) Export the PFX for Azure Automation import +$pwd = ConvertTo-SecureString -String "YourStrongPassword" -Force -AsPlainText +Export-PfxCertificate -Cert $cert -FilePath ".\ESI-Collector-Auth.pfx" -Password $pwd +``` + +### Option B: OpenSSL (Linux / macOS) + +```bash +# Generate private key and self-signed certificate (2 years) +openssl req -x509 -newkey rsa:2048 \ + -keyout esi-collector-key.pem \ + -out esi-collector-cert.pem \ + -sha256 -days 730 \ + -subj "/CN=ESI-Collector-Auth" \ + -nodes + +# Convert to PFX for Azure Automation +openssl pkcs12 -export \ + -out esi-collector.pfx \ + -inkey esi-collector-key.pem \ + -in esi-collector-cert.pem + +# Get the thumbprint +openssl x509 -in esi-collector-cert.pem -noout -fingerprint -sha1 \ + | sed 's/://g' | cut -d= -f2 +``` + +> [!NOTE] +> Record the certificate thumbprint. You need it when configuring the collector and when running from a server. For Azure Automation, import the PFX into the Automation Account certificate store. + +## Step 2: Register an Entra ID Application + +The application identity enables the collector to obtain tokens for the Azure Monitor ingestion endpoint. + +### Option A: Azure Portal + +1. Navigate to **Microsoft Entra ID** > **App registrations** > **New registration** +2. Set the display name to `ESI-Collector-LogIngestion` (or your preferred name) +3. Leave the redirect URI empty (no interactive sign-in needed) +4. Click **Register** +5. Note the **Application (client) ID** and **Directory (tenant) ID** from the Overview page +6. Navigate to **Certificates & secrets** > **Certificates** > **Upload certificate** +7. Upload the `.cer` file generated in Step 1 +8. Create a **Service Principal** (automatic when registering via portal) + +### Option B: PowerShell with Microsoft Graph + +```powershell +# Connect to Microsoft Graph +Connect-MgGraph -Scopes "Application.ReadWrite.All" + +# Create the application +$app = New-MgApplication -DisplayName "ESI-Collector-LogIngestion" + +# Create the service principal +$sp = New-MgServicePrincipal -AppId $app.AppId + +# Upload the certificate +$certContent = [System.IO.File]::ReadAllBytes(".\ESI-Collector-Auth.cer") +$base64Cert = [System.Convert]::ToBase64String($certContent) + +$keyCredential = @{ + Type = "AsymmetricX509Cert" + Usage = "Verify" + Key = [System.Convert]::FromBase64String($base64Cert) + DisplayName = "ESI-Collector-Auth" +} + +Update-MgApplication -ApplicationId $app.Id -KeyCredentials @($keyCredential) + +# Display information needed for configuration +Write-Host "Application (Client) ID: $($app.AppId)" +Write-Host "Object ID: $($app.Id)" +Write-Host "Service Principal Object ID: $($sp.Id)" +``` + +### Option C: Azure CLI + +```bash +# Create the application with the certificate +az ad app create --display-name "ESI-Collector-LogIngestion" + +# Get the App ID +APP_ID=$(az ad app list --display-name "ESI-Collector-LogIngestion" --query "[0].appId" -o tsv) + +# Create a service principal +az ad sp create --id $APP_ID + +# Upload the certificate +az ad app credential reset --id $APP_ID --cert @esi-collector-cert.pem --append + +echo "Application (Client) ID: $APP_ID" +``` + +> [!TIP] +> No API permissions are required on the Entra ID Application itself. Access control is handled through Azure RBAC on the Data Collection Rule. + +## Step 3: Deploy the Data Collection Endpoint, Rules, and Tables + +The ARM template in the `Deployments` folder creates all required Azure Monitor resources: + +- 1 Data Collection Endpoint (DCE) +- Up to 3 Custom Log Analytics tables (each controlled by a master switch): + - `ESIAPIExchangeOnPremConfig_CL` (Exchange On-Premises configuration) + - `ESIAPIExchangeOnlineConfig_CL` (Exchange Online configuration) + - `ExchangeOnlineMessageTracking_CL` (message tracking) +- Up to 3 Data Collection Rules (one per table): + - `DCR-ESI-OnPremisesConfig` + - `DCR-ESI-OnlineConfig` + - `DCR-ESI-MessageTracking` + +> [!TIP] +> Master parameters `deployTables`, `deployDataCollection`, `deployOnPremConfigTable`, `deployOnlineConfigTable`, and `deployMessageTrackingTable` let you deploy any subset. See [README_LogIngestionAPI.md](README_LogIngestionAPI.md) for the full parameter reference. + +### Option A: Using Azure CLI + +```bash +# Login and set subscription +az login +az account set --subscription "YOUR_SUBSCRIPTION_ID" + +# Create a resource group (if needed) +az group create --name "rg-sentinel-esi" --location "eastus" + +# Deploy the template +az deployment group create \ + --resource-group "rg-sentinel-esi" \ + --template-file azuredeploy_ESI_LogIngestionAPI.json \ + --parameters azuredeploy_ESI_LogIngestionAPI.parameters.json +``` + +### Option B: Using PowerShell + +```powershell +# Login and set subscription +Connect-AzAccount +Set-AzContext -SubscriptionId "YOUR_SUBSCRIPTION_ID" + +# Create a resource group (if needed) +New-AzResourceGroup -Name "rg-sentinel-esi" -Location "eastus" + +# Deploy the template +New-AzResourceGroupDeployment ` + -ResourceGroupName "rg-sentinel-esi" ` + -TemplateFile "azuredeploy_ESI_LogIngestionAPI.json" ` + -TemplateParameterFile "azuredeploy_ESI_LogIngestionAPI.parameters.json" +``` + +### Option C: Using Azure Portal + +1. Navigate to **Deploy a custom template** in the Azure Portal +2. Click **Build your own template in the editor** +3. Paste the content of `azuredeploy_ESI_LogIngestionAPI.json` +4. Fill in the parameters: + + | Parameter | Description | Example | + |-----------|-------------|---------| + | `workspaceName` | Name of your existing Log Analytics workspace | `law-sentinel-prod` | + | `location` | Azure region matching your workspace | `eastus` | + | `dataCollectionEndpointName` | Name for the DCE | `DCE-ESI-LogIngestion` | + | `dataCollectionRuleOnPremisesConfigName` | Name for the On-Premises Config DCR | `DCR-ESI-OnPremisesConfig` | + | `dataCollectionRuleOnlineConfigName` | Name for the Online Config DCR | `DCR-ESI-OnlineConfig` | + | `dataCollectionRuleMessageTrackingName` | Name for the Message Tracking DCR | `DCR-ESI-MessageTracking` | + | `retentionInDays` | Data retention period (30-730) | `90` | + | `deployTables` | Deploy the custom Log Analytics tables | `true` | + | `deployDataCollection` | Deploy the DCE and DCRs | `true` | + | `deployOnPremConfigTable` | Deploy the On-Premises config resources | `true` | + | `deployOnlineConfigTable` | Deploy the Exchange Online config resources | `true` | + | `deployMessageTrackingTable` | Deploy message tracking resources | `true` | + +5. Click **Review + create** + +### Collect Deployment Outputs + +After deployment, retrieve these values from the outputs: + +```powershell +$deployment = Get-AzResourceGroupDeployment ` + -ResourceGroupName "rg-sentinel-esi" ` + -Name "YOUR_DEPLOYMENT_NAME" + +# Values needed for configuration +$deployment.Outputs.dataCollectionEndpointUri.Value # DCE URI +$deployment.Outputs.dataCollectionRuleOnPremisesConfigImmutableId.Value # DCR Immutable ID (On-Premises Config) +$deployment.Outputs.dataCollectionRuleOnlineConfigImmutableId.Value # DCR Immutable ID (Online Config) +$deployment.Outputs.dataCollectionRuleMessageTrackingImmutableId.Value # DCR Immutable ID (Message Tracking) +``` + +Or via Azure CLI: + +```bash +az deployment group show \ + --resource-group "rg-sentinel-esi" \ + --name "YOUR_DEPLOYMENT_NAME" \ + --query properties.outputs +``` + +> [!IMPORTANT] +> Record the **DCE URI** and **DCR Immutable IDs** from the deployment outputs. You need these values for both the permission assignment and the collector configuration. + +## Step 4: Assign Permissions on the Data Collection Rule + +The Entra ID Application (service principal) requires the **Monitoring Metrics Publisher** role on each Data Collection Rule it sends data to. + +### Option A: Azure CLI + +```bash +# Get the service principal Object ID +SP_OBJECT_ID=$(az ad sp list --filter "appId eq 'YOUR_APP_ID'" --query "[0].id" -o tsv) + +# Assign on On-Premises Config DCR (if deployed) +az role assignment create \ + --role "Monitoring Metrics Publisher" \ + --assignee-object-id "$SP_OBJECT_ID" \ + --assignee-principal-type ServicePrincipal \ + --scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnPremisesConfig" + +# Assign on Online Config DCR (if deployed) +az role assignment create \ + --role "Monitoring Metrics Publisher" \ + --assignee-object-id "$SP_OBJECT_ID" \ + --assignee-principal-type ServicePrincipal \ + --scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" + +# Assign on Message Tracking DCR (if deployed) +az role assignment create \ + --role "Monitoring Metrics Publisher" \ + --assignee-object-id "$SP_OBJECT_ID" \ + --assignee-principal-type ServicePrincipal \ + --scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-MessageTracking" +``` + +### Option B: PowerShell + +```powershell +# Get the service principal Object ID +$sp = Get-AzADServicePrincipal -ApplicationId "YOUR_APP_ID" + +# Assign on On-Premises Config DCR (if deployed) +New-AzRoleAssignment ` + -ObjectId $sp.Id ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnPremisesConfig" + +# Assign on Online Config DCR (if deployed) +New-AzRoleAssignment ` + -ObjectId $sp.Id ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" + +# Assign on Message Tracking DCR (if deployed) +New-AzRoleAssignment ` + -ObjectId $sp.Id ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-MessageTracking" +``` + +### Option C: Azure Portal + +1. Navigate to **Monitor** > **Data Collection Rules** +2. Select your DCR (e.g., `DCR-ESI-OnlineConfig`) +3. Go to **Access control (IAM)** > **Add role assignment** +4. Select role **Monitoring Metrics Publisher** +5. Under Members, select **User, group, or service principal** +6. Search for your application name (`ESI-Collector-LogIngestion`) +7. Click **Review + assign** +8. Repeat for the On-Premises Config DCR and the Message Tracking DCR if deployed + +> [!WARNING] +> Without the **Monitoring Metrics Publisher** role on the DCR, the collector receives `403 Forbidden` errors when ingesting data. Verify the assignment before testing. + +## Step 5: Configure the ESI Collector + +Update the `CollectExchSecConfiguration.json` file with the values collected during setup. + +### Log Collection Section + +```json +{ + "LogCollection": { + "ActivateLogUpdloadToSentinel": "true", + "SentinelLogIngestionAPIActivated": "true", + "DataCollectionEndpointURI": "https://YOUR-DCE-NAME.region.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-00000000000000000000000000000000", + "UseManagedIdentity": "false", + "TargetLogTenantID": "YOUR-TENANT-ID", + "TargetLogAppID": "YOUR-APPLICATION-CLIENT-ID", + "TargetLogCertificateThumbprint": "YOUR-CERTIFICATE-THUMBPRINT", + "LogTypeName": "ESIExchangeConfig", + "CSVOutputFile": "ExchSecIns.csv", + "ExportDomainsInformation": "True" + } +} +``` + +| Field | Value Source | +|-------|-------------| +| `DataCollectionEndpointURI` | Deployment output: `dataCollectionEndpointUri` | +| `DCRImmutableId` | Deployment output matching the target table: `dataCollectionRuleOnPremisesConfigImmutableId`, `dataCollectionRuleOnlineConfigImmutableId`, or `dataCollectionRuleMessageTrackingImmutableId` | +| `TargetLogTenantID` | Entra ID > Overview > Tenant ID | +| `TargetLogAppID` | Entra ID > App registrations > Application (client) ID | +| `TargetLogCertificateThumbprint` | Certificate thumbprint from Step 1 | + +> [!NOTE] +> `DCRImmutableId` must correspond to the DCR routing to the table you want to target (on-premises, online, or message tracking). Set the ESI collector `LogTypeName` accordingly (`ESIExchangeConfig`, `ESIExchangeOnlineConfig`, or `ExchangeOnlineMessageTracking`). + +### Azure Automation Execution (Managed Identity) + +When running from Azure Automation with a managed identity, set `UseManagedIdentity` to `"true"` and assign the **Monitoring Metrics Publisher** role to the Automation Account's system-assigned managed identity on each DCR. + +```json +{ + "LogCollection": { + "SentinelLogIngestionAPIActivated": "true", + "UseManagedIdentity": "true", + "DataCollectionEndpointURI": "https://YOUR-DCE-NAME.region.ingest.monitor.azure.com", + "DCRImmutableId": "dcr-00000000000000000000000000000000" + } +} +``` + +```powershell +# Assign Monitoring Metrics Publisher to the Automation Account's managed identity +$automationMI = (Get-AzAutomationAccount -ResourceGroupName "rg-automation" -Name "aa-esi-collector").Identity.PrincipalId + +# Repeat this assignment for every DCR you send to (OnPremises, Online, MessageTracking). +New-AzRoleAssignment ` + -ObjectId $automationMI ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" +``` + +## Step 6: Validate the Setup + +### Test Authentication + +```powershell +# Connect using the certificate +Connect-AzAccount ` + -CertificateThumbprint "YOUR_CERTIFICATE_THUMBPRINT" ` + -ApplicationId "YOUR_APP_ID" ` + -Tenant "YOUR_TENANT_ID" ` + -ServicePrincipal + +# Get a token for the Monitor endpoint +$context = Get-AzContext +$token = (Get-AzAccessToken -ResourceUrl "https://monitor.azure.com").Token + +Write-Host "Token acquired successfully" -ForegroundColor Green +``` + +### Send a Test Payload + +```powershell +$dceUri = "https://YOUR-DCE-NAME.region.ingest.monitor.azure.com" +$dcrImmutableId = "dcr-00000000000000000000000000000000" +$streamName = "Custom-ESIExchangeOnlineConfig" + +$uri = "$dceUri/dataCollectionRules/$dcrImmutableId/streams/$($streamName)?api-version=2023-01-01" + +$testData = @( + @{ + TimeGenerated = (Get-Date).ToUniversalTime().ToString("o") + EntryDate = (Get-Date).ToUniversalTime().ToString("o") + GenerationInstanceID = "test-validation" + ESIEnvironment = "TestEnvironment" + Section = "ValidationTest" + RawData = '{"test": true}' + } +) | ConvertTo-Json -AsArray + +$headers = @{ + "Authorization" = "Bearer $token" + "Content-Type" = "application/json" +} + +$response = Invoke-RestMethod -Uri $uri -Method Post -Body $testData -Headers $headers +Write-Host "Data sent successfully" -ForegroundColor Green +``` + +### Verify Data in Log Analytics + +Wait a few minutes, then query your workspace: + +```kql +ESIAPIExchangeOnlineConfig_CL +| where ESIEnvironment_s == "TestEnvironment" +| where Section_s == "ValidationTest" +| project TimeGenerated, ESIEnvironment_s, Section_s, GenerationInstanceID_g +``` + +## Stream Names Reference + +When the collector sends data via the Log Ingestion API, it targets these stream names: + +| Table | Stream Name | +|------------------------------------|------------------------------------------| +| `ESIAPIExchangeOnPremConfig_CL` | `Custom-ESIExchangeConfig` | +| `ESIAPIExchangeOnlineConfig_CL` | `Custom-ESIExchangeOnlineConfig` | +| `ExchangeOnlineMessageTracking_CL` | `Custom-ExchangeOnlineMessageTracking` | + +> [!NOTE] +> The on-premises and online configuration tables also expose extracted `Identity_*` sub-property columns (`Identity_Depth_d`, `Identity_DistinguishedName_s`, `Identity_ObjectGuid_g`, ...). These are populated by the DCR `transformKql` from the source `Identity` object using `parse_json`. See the full column list in [README_LogIngestionAPI.md](README_LogIngestionAPI.md#table-schemas). + +## Troubleshooting + +### Authentication Errors (401 / 403) + +1. Verify the certificate thumbprint matches the one uploaded to the Entra ID Application +2. Confirm the Tenant ID and Application ID in the configuration +3. Check that the **Monitoring Metrics Publisher** role is correctly assigned on the DCR (not the DCE or workspace) + +### DCE Not Reachable + +1. Validate the `DataCollectionEndpointURI` from the deployment outputs +2. Ensure firewall or NSG rules allow outbound HTTPS to `*.ingest.monitor.azure.com` +3. If using a proxy, configure `Useproxy` and `ProxyUrl` in the Advanced section of the configuration + +### Data Not Appearing in Tables + +1. Verify the `DCRImmutableId` corresponds to the correct DCR for the target table +2. Check that table schema columns match the stream declaration in the DCR +3. Review collector logs for payload size errors (1 MB limit per API call for Log Ingestion API) +4. Ensure `SentinelLogIngestionAPIActivated` is set to `"true"` in the configuration + +### Payload Size Errors + +The Log Ingestion API limits each request to 1 MB. The collector automatically segments larger payloads. If segmentation errors persist, reduce the `MaximalSentinelPacketSizeMb` value in the Advanced configuration section. + +## Complete Setup Checklist + +Use this checklist to track your progress: + +- [ ] Generate or obtain a certificate (Step 1) +- [ ] Record the certificate thumbprint +- [ ] Register an Entra ID Application (Step 2) +- [ ] Record the Application (Client) ID +- [ ] Record the Tenant ID +- [ ] Upload the certificate to the Application +- [ ] Deploy DCE, DCR, and tables via ARM template (Step 3) +- [ ] Record the DCE URI from deployment outputs +- [ ] Record the DCR Immutable ID(s) from deployment outputs +- [ ] Assign Monitoring Metrics Publisher role on each DCR (Step 4) +- [ ] Update `CollectExchSecConfiguration.json` with all values (Step 5) +- [ ] Validate authentication and test data ingestion (Step 6) +- [ ] Verify data appears in Log Analytics tables + +## Cleanup + +To remove all deployed resources: + +```powershell +# Remove role assignments first (repeat per DCR you assigned) +Remove-AzRoleAssignment ` + -ObjectId "SERVICE_PRINCIPAL_OBJECT_ID" ` + -RoleDefinitionName "Monitoring Metrics Publisher" ` + -Scope "/subscriptions/YOUR_SUB_ID/resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" + +# Delete DCRs +Remove-AzDataCollectionRule -Name "DCR-ESI-OnPremisesConfig" -ResourceGroupName "rg-sentinel-esi" +Remove-AzDataCollectionRule -Name "DCR-ESI-OnlineConfig" -ResourceGroupName "rg-sentinel-esi" +Remove-AzDataCollectionRule -Name "DCR-ESI-MessageTracking" -ResourceGroupName "rg-sentinel-esi" + +# Delete DCE +Remove-AzDataCollectionEndpoint -Name "DCE-ESI-LogIngestion" -ResourceGroupName "rg-sentinel-esi" + +# Delete the Entra ID Application (optional) +Remove-MgApplication -ApplicationId "APP_OBJECT_ID" +``` + +> [!CAUTION] +> Custom Log Analytics tables cannot be deleted via API. They can only be removed through the Log Analytics workspace in the Azure Portal. Removing the workspace deletes all tables and data. diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README_LogIngestionAPI.md b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README_LogIngestionAPI.md new file mode 100644 index 00000000000..74b46b54922 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/README_LogIngestionAPI.md @@ -0,0 +1,323 @@ +# Exchange Security Insights - Log Ingestion API Deployment + +This ARM template deploys the Azure infrastructure required for Exchange Security Insights (ESI) data collection using the **Azure Monitor Log Ingestion API** (the modern replacement for the deprecated Log Analytics HTTP Data Collector API). + +> [!IMPORTANT] +> The legacy **Log Analytics HTTP Data Collector API** (workspace ID + shared key) is being retired by Microsoft. All new ESI deployments must use the Log Ingestion API. Existing deployments should migrate before the end-of-support date. See the migration guide: [Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md](./Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md). + +## Resources Deployed + +The template `azuredeploy_ESI_LogIngestionAPI.json` provisions the following resources into an **existing Log Analytics workspace**: + +1. **Data Collection Endpoint (DCE)** — HTTPS endpoint receiving log payloads. +2. **Custom Log Analytics Tables** (each deployment is optional via master switches): + - `ESIAPIExchangeOnPremConfig_CL` — Exchange **On-Premises** configuration data. + - `ESIAPIExchangeOnlineConfig_CL` — Exchange **Online** configuration data. + - `ExchangeOnlineMessageTracking_CL` — Message tracking logs. +3. **Data Collection Rules (DCR)** — one per table, defining schema, transform (`transformKql`), and routing: + - `DCR-ESI-OnPremisesConfig` + - `DCR-ESI-OnlineConfig` + - `DCR-ESI-MessageTracking` + +Each block is guarded by a boolean parameter so you can deploy the full stack or only a subset (for example, add on-premises collection to an existing online setup). + +## Template Parameters + +| Parameter | Type | Default | Purpose | +|-------------------------------------------|---------|----------------------------|--------------------------------------------------------------------------------------------------------------------| +| `workspaceName` | string | (required) | Name of the existing Log Analytics workspace (same subscription/resource group/region as the DCE). | +| `location` | string | `resourceGroup().location` | Azure region for the DCE and DCRs. Must match the workspace region. | +| `dataCollectionEndpointName` | string | `DCE-ESI-LogIngestion` | Name of the DCE. | +| `dataCollectionRuleOnPremisesConfigName` | string | `DCR-ESI-OnPremisesConfig` | Name of the on-premises config DCR. | +| `dataCollectionRuleOnlineConfigName` | string | `DCR-ESI-OnlineConfig` | Name of the Exchange Online config DCR. | +| `dataCollectionRuleMessageTrackingName` | string | `DCR-ESI-MessageTracking` | Name of the message tracking DCR. | +| `retentionInDays` | int | `90` (min 30, max 730) | Retention for the custom tables. | +| `deployTables` | bool | `true` | Master switch — deploy the custom Log Analytics tables. Set to `false` when the tables already exist. | +| `deployDataCollection` | bool | `true` | Master switch — deploy the DCE and DCRs. Set to `false` to only (re)deploy the tables. | +| `deployOnPremConfigTable` | bool | `true` | Deploy the on-premises config table and its DCR. | +| `deployOnlineConfigTable` | bool | `true` | Deploy the Exchange Online config table and its DCR. | +| `deployMessageTrackingTable` | bool | `true` | Deploy the message tracking table and its DCR. | + +> [!TIP] +> Use `deployTables=false` and `deployDataCollection=true` when reusing pre-existing tables (for example, after a schema-only redeployment). Use `deployTables=true` and `deployDataCollection=false` to only create or update table schemas. + +## Prerequisites + +- Azure subscription with **Contributor** (or higher) on the target resource group. +- An **existing Log Analytics workspace** (Microsoft Sentinel-enabled if you plan to detect on this data). +- Azure CLI or PowerShell with the `Az.*` modules. +- The Entra ID application (or managed identity) that will send data (see [README_AzureMonitorSetup.md](README_AzureMonitorSetup.md) for the full identity setup). + +## Deployment + +### Azure CLI + +```bash +az login +az account set --subscription "YOUR_SUBSCRIPTION_ID" + +# Create resource group (if needed) +az group create --name "rg-sentinel-esi" --location "eastus" + +az deployment group create \ + --resource-group "rg-sentinel-esi" \ + --template-file azuredeploy_ESI_LogIngestionAPI.json \ + --parameters workspaceName=law-sentinel-prod +``` + +### PowerShell + +```powershell +Connect-AzAccount +Set-AzContext -SubscriptionId "YOUR_SUBSCRIPTION_ID" + +New-AzResourceGroup -Name "rg-sentinel-esi" -Location "eastus" -Force + +New-AzResourceGroupDeployment ` + -ResourceGroupName "rg-sentinel-esi" ` + -TemplateFile "azuredeploy_ESI_LogIngestionAPI.json" ` + -workspaceName "law-sentinel-prod" +``` + +### Azure Portal + +1. Navigate to **Deploy a custom template**. +2. Select **Build your own template in the editor**. +3. Paste the content of `azuredeploy_ESI_LogIngestionAPI.json`. +4. Fill in the parameters and confirm the master switches. +5. Click **Review + create**. + +## Deployment Outputs + +| Output name | Purpose | +|---------------------------------------------------|----------------------------------------------------------------| +| `dataCollectionEndpointId` | Full resource ID of the DCE. | +| `dataCollectionEndpointUri` | HTTPS ingestion URI. Required by the collector. | +| `dataCollectionRuleOnPremisesConfigId` | Full resource ID of the on-premises DCR. | +| `dataCollectionRuleOnPremisesConfigImmutableId` | **Immutable ID** used by the collector for on-premises data. | +| `dataCollectionRuleOnlineConfigId` | Full resource ID of the online DCR. | +| `dataCollectionRuleOnlineConfigImmutableId` | **Immutable ID** used by the collector for online data. | +| `dataCollectionRuleMessageTrackingId` | Full resource ID of the message tracking DCR. | +| `dataCollectionRuleMessageTrackingImmutableId` | **Immutable ID** used by the collector for message tracking. | +| `onPremConfigTableName` | Confirms the on-premises table name (or `Not deployed`). | +| `configTableName` | Confirms the Exchange Online table name (or `Not deployed`). | +| `messageTrackingTableName` | Confirms the message tracking table name (or `Not deployed`). | + +Outputs of skipped resources return the string `Not deployed`. + +Retrieve them after deployment: + +```powershell +$deploy = Get-AzResourceGroupDeployment -ResourceGroupName "rg-sentinel-esi" -Name "YOUR_DEPLOYMENT_NAME" +$deploy.Outputs.dataCollectionEndpointUri.Value +$deploy.Outputs.dataCollectionRuleOnlineConfigImmutableId.Value +``` + +## Post-Deployment: Assign Ingestion Permissions + +The identity used by the ESI collector needs the **Monitoring Metrics Publisher** role on **each DCR** it sends data to. + +```bash +# Example: assign to a service principal on the Online Config DCR +az role assignment create \ + --role "Monitoring Metrics Publisher" \ + --assignee "YOUR_APP_OBJECT_ID" \ + --assignee-principal-type ServicePrincipal \ + --scope "/subscriptions//resourceGroups/rg-sentinel-esi/providers/Microsoft.Insights/dataCollectionRules/DCR-ESI-OnlineConfig" +``` + +Repeat for `DCR-ESI-OnPremisesConfig` and `DCR-ESI-MessageTracking` as needed. + +## Update the Collector Configuration + +Update `CollectExchSecConfiguration.json` with the deployment outputs: + +```json +{ + "LogCollection": { + "ActivateLogUpdloadToSentinel": "true", + "SentinelLogIngestionAPIActivated": "true", + "DataCollectionEndpointURI": "", + "DCRImmutableId": "", + "UseManagedIdentity": "false", + "TargetLogTenantID": "YOUR_TENANT_ID", + "TargetLogAppID": "YOUR_APP_ID", + "TargetLogCertificateThumbprint": "YOUR_CERTIFICATE_THUMBPRINT", + "LogTypeName": "ESIExchangeConfig" + } +} +``` + +Pick the immutable ID matching the target table (on-premises, online, or message tracking). + +## Table Schemas + +Column suffixes follow Log Analytics conventions: `_s` string, `_d` real, `_g` guid, `_b` boolean, `_t` datetime, `_l` long, `_i` int. + +### `ESIAPIExchangeOnPremConfig_CL` and `ESIAPIExchangeOnlineConfig_CL` + +Both tables share the same schema (only the target audience differs). + +| Column | Type | Description | +|---------------------------------|----------|--------------------------------------------------------| +| `TimeGenerated` | datetime | Ingestion timestamp (set by `transformKql`). | +| `EntryDate_s` | string | Date of the configuration entry. | +| `GenerationInstanceID_g` | guid | Unique identifier for the collector execution. | +| `ESIEnvironment_s` | string | Exchange environment identifier. | +| `Section_s` | string | Configuration section name. | +| `ExecutionResult_s` | string | Execution result (`Success`, `Error`, ...). | +| `Identity_s` | string | Raw serialized `Identity` object (source of truth). | +| `Identity_Depth_d` | real | Depth of the identity in the directory hierarchy. | +| `Identity_DistinguishedName_s` | string | Distinguished name (LDAP DN). | +| `Identity_DomainId_s` | string | Domain identifier (serialized when object). | +| `Identity_IsDeleted_b` | boolean | Whether the identity is flagged as deleted. | +| `Identity_IsRelativeDn_b` | boolean | Whether the DN is relative. | +| `Identity_Name_s` | string | Identity name. | +| `Identity_ObjectGuid_g` | guid | Object GUID. | +| `Identity_Parent_s` | string | Parent identity (serialized when object). | +| `Identity_PartitionFQDN_s` | string | Partition FQDN. | +| `Identity_PartitionGuid_g` | guid | Partition GUID. | +| `Identity_Rdn_s` | string | Relative distinguished name (serialized when object). | +| `IdentityString_s` | string | Human-readable identity string. | +| `RawData_s` | string | Full raw configuration payload in JSON. | +| `Name_s` | string | Object name. | +| `ProcessedByServer_s` | string | Server that produced the entry. | +| `PSCmdL_s` | string | PowerShell cmdlet used to collect the entry. | +| `WhenChanged_t` | datetime | `WhenChanged` timestamp. | +| `WhenCreated_t` | datetime | `WhenCreated` timestamp. | + +> [!NOTE] +> The `Identity_*` sub-property columns are extracted from the source `Identity` object by the DCR's `transformKql` using `parse_json`. When `Identity` is `null` (some sections do not populate it), the sub-columns are `null`/empty — no ingestion error. + +### `ExchangeOnlineMessageTracking_CL` + +| Column | Type | Description | +|---------------------------------|----------|------------------------------------------------| +| `TimeGenerated` | datetime | Ingestion timestamp. | +| `schemaVersion_s` | string | Schema version of the log entry. | +| `clientIp_s` | string | Client IP address. | +| `clientHostname_s` | string | Client hostname. | +| `serverIp_s` | string | Server IP address. | +| `senderHostname_s` | string | Sender hostname. | +| `sourceContext_s` | string | Source context. | +| `connectorId_s` | string | Connector identifier. | +| `source_s` | string | Message source. | +| `eventId_s` | string | Event identifier. | +| `internalMessageId_s` | string | Internal message identifier. | +| `messageId_s` | string | Message identifier. | +| `networkMessageId_s` | string | Network message identifier. | +| `recipientAddress_s` | string | Recipient email address. | +| `recipientStatus_s` | string | Recipient delivery status. | +| `totalBytes_l` | long | Message size in bytes. | +| `recipientCount_i` | int | Number of recipients. | +| `relatedRecipientAddress_s` | string | Related recipient address. | +| `reference_s` | string | Message reference. | +| `messageSubject_s` | string | Email subject line. | +| `senderAddress_s` | string | Sender email address. | +| `returnPath_s` | string | Return path address. | +| `directionality_s` | string | Directionality (Originating / Incoming). | +| `messageInfo_s` | string | Additional message info. | +| `originalClientIp_s` | string | Original client IP. | +| `originalServerIp_s` | string | Original server IP. | +| `customData_s` | string | Custom metadata. | +| `transportTrafficType_s` | string | Transport traffic type. | +| `FilePath_s` | string | File path of the log entry. | +| `logId_s` | string | Log identifier. | +| `messageTrackingTenantId_s` | string | Tenant identifier for the message tracking log.| + +## Stream Names for API Ingestion + +When calling the Log Ingestion API directly, the stream name that follows the DCR immutable ID must match the DCR: + +| DCR | Stream declared | Output stream (table) | +|----------------------------|----------------------------------------|-------------------------------------------| +| `DCR-ESI-OnPremisesConfig` | `Custom-ESIExchangeConfig` | `Custom-ESIAPIExchangeOnPremConfig_CL` | +| `DCR-ESI-OnlineConfig` | `Custom-ESIExchangeOnlineConfig` | `Custom-ESIAPIExchangeOnlineConfig_CL` | +| `DCR-ESI-MessageTracking` | `Custom-ExchangeOnlineMessageTracking` | `Custom-ExchangeOnlineMessageTracking_CL` | + +Example API call: + +```powershell +$endpoint = "https://..ingest.monitor.azure.com" +$dcrImmutableId = "dcr-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +$streamName = "Custom-ESIExchangeOnlineConfig" + +$uri = "$endpoint/dataCollectionRules/$dcrImmutableId/streams/$streamName" + + "?api-version=2023-01-01" + +Invoke-RestMethod -Uri $uri -Method Post -Body $jsonData -Headers @{ + "Authorization" = "Bearer $accessToken" + "Content-Type" = "application/json" +} +``` + +## Monitoring + +Verify ingestion after deployment: + +```kql +// Exchange Online configuration ingestion +ESIAPIExchangeOnlineConfig_CL +| summarize count() by bin(TimeGenerated, 1h), Section_s +| render timechart + +// Exchange On-Premises configuration ingestion +ESIAPIExchangeOnPremConfig_CL +| summarize count() by bin(TimeGenerated, 1h), Section_s +| render timechart + +// Message Tracking ingestion +ExchangeOnlineMessageTracking_CL +| summarize count() by bin(TimeGenerated, 1h), directionality_s +| render timechart +``` + +## Troubleshooting + +### 403 Forbidden on ingestion + +Verify the identity used by the collector has the **Monitoring Metrics Publisher** role on the correct DCR (not the DCE, not the workspace). + +### Data does not appear + +1. Confirm the `DCRImmutableId` in the collector configuration matches the DCR routing to the target table. +2. Check the `transformKql` output columns match the destination table columns. +3. Review collector logs for payload size errors (Log Ingestion API limit is 1 MB per call — the collector auto-segments). + +### Table already exists + +If a table already exists with a different schema, either: + +- Redeploy with `deployTables=false` (leave tables as-is), or +- Manually align columns in the workspace, or +- Delete the table via the Log Analytics workspace and redeploy. + +### Deployment fails on cross-region resources + +The DCE, DCRs, and workspace must be in the **same region**. Adjust the `location` parameter or move the workspace. + +## Cleanup + +```bash +# Delete DCRs +az monitor data-collection rule delete --name "DCR-ESI-OnPremisesConfig" --resource-group "rg-sentinel-esi" +az monitor data-collection rule delete --name "DCR-ESI-OnlineConfig" --resource-group "rg-sentinel-esi" +az monitor data-collection rule delete --name "DCR-ESI-MessageTracking" --resource-group "rg-sentinel-esi" + +# Delete DCE +az monitor data-collection endpoint delete --name "DCE-ESI-LogIngestion" --resource-group "rg-sentinel-esi" +``` + +> [!CAUTION] +> Custom Log Analytics tables cannot be deleted via API. They can only be removed through the Log Analytics workspace in the Azure Portal, or by deleting the workspace itself. + +## Related Documentation + +- Full identity + permission setup: [README_AzureMonitorSetup.md](README_AzureMonitorSetup.md) +- Migration from the legacy Log Analytics API: [Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md](Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md) +- Sample payload: [sample-Custom-ESIExchangeConfig.json](sample-Custom-ESIExchangeConfig.json) + +## Support + +- GitHub: +- Microsoft Sentinel community: diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/WinformConfigReadme.md b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/WinformConfigReadme.md new file mode 100644 index 00000000000..3cdb9291db1 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Documentations/WinformConfigReadme.md @@ -0,0 +1,43 @@ +# CollectExchSec Configuration Editor (WinForms Modular) + +Features: +- Modular multi-file architecture +- Responsive multi-column question layout (FlowLayoutPanel, auto re-wrap on resize) +- Full conditional visibility + rehydration +- Per-field restore, validation (regex + hook) +- Array & addons editors +- UDS Log Processor and Instance Configuration grids +- Undo/Redo stack (Ctrl+Z / Ctrl+Y) with full state snapshots +- Logging (plain text) & telemetry (JSON lines) with session id +- Export of only changed settings (File > Export Changed Settings…) +- Clipboard operations (load, paste dialog, copy current JSON) +- Raw JSON preview & diff summary before saving +- Read-only toggle + +Run: +```powershell +pwsh -ExecutionPolicy Bypass -File .\Scripts\WinFormsMod\Main.ps1 -ConfigPath .\Config\CollectExchSecConfiguration.json +``` + +Logs: +- Created under `Scripts/WinFormsMod/logs/` + - actions_*.log: human readable + - telemetry_*.jsonl: structured events + +Exporting Changed Settings: +- Produces JSON with: + - Questions: dictionary of path->new value + - UDSLogProcessor: array of modified rows (full rows) + - InstanceConfiguration: hash of modified instances (full sub-objects) + +Customization: +- Adjust `$Global:QuestionPanelBaseWidth` & related constants in `UI.Sections.ps1`. +- Increase undo history via `UndoMax` in `State.ps1`. +- Extend validation hooks in `Validate-Question` (Utilities.ps1). +- Add encryption for secure fields prior to save if required. + +Future Ideas: +- Debounced undo snapshotting +- JSON schema validation +- Theming / high contrast +- Telemetry opt-in/out flag \ No newline at end of file diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Operations/Watchlists/ExchBuildNumber.csv b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Operations/Watchlists/ExchBuildNumber.csv index bcbd079c400..9e451ca187a 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Operations/Watchlists/ExchBuildNumber.csv +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Operations/Watchlists/ExchBuildNumber.csv @@ -1,7 +1,39 @@ Productname,CU,SU,BuildNbAll,BuilCUNb,Major,CUBuildNb,SUBuildNb +Exchange Server SE,RTM,Jul26SU,15.02.2562.045,15.02.2562,15.02,2562,45 +Exchange Server SE,RTM,Jun26SU,15.02.2562.043,15.02.2562,15.02,2562,43 +Exchange Server SE,RTM,May26HU,15.02.2562.041,15.02.2562,15.02,2562,41 +Exchange Server SE,RTM,Feb26SU,15.02.2562.037,15.02.2562,15.02,2562,37 +Exchange Server SE,RTM,Dec25SU,15.02.2562.035,15.02.2562,15.02,2562,35 +Exchange Server SE,RTM,Oct25SU,15.02.2562.029,15.02.2562,15.02,2562,29 +Exchange Server SE,RTM,Sep25HU,15.02.2562.027,15.02.2562,15.02,2562,27 +Exchange Server SE,RTM,Aug25SU,15.02.2562.020,15.02.2562,15.02,2562,20 +Exchange Server SE,RTM,NoSU,15.02.2562.017,15.02.2562,15.02,2562,17 +Exchange Server 2019,CU15,Jul26SU,15.02.1748.048,15.02.1748,15.02,1748,48 +Exchange Server 2019,CU15,Jun26SU,15.02.1748.046,15.02.1748,15.02,1748,46 +Exchange Server 2019,CU15,Feb26SU,15.02.1748.043,15.02.1748,15.02,1748,43 +Exchange Server 2019,CU15,Dec25SU,15.02.1748.042,15.02.1748,15.02,1748,42 +Exchange Server 2019,CU15,Oct25SU,15.02.1748.039,15.02.1748,15.02,1748,39 +Exchange Server 2019,CU15,Sep25HU,15.02.1748.037,15.02.1748,15.02,1748,37 +Exchange Server 2019,CU15,Aug25SU,15.02.1748.036,15.02.1748,15.02,1748,36 +Exchange Server 2019,CU15,May25HU,15.02.1748.026,15.02.1748,15.02,1748,26 +Exchange Server 2019,CU15,Apr25HU,15.02.1748.024,15.02.1748,15.02,1748,24 +Exchange Server 2019,CU15,NoSU,15.02.1748.010,15.02.1748,15.02,1748,10 +Exchange Server 2019,CU14,Jul26SU,15.02.1544.043,15.02.1544,15.02,1544,43 +Exchange Server 2019,CU14,Jun26SU,15.02.1544.041,15.02.1544,15.02,1544,41 +Exchange Server 2019,CU14,Feb26SU,15.02.1544.039,15.02.1544,15.02,1544,39 +Exchange Server 2019,CU14,Dec25SU,15.02.1544.037,15.02.1544,15.02,1544,37 +Exchange Server 2019,CU14,Oct25SU,15.02.1544.036,15.02.1544,15.02,1544,36 +Exchange Server 2019,CU14,Sep25HU,15.02.1544.034,15.02.1544,15.02,1544,34 +Exchange Server 2019,CU14,Aug25SU,15.02.1544.033,15.02.1544,15.02,1544,33 +Exchange Server 2019,CU14,May25HU,15.02.1544.027,15.02.1544,15.02,1544,27 +Exchange Server 2019,CU14,Apr25HU,15.02.1544.025,15.02.1544,15.02,1544,25 +Exchange Server 2019,CU14,Nov24SUv2,15.02.1544.014,15.02.1544,15.02,1544,14 +Exchange Server 2019,CU14,Nov24SU,15.02.1544.013,15.02.1544,15.02,1544,13 Exchange Server 2019,CU14,Apr24SU,15.02.1544.011,15.02.1544,15.02,1544,11 Exchange Server 2019,CU14,Mar24SU,15.02.1544.009,15.02.1544,15.02,1544,9 Exchange Server 2019,CU14,NoSU,15.02.1544.004,15.02.1544,15.02,1544,4 +Exchange Server 2019,CU13,Nov24SUv2,15.02.1258.039,15.02.1258,15.02,1258,39 +Exchange Server 2019,CU13,Nov24SU,15.02.1258.038,15.02.1258,15.02,1258,38 Exchange Server 2019,CU13,Apr24SU,15.02.1258.034,15.02.1258,15.02,1258,34 Exchange Server 2019,CU13,Mar24SU,15.02.1258.032,15.02.1258,15.02,1258,32 Exchange Server 2019,CU13,Nov23SU,15.02.1258.028,15.02.1258,15.02,1258,28 @@ -62,6 +94,17 @@ Exchange Server 2019,CU1,NoSU,15.02.330.005,15.02.330,15.02,330,5 Exchange Server 2019,RTM,Mar21SU,15.02.221.018,15.02.221,15.02,221,18 Exchange Server 2019,RTM,NoSU,15.02.221.012,15.02.221,15.02,221,12 Exchange Server 2019,Preview,NoSU,15.02.196.00,15.02.196,15.02,196, +Exchange Server 2016,CU23,Jul26SU,15.01.2507.071,15.01.2507,15.01,2507,71 +Exchange Server 2016,CU23,Jun26SU,15.01.2507.069,15.01.2507,15.01,2507,69 +Exchange Server 2016,CU23,Feb26SU,15.01.2507.066,15.01.2507,15.01,2507,66 +Exchange Server 2016,CU23,Dec25SU,15.01.2507.063,15.01.2507,15.01,2507,63 +Exchange Server 2016,CU23,Oct25SU,15.01.2507.061,15.01.2507,15.01,2507,61 +Exchange Server 2016,CU23,Sep25HU,15.01.2507.059,15.01.2507,15.01,2507,59 +Exchange Server 2016,CU23,Aug25SU,15.01.2507.058,15.01.2507,15.01,2507,58 +Exchange Server 2016,CU23,May25HU,15.01.2507.057,15.01.2507,15.01,2507,57 +Exchange Server 2016,CU23,Apr25HU,15.01.2507.055,15.01.2507,15.01,2507,55 +Exchange Server 2016,CU23,Nov24SUv2,15.01.2507.044,15.01.2507,15.01,2507,44 +Exchange Server 2016,CU23,Nov24SU,15.01.2507.043,15.01.2507,15.01,2507,43 Exchange Server 2016,CU23,Apr24SU,15.01.2507.039,15.1.2507,15.01,2507,35 Exchange Server 2016,CU23,Mar24SU,15.01.2507.037,15.1.2507,15.01,2507,39 Exchange Server 2016,CU23,Nov23SU,15.01.2507.035,15.1.2507,15.01,2507,37 diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Operations/Watchlists/standardMRAOnline.csv b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Operations/Watchlists/standardMRAOnline.csv index ca5b5e30309..a52ff3a9cda 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Operations/Watchlists/standardMRAOnline.csv +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Operations/Watchlists/standardMRAOnline.csv @@ -11,10 +11,20 @@ Application Mail.Read-Organization Management-Delegating,Application Mail.Read,O Application Mail.ReadBasic-Organization Management-Delegating,Application Mail.ReadBasic,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Application Mail.ReadWrite-Organization Management-Delegating,Application Mail.ReadWrite,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Application Mail.Send-Organization Management-Delegating,Application Mail.Send,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxConfigItem.Read-Organization Management-Deleg,Application MailboxConfigItem.Read,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxConfigItem.ReadWrite-Organization Management-,Application MailboxConfigItem.ReadWrite,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxFolder.Read-Organization Management-Delegatin,Application MailboxFolder.Read,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxFolder.ReadWrite-Organization Management-Dele,Application MailboxFolder.ReadWrite,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxItem.Export-Organization Management-Delegatin,Application MailboxItem.Export,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxItem.ImportExport-Organization Management-Del,Application MailboxItem.ImportExport,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxItem.Read-Organization Management-Delegating,Application MailboxItem.Read,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailboxItem.ReadWrite-Organization Management-Delega,Application MailboxItem.ReadWrite,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Application MailboxSettings.Read-Organization Management-Delegat,Application MailboxSettings.Read,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Application MailboxSettings.ReadWrite-Organization Management-De,Application MailboxSettings.ReadWrite,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application MailTips.ReadBasic.All-Organization Management-Deleg,Application MailTips.ReadBasic.All,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Application SMTP.SendAsApp-Organization Management-Delegating,Application SMTP.SendAsApp,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct ApplicationImpersonation-Organization Management-Delegating,ApplicationImpersonation,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct -ApplicationImpersonation-RIM-MailboxAdminscc5e64999db94ccba09208,ApplicationImpersonation,RIM-MailboxAdminscc5e64999db94ccba09208f0387dfd74,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +ApplicationImpersonation-RIM-MailboxAdmins27188811fdd1476d9098e5,ApplicationImpersonation,RIM-MailboxAdmins27188811fdd1476d9098e5e5885d0bd0,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct ArchiveApplication-Organization Management-Delegating,ArchiveApplication,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Audit Logs-Compliance Management,Audit Logs,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Audit Logs-Organization Management,Audit Logs,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct @@ -32,13 +42,13 @@ Compliance Admin-Organization Management-Delegating,Compliance Admin,Organizatio Data Loss Prevention-Compliance Management,Data Loss Prevention,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Data Loss Prevention-Organization Management,Data Loss Prevention,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Data Loss Prevention-Organization Management-Delegating,Data Loss Prevention,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Distribution Groups-Organization Management,Distribution Groups,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Distribution Groups-Organization Management-Delegating,Distribution Groups,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Distribution Groups-Organization Management,Distribution Groups,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Distribution Groups-Recipient Management,Distribution Groups,Recipient Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -E-Mail Address Policies-Organization Management,E-Mail Address Policies,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct E-Mail Address Policies-Organization Management-Delegating,E-Mail Address Policies,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Federated Sharing-Organization Management,Federated Sharing,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +E-Mail Address Policies-Organization Management,E-Mail Address Policies,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Federated Sharing-Organization Management-Delegating,Federated Sharing,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Federated Sharing-Organization Management,Federated Sharing,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Information Protection Admin-Information Protection,Information Protection Admin,Information Protection,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Information Protection Admin-Information Protection Admins,Information Protection Admin,Information Protection Admins,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Information Protection Admin-Organization Management,Information Protection Admin,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct @@ -62,12 +72,12 @@ Insider Risk Management Investigation-Insider Risk Management,Insider Risk Manag Insider Risk Management Investigation-Insider Risk Management In,Insider Risk Management Investigation,Insider Risk Management Investigators,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Insider Risk Management Investigation-Organization Management,Insider Risk Management Investigation,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Journaling-Compliance Management,Journaling,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Journaling-Organization Management,Journaling,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Journaling-Organization Management-Delegating,Journaling,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Journaling-Organization Management,Journaling,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Journaling-Records Management,Journaling,Records Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Legal Hold-Discovery Management,Legal Hold,Discovery Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,None,Direct -Legal Hold-Organization Management,Legal Hold,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,None,Direct Legal Hold-Organization Management-Delegating,Legal Hold,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,None,Direct +Legal Hold-Organization Management,Legal Hold,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,None,Direct LegalHoldApplication-Organization Management-Delegating,LegalHoldApplication,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Mail Enabled Public Folders-Organization Management,Mail Enabled Public Folders,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Mail Enabled Public Folders-Organization Management-Delegating,Mail Enabled Public Folders,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct @@ -89,8 +99,8 @@ Message Tracking-Organization Management,Message Tracking,Organization Managemen Message Tracking-Organization Management-Delegating,Message Tracking,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Message Tracking-Recipient Management,Message Tracking,Recipient Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Message Tracking-Records Management,Message Tracking,Records Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Migration-Organization Management,Migration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Migration-Organization Management-Delegating,Migration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Migration-Organization Management,Migration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Migration-Recipient Management,Migration,Recipient Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Move Mailboxes-Organization Management,Move Mailboxes,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Move Mailboxes-Organization Management-Delegating,Move Mailboxes,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct @@ -101,24 +111,24 @@ My Custom Apps-Organization Management-Delegating,My Custom Apps,Organization Ma My Marketplace Apps-Default Role Assignment Policy,My Marketplace Apps,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct My Marketplace Apps-Default Role Assignment Policy-1,My Marketplace Apps,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct My Marketplace Apps-Organization Management-Delegating,My Marketplace Apps,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -My ReadWriteMailbox Apps-Default Role Assignment Policy,My ReadWriteMailbox Apps,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct My ReadWriteMailbox Apps-Default Role Assignment Policy-1,My ReadWriteMailbox Apps,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct +My ReadWriteMailbox Apps-Default Role Assignment Policy,My ReadWriteMailbox Apps,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct My ReadWriteMailbox Apps-Organization Management-Delegating,My ReadWriteMailbox Apps,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -MyBaseOptions-Default Role Assignment Policy,MyBaseOptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyBaseOptions-Default Role Assignment Policy-1,MyBaseOptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct +MyBaseOptions-Default Role Assignment Policy,MyBaseOptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyBaseOptions-Organization Management-Delegating,MyBaseOptions,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyContactInformation-Default Role Assignment Policy,MyContactInformation,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyContactInformation-Default Role Assignment Policy-1,MyContactInformation,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyContactInformation-Organization Management-Delegating,MyContactInformation,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -MyDistributionGroupMembership-Default Role Assignment Policy,MyDistributionGroupMembership,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,None,MyGAL,None,Direct MyDistributionGroupMembership-Default Role Assignment Policy-1,MyDistributionGroupMembership,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,None,MyGAL,None,Direct +MyDistributionGroupMembership-Default Role Assignment Policy,MyDistributionGroupMembership,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,None,MyGAL,None,Direct MyDistributionGroupMembership-Organization Management-Delegating,MyDistributionGroupMembership,Organization Management,All Group Members,RoleGroup,,,MyGAL,None,MyGAL,None,Direct -MyDistributionGroups-Default Role Assignment Policy,MyDistributionGroups,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,OrganizationConfig,MyDistributionGroups,None,Direct MyDistributionGroups-Default Role Assignment Policy-1,MyDistributionGroups,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,OrganizationConfig,MyDistributionGroups,None,Direct +MyDistributionGroups-Default Role Assignment Policy,MyDistributionGroups,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,MyGAL,OrganizationConfig,MyDistributionGroups,None,Direct MyDistributionGroups-Organization Management-Delegating,MyDistributionGroups,Organization Management,All Group Members,RoleGroup,,,MyGAL,OrganizationConfig,MyDistributionGroups,None,Direct MyMailboxDelegation-Organization Management-Delegating,MyMailboxDelegation,Organization Management,All Group Members,RoleGroup,,,MyGAL,OrganizationConfig,MailboxICanDelegate,None,Direct -MyMailSubscriptions-Default Role Assignment Policy,MyMailSubscriptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyMailSubscriptions-Default Role Assignment Policy-1,MyMailSubscriptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct +MyMailSubscriptions-Default Role Assignment Policy,MyMailSubscriptions,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyMailSubscriptions-Organization Management-Delegating,MyMailSubscriptions,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyProfileInformation-Default Role Assignment Policy,MyProfileInformation,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyProfileInformation-Default Role Assignment Policy-1,MyProfileInformation,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct @@ -126,81 +136,86 @@ MyProfileInformation-Organization Management-Delegating,MyProfileInformation,Org MyRetentionPolicies-Default Role Assignment Policy,MyRetentionPolicies,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyRetentionPolicies-Default Role Assignment Policy-1,MyRetentionPolicies,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyRetentionPolicies-Organization Management-Delegating,MyRetentionPolicies,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -MyTextMessaging-Default Role Assignment Policy,MyTextMessaging,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyTextMessaging-Default Role Assignment Policy-1,MyTextMessaging,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct +MyTextMessaging-Default Role Assignment Policy,MyTextMessaging,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyTextMessaging-Organization Management-Delegating,MyTextMessaging,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -MyVoiceMail-Default Role Assignment Policy,MyVoiceMail,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyVoiceMail-Default Role Assignment Policy-1,MyVoiceMail,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct +MyVoiceMail-Default Role Assignment Policy,MyVoiceMail,Default Role Assignment Policy,All Policy Assignees,RoleAssignmentPolicy,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct MyVoiceMail-Organization Management-Delegating,MyVoiceMail,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct O365SupportViewConfig-O365 Support View Only,O365SupportViewConfig,O365 Support View Only,O365 Support View Only,PartnerLinkedRoleGroup,,,Organization,OrganizationConfig,None,None,Direct OfficeExtensionApplication-Organization Management-Delegating,OfficeExtensionApplication,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -Org Custom Apps-Organization Management,Org Custom Apps,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Org Custom Apps-Organization Management-Delegating,Org Custom Apps,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Org Custom Apps-Organization Management,Org Custom Apps,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Org Marketplace Apps-Organization Management,Org Marketplace Apps,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Org Marketplace Apps-Organization Management-Delegating,Org Marketplace Apps,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Organization Client Access-Organization Management,Organization Client Access,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Organization Client Access-Organization Management-Delegating,Organization Client Access,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Organization Configuration-Organization Management,Organization Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Organization Client Access-Organization Management,Organization Client Access,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Organization Configuration-Organization Management-Delegating,Organization Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Organization Configuration-Organization Management,Organization Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Organization Transport Settings-Organization Management,Organization Transport Settings,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Organization Transport Settings-Organization Management-Delegati,Organization Transport Settings,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +PlacesBuildingManagement-Organization Management,PlacesBuildingManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +PlacesBuildingManagement-Organization Management-Delegating,PlacesBuildingManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +PlacesDeskManagement-Organization Management,PlacesDeskManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +PlacesDeskManagement-Organization Management-Delegating,PlacesDeskManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Privacy Management Admin-Organization Management,Privacy Management Admin,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Privacy Management Admin-Privacy Management,Privacy Management Admin,Privacy Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Privacy Management Admin-Privacy Management Administrators,Privacy Management Admin,Privacy Management Administrators,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Privacy Management Investigation-Organization Management,Privacy Management Investigation,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Privacy Management Investigation-Privacy Management,Privacy Management Investigation,Privacy Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct Privacy Management Investigation-Privacy Management Investigator,Privacy Management Investigation,Privacy Management Investigators,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct -Public Folders-Organization Management,Public Folders,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Public Folders-Organization Management-Delegating,Public Folders,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Public Folders-Organization Management,Public Folders,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Recipient Policies-Organization Management,Recipient Policies,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Recipient Policies-Organization Management-Delegating,Recipient Policies,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Recipient Policies-Recipient Management,Recipient Policies,Recipient Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Remote and Accepted Domains-Organization Management,Remote and Accepted Domains,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Remote and Accepted Domains-Organization Management-Delegating,Remote and Accepted Domains,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Remote and Accepted Domains-Organization Management,Remote and Accepted Domains,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Reset Password-Help Desk,Reset Password,Help Desk,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Reset Password-Organization Management,Reset Password,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Reset Password-Organization Management-Delegating,Reset Password,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Reset Password-Recipient Management,Reset Password,Recipient Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Retention Management-Compliance Management,Retention Management,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Retention Management-Organization Management,Retention Management,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Retention Management-Organization Management-Delegating,Retention Management,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Retention Management-Organization Management,Retention Management,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Retention Management-Records Management,Retention Management,Records Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Role Management-Organization Management,Role Management,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Role Management-Organization Management-Delegating,Role Management,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Admin-Organization Management,Security Admin,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Admin-Organization Management-Delegating,Security Admin,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Admin-Security Administrator,Security Admin,Security Administrator,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Security Group Creation and Membership-Organization Management,Security Group Creation and Membership,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Group Creation and Membership-Organization Management-D,Security Group Creation and Membership,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Security Reader-Organization Management,Security Reader,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Security Group Creation and Membership-Organization Management,Security Group Creation and Membership,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Reader-Organization Management-Delegating,Security Reader,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Security Reader-Organization Management,Security Reader,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Security Reader-Security Reader,Security Reader,Security Reader,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct SendMailApplication-Organization Management-Delegating,SendMailApplication,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct SensitivityLabelAdministrator-Organization Management-Delegating,SensitivityLabelAdministrator,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct SensitivityLabelAdministrator-Security Administrator,SensitivityLabelAdministrator,Security Administrator,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct TeamMailboxLifecycleApplication-Organization Management-Delegati,TeamMailboxLifecycleApplication,Organization Management,All Group Members,RoleGroup,,,Self,OrganizationConfig,Self,OrganizationConfig,Direct -Tenant AllowBlockList Manager-Organization Management-Delegating,Tenant AllowBlockList Manager,Organization Management,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct -Tenant AllowBlockList Manager-Security Operator,Tenant AllowBlockList Manager,Security Operator,All Group Members,RoleGroup,,,Organization,None,Organization,None,Direct +Tenant AllowBlockList Manager-Organization Management-Delegating,Tenant AllowBlockList Manager,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Tenant AllowBlockList Manager-Security Operator,Tenant AllowBlockList Manager,Security Operator,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct TenantPlacesManagement-Organization Management,TenantPlacesManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct TenantPlacesManagement-Organization Management-Delegating,TenantPlacesManagement,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +TenantPlacesManagement-Places Administrator,TenantPlacesManagement,Places Administrator,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Hygiene-Hygiene Management,Transport Hygiene,Hygiene Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Hygiene-Organization Management,Transport Hygiene,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Hygiene-Organization Management-Delegating,Transport Hygiene,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Rules-Compliance Management,Transport Rules,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct -Transport Rules-Organization Management,Transport Rules,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Rules-Organization Management-Delegating,Transport Rules,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct +Transport Rules-Organization Management,Transport Rules,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct Transport Rules-Records Management,Transport Rules,Records Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct User Options-Help Desk,User Options,Help Desk,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct User Options-Organization Management,User Options,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct User Options-Organization Management-Delegating,User Options,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct UserApplication-Organization Management-Delegating,UserApplication,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,Organization,OrganizationConfig,Direct View-Only Audit Logs-Compliance Management,View-Only Audit Logs,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct -View-Only Audit Logs-Organization Management,View-Only Audit Logs,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Audit Logs-Organization Management-Delegating,View-Only Audit Logs,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct +View-Only Audit Logs-Organization Management,View-Only Audit Logs,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Configuration-Compliance Management,View-Only Configuration,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Configuration-Hygiene Management,View-Only Configuration,Hygiene Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct -View-Only Configuration-Organization Management,View-Only Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Configuration-Organization Management-Delegating,View-Only Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct +View-Only Configuration-Organization Management,View-Only Configuration,Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Configuration-View-Only Organization Management,View-Only Configuration,View-Only Organization Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Recipients-Compliance Management,View-Only Recipients,Compliance Management,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct View-Only Recipients-Help Desk,View-Only Recipients,Help Desk,All Group Members,RoleGroup,,,Organization,OrganizationConfig,None,None,Direct diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Beta/CollectExchSecIns.zip b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Beta/CollectExchSecIns.zip new file mode 100644 index 00000000000..9eed7cd0e89 Binary files /dev/null and b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Beta/CollectExchSecIns.zip differ diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Beta/CollectExchSecInsVersionTracking.json b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Beta/CollectExchSecInsVersionTracking.json index ca062ad5c7b..2dfc7f87280 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Beta/CollectExchSecInsVersionTracking.json +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Beta/CollectExchSecInsVersionTracking.json @@ -1,17 +1,27 @@ { - "LatestVersion":"7.6.0.1", - "LatestVersionDate":"2024-07-26T00:00:00.000Z", + "LatestVersion":"8.0.0.0", + "LatestVersionDate":"2025-08-29T00:00:00.000Z", "LatestVersionDownload":"https://aka.ms/ESI-ExchangeCollector-RawScript", - "ESICollectorRawRepository":"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Solutions/ESICollector/", + "ESICollectorRawRepository":"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Solutions/ESICollector", "VersionHistory":[ { + "Version":"8.0.0.0", + "InternalVersion":8000, + "VersionFileName":"CollectExchSecIns-v8.0.0.0.zip", + "Beta": false, + "BreakingChanges":[ + { + "Description":"The script now uses DCR (Data Collection Rules) for data collection. You need to configure DCR before running the script.", + "Target":"All" + } + ] + },{ "Version":"7.6.0.1", "InternalVersion":7601, "VersionFileName":"CollectExchSecIns-v7.6.0.1.zip", "Beta": false, "BreakingChanges":[] - }, - { + },{ "Version":"7.6.0.0", "InternalVersion":7600, "VersionFileName":"CollectExchSecIns-v7.6.0.0.zip", @@ -60,7 +70,7 @@ "VersionFileName":"CollectExchSecIns-v7.4.2.0.zip", "BreakingChanges":[ { - "Description":"The script now uses the new Exchange Online V2 module. You need to install it before running the script. See https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Solutions/ESICollector#from-732-to-742 for more information.", + "Description":"The script now uses the new Exchange Online V2 module. You need to install it before running the script. See https://github.com/nlepagnez/ESI-PublicContent/tree/main/Solutions/ESICollector#from-732-to-742 for more information.", "Target":"Online" } ] diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecIns-v8.0.0.0.zip b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecIns-v8.0.0.0.zip new file mode 100644 index 00000000000..9eed7cd0e89 Binary files /dev/null and b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecIns-v8.0.0.0.zip differ diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecIns.zip b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecIns.zip index d8e957ba8cb..9eed7cd0e89 100644 Binary files a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecIns.zip and b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecIns.zip differ diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecInsVersionTracking.json b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecInsVersionTracking.json index ca062ad5c7b..5b6250d71e9 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecInsVersionTracking.json +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/CollectExchSecInsVersionTracking.json @@ -1,17 +1,27 @@ { - "LatestVersion":"7.6.0.1", - "LatestVersionDate":"2024-07-26T00:00:00.000Z", + "LatestVersion":"8.0.0.0", + "LatestVersionDate":"2026-07-29T00:00:00.000Z", "LatestVersionDownload":"https://aka.ms/ESI-ExchangeCollector-RawScript", - "ESICollectorRawRepository":"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Solutions/ESICollector/", + "ESICollectorRawRepository":"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Solutions/ESICollector", "VersionHistory":[ { + "Version":"8.0.0.0", + "InternalVersion":8000, + "VersionFileName":"CollectExchSecIns-v8.0.0.0.zip", + "Beta": false, + "BreakingChanges":[ + { + "Description":"The script now uses DCR (Data Collection Rules) for data collection. You need to configure DCR before running the script.", + "Target":"All" + } + ] + },{ "Version":"7.6.0.1", "InternalVersion":7601, "VersionFileName":"CollectExchSecIns-v7.6.0.1.zip", "Beta": false, "BreakingChanges":[] - }, - { + },{ "Version":"7.6.0.0", "InternalVersion":7600, "VersionFileName":"CollectExchSecIns-v7.6.0.0.zip", @@ -60,7 +70,7 @@ "VersionFileName":"CollectExchSecIns-v7.4.2.0.zip", "BreakingChanges":[ { - "Description":"The script now uses the new Exchange Online V2 module. You need to install it before running the script. See https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Solutions/ESICollector#from-732-to-742 for more information.", + "Description":"The script now uses the new Exchange Online V2 module. You need to install it before running the script. See https://github.com/nlepagnez/ESI-PublicContent/tree/main/Solutions/ESICollector#from-732-to-742 for more information.", "Target":"Online" } ] diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/OnlineDeployment/CollectExchSecIns.ps1 b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/OnlineDeployment/CollectExchSecIns.ps1 index cd55db189bb..f23e59b7b33 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/OnlineDeployment/CollectExchSecIns.ps1 +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/OnlineDeployment/CollectExchSecIns.ps1 @@ -23,10 +23,19 @@ possibility of such damages .\CollectExchSecIns.ps1 .OUTPUTS - The output a csv file of collected data + Ingestion in Azure Sentinel .NOTES Developed by ksangui@microsoft.com and Nicolas Lepagnez (nilepagn@microsoft.com) + Version : 8.0.0.0 - Released : IN DEV - nilepagn + - Implement Log Ingestion API for Sentinel + - Create $Script:ESIDataPath to store data in a specific folder and become independant from CSV configuration + - Move ExportDomainsInformation to LogCollection Section in configuration. If set to true, the Domain Information will be exported in the Log Collection. Default Value is True as before. + - Change Github link for Configuration file to use the new repository + - Adding possibility to use github API insteafd of direct download for configuration file + - Adding runtime warning banner when the collector still uses the legacy Log Analytics HTTP Data Collector API. + See ESI-PublicContent/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md for the migration procedure. + Version : 7.6.0.1 - Released : 26/07/2024 - nilepagn - Adding Try-Catch on Get-AutomationVariable Test - Correct a bug on Get-LastVersion with Write-LogMessage @@ -142,48 +151,6 @@ possibility of such damages - Prepare the ability to retrieve Add-On files from Internet - Prepare the ability to check a checksum of AuditFunctions. - Version : 6.5 - Released : 27/09/2022 - nilepagn - - Correction of bug on retrieving Exchange Servers - - Adding ESIEnvironment Information to correlate Configuration with logs in Sentinel - - Version : 6.4 - Released : 22/09/2022 - nilepagn - - Filtering EDGE Servers that can't be analyzed - - Correcting a bug on Custom Select Fields - - Adding possibility to generate information for a specific Sentinel API Table. Add '//' in OutputStream of the function. Like "myfile.csv//SpecificSentinelTable" - - Version : 6.3 - Released : 19/09/2022 - nilepagn - - Correct bug on AD Requests on a multi-domain environment - - Add the processing of the JobStatus type "Error" during transformation - - Changes how Errors from jobs are displayed in logs : Display as warning to doesn't throw error - - Add a correct error processing when user domain doesn't have the homeMBD attribute - - Modify the end of script to correctly ends the logging - - Version : 6.2.2 - Released : 12/09/2022 - Ksangui - -Add Get-inboundConnecot and Get OutboungConnector for Online - - Version : 6.2.1 - Released : 10/09/2022 - nilepagn - - Possibility to display TargetServer on Select (It was a regression from 4.x version) - - Version : 6.2 - Released : ? - nilepagn - - Possibility to use Log Analytics API and CSV in same time. - - Version : 6.1.1 - Released : 24/08/2022 - nilepagn - - Correcting bug on multithreading. - - Version published on On-Premises testing environment and validated. - - Version : 6.1 - Released : 24/08/2022 - nilepagn - - Adding ESIEnvironment column in entries adding the possibility to audit multiple On-Premises and Online Exchange configuration - - Version : 6.0.1 - Released : 24/08/2022 - nilepagn - - Bug on Write-LogMessage during function loading. - - Version : 6.0 - Released : 24/08/2022 - nilepagn - - Merge of On-Premises version and Cloud Version of ESI Collector - - Deactivate the possibility to launch multi-threading in Azure Automation - - Add "ESIProcessingType":"Online" in Global Section of JSON File. The value can be "Online" or "On-Premises" - - Add "ProcessingCategory":"All" for Audit Functions. The value can be "All", "Online" or "On-Premises" - - Reorganization of functions in the code by category - ** For Previous version history, see Github page ** #> @@ -196,12 +163,14 @@ Param ( $EPS2010=$false, [switch] $NoDateTracing, [string] $InstanceName = "Default", + [switch] $ExchangeSimulationInjection, + [String] $SimulationInformation = "Internal", [switch] $GetVersion, [string] $ReceivedTenantName, [switch] $IsOutsideAzureAutomation ) -$ESICollectorCurrentVersion = "7.6.0.1" +$ESICollectorCurrentVersion = "8.0.0.0" if ($GetVersion) {return $ESICollectorCurrentVersion} $Script:SupportedConfigurationVersion = "2.4" @@ -571,6 +540,12 @@ $Script:SupportedConfigurationVersion = "2.4" $CapabilitiesList = @('OP', 'ADINFOS') ) + if ($Script:_InternalInjection) + { + Write-LogMessage -Message "Internal Injection, Capabilities are not loaded" -Level Warning + return + } + $script:CapabilityLoaded = @() foreach ($Capability in $CapabilitiesList) @@ -713,9 +688,9 @@ $Script:SupportedConfigurationVersion = "2.4" if ($Global:InstanceName -ne "Default") { $fileName = "DateTracking-$($Global:InstanceName).esi"} - if (Test-Path ((Split-Path $outputpath) + "\$fileName")) + if (Test-Path (($Script:ESIDataPath) + "\$fileName")) { - $ContentDate = Get-Content ((Split-Path $outputpath) + "\$fileName") + $ContentDate = Get-Content (($Script:ESIDataPath) + "\$fileName") } else { $ContentDate = $null @@ -923,9 +898,9 @@ $Script:SupportedConfigurationVersion = "2.4" else { if ($Global:InstanceName -ne "Default") { - $JSNToSave | Set-ESIContent ((Split-Path $outputpath) + "\DateTracking-$($Global:InstanceName).esi") + $JSNToSave | Set-ESIContent -Path (($Script:ESIDataPath) + "\DateTracking-$($Global:InstanceName).esi") } - else { $JSNToSave | Set-ESIContent ((Split-Path $outputpath) + "\DateTracking.esi") } + else { $JSNToSave | Set-ESIContent -Path (($Script:ESIDataPath) + "\DateTracking.esi") } } } } @@ -1272,6 +1247,43 @@ $Script:SupportedConfigurationVersion = "2.4" return $object } + function Connect-LogIngestionAPI + { + Write-LogMessage -Message ("Generate Log Ingestion API Token with Type $($Script:ESIProcessingType) ...") + Write-LogMessage -Message "Connect to Azure RM" + + if (-not $Global:AlreadyAzSentinelConnected) + { + # Ensures you do not inherit an AzContext in your runbook + Disable-AzContextAutosave -Scope Process + + if ($isRunbook -and $Script:SentinelLogCollector.UseManagedIdentity) + { + # Connect to Azure with system-assigned managed identity + $AzureContext = (Connect-AzAccount -Identity -ContextName "SentinelIngestion").context + $Global:AlreadyAzSentinelConnected = $true + } + else { + if ($Global:Interactive) + { + Write-LogMessage -Message "Az Connect with Interactive Logon" + $AzureContext = (Connect-AzAccount -ContextName "SentinelIngestion").context + } + else { + Write-LogMessage -Message "Az Connect with Interactive Logon" + $AzureContext = (Connect-AzAccount -ContextName "SentinelIngestion" -CertificateThumbprint $Script:SentinelLogCollector.TargetLogCertificateThumbprint -Tenant $Script:SentinelLogCollector.TargetLogTenantID -ApplicationId $Script:SentinelLogCollector.TargetLogAppID).context + } + $Global:AlreadyAzSentinelConnected = $true + } + } + else {$AzureContext = Get-AzContext -Name "SentinelIngestion"} + + #$AzureContext = Set-AzContext -SubscriptionName $AzureContext.Subscription -DefaultProfile $AzureContext + $context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.Contexts["SentinelIngestion"] + + $Script:SentinelLogIngestionToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://monitor.azure.com") + } + function Get-OutputOptions { Param( @@ -1284,6 +1296,7 @@ $Script:SupportedConfigurationVersion = "2.4" $OutputOptions | Add-Member Noteproperty -Name OutputFileName -value $null $OutputOptions | Add-Member Noteproperty -Name IsPartial -value $false $OutputOptions | Add-Member Noteproperty -Name AlreadyBackuped -value $false + $OutputOptions | Add-Member Noteproperty -Name LogTypeName -value $Script:SentinelLogCollector.LogTypeName if ($OutputName -match '_Page') { @@ -1304,7 +1317,7 @@ $Script:SupportedConfigurationVersion = "2.4" { if ([string]::IsNullOrEmpty($Script:InstanceConfiguration.OutputName)) { - $TargetOutputName = $script:outputpath -replace '.csv',"-$($Script:InstanceName).csv" + $TargetOutputName = $script:CSVOutputFile -replace '.csv',"-$($Script:InstanceName).csv" $OutputOptions.OutputFileName = $Script:InstanceName } else @@ -1319,7 +1332,7 @@ $Script:SupportedConfigurationVersion = "2.4" $OutputOptions.OutputSentinelAPI = $Script:InstanceConfiguration.OutputName } } - else { $OutputOptions.OutputFileName = $script:outputpath } + else { $OutputOptions.OutputFileName = $script:CSVOutputFile } } else { $OutputOptions.OutputFileName = $OutputName @@ -1347,7 +1360,15 @@ $Script:SupportedConfigurationVersion = "2.4" if ($Script:SentinelLogCollector.ActivateLogUpdloadToSentinel) { - Start-LogsInjestion -OutputName $OutputName -PartialData:$PartialData -PartialDataName $PartialDataName -PartialDataRawFormat:$PartialDataRawFormat -RawData $RawData + if ($Script:SentinelLogCollector.UseForwarder) + { + Write-LogMessage -Message "Forwarder mode activated, logs will written to target directory for forwarder pickup" + SaveLogs-To-ForwarderPickup -OutputName $OutputName -PartialData:$PartialData -PartialDataName $PartialDataName -PartialDataRawFormat:$PartialDataRawFormat -RawData $RawData -TargetPath $Script:SentinelLogCollector.ForwarderPickupPath + } + else { + Write-LogMessage -Message "Logs will be sent to Sentinel during the script execution" + Start-LogsInjestion -OutputName $OutputName -PartialData:$PartialData -PartialDataName $PartialDataName -PartialDataRawFormat:$PartialDataRawFormat -RawData $RawData + } } if (-not $Script:SentinelLogCollector.ActivateLogUpdloadToSentinel -or $Script:SentinelLogCollector.TogetherMode) @@ -1356,6 +1377,69 @@ $Script:SupportedConfigurationVersion = "2.4" } } + function Send-SentinelSegment { + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [array]$SegmentData, + + [Parameter(Mandatory=$true)] + [string]$LogType, + + [Parameter(Mandatory=$true)] + [double]$MaxSize, + + [Parameter(Mandatory=$false)] + [string]$SegmentLabel = "segment" + ) + + # always be sure that $segmentData is an array, even if it contains only one element + if ($SegmentData -isnot [array]) { + $SegmentData = @($SegmentData) + } + + $segmentJson = $SegmentData | ConvertTo-Json -Compress -Depth 10 + $segmentLength = [System.Text.Encoding]::UTF8.GetBytes($segmentJson).Length + + if ($segmentLength -gt $MaxSize) { + if ($SegmentData.Count -lt 2) { + Write-LogMessage "$SegmentLabel is larger than max size ($([Math]::Round($segmentLength/1MB, 2)) MB > $([Math]::Round($MaxSize/1MB, 2)) MB) and cannot be split further" -Level Error + return $false + } + + Write-LogMessage "$SegmentLabel is larger than max size ($([Math]::Round($segmentLength/1MB, 2)) MB). Retrying in 2 parts" -Level Warning + + $mid = [math]::Floor($SegmentData.Count / 2) + + if ($mid -lt 1) { + Write-LogMessage "Unable to split $SegmentLabel into 2 valid parts" -Level Error + return $false + } + + $firstHalf = @($SegmentData[0..($mid - 1)]) + $secondHalf = @($SegmentData[$mid..($SegmentData.Count - 1)]) + + $firstResult = Send-SentinelSegment -SegmentData $firstHalf -LogType $LogType -MaxSize $MaxSize -SegmentLabel "$SegmentLabel part 1/2" + $secondResult = Send-SentinelSegment -SegmentData $secondHalf -LogType $LogType -MaxSize $MaxSize -SegmentLabel "$SegmentLabel part 2/2" + + return ($firstResult -and $secondResult) + } + + Write-LogMessage "Sending $SegmentLabel ($([Math]::Round($segmentLength/1MB, 2)) MB) to Sentinel API $LogType" + + if ($Script:SentinelLogCollector.SentinelLogIngestionAPIActivated) { + return (Post-LogMonitorData -body $segmentJson -logType $LogType) + } + else { + $segmentBytes = [System.Text.Encoding]::UTF8.GetBytes($segmentJson) + return (Post-LogAnalyticsData ` + -customerId $Script:SentinelLogCollector.WorkspaceId ` + -sharedKey $Script:SentinelLogCollector.WorkspaceKey ` + -body $segmentBytes ` + -logType $LogType) + } + } + function Start-LogsInjestion { Param( @@ -1393,68 +1477,72 @@ $Script:SupportedConfigurationVersion = "2.4" throw("Input data cannot be converted into a JSON object. Please make sure that the input data is a standard PowerShell table") } - if ([String]::IsNullOrEmpty($OutputSentinelAPI)) {$OutputSentinelAPI = $Script:SentinelLogCollector.LogTypeName} + if ([String]::IsNullOrEmpty($OutputSentinelAPI)) + { + $OutputSentinelAPI = $Script:SentinelLogCollector.LogTypeName + } if ($Script:InstanceConfiguration.FileFilterType -match "Categorize" -and [String]::IsNullOrEmpty($Script:InstanceConfiguration.OutputName)) { $OutputSentinelAPI = $OutputSentinelAPI -replace 'ESI', "ESI-$($Script:InstanceConfiguration.Category)-" } + $maxSize = $Script:MaximalSentinelPacketSizeMb *1024*1024 + $ResultLength = [System.Text.Encoding]::UTF8.GetBytes($ResultInjsonFormat).Length - $contentDivision = [math]::Ceiling($ResultLength / ($Script:MaximalSentinelPacketSizeMb *1024*1024)) + $contentDivision = [math]::Ceiling($ResultLength / $maxSize) if ($contentDivision -le 1) { - Write-LogMessage -Message ("Upload payload size is less than $($Script:MaximalSentinelPacketSizeMb)Mb. It will be sent in 1 segment") - # Submit the data to the API endpoint - Post-LogAnalyticsData -customerId $Script:SentinelLogCollector.WorkspaceId ` - -sharedKey $Script:SentinelLogCollector.WorkspaceKey ` - -body ([System.Text.Encoding]::UTF8.GetBytes($ResultInjsonFormat)) ` - -logType $OutputSentinelAPI + Write-LogMessage -Message ("Upload payload size is less than $($Script:MaximalSentinelPacketSizeMb)Mb. It will be sent in 1 segment to $OutputSentinelAPI") + + try { + $result = Send-SentinelSegment -SegmentData $ResultInjsonFormat -LogType $OutputSentinelAPI -MaxSize $maxSize -SegmentLabel "segment 1/1" + + if (-not $result) { + Write-LogMessage -Message "Failed to send logs to Sentinel" -Level Error + } + } + catch + { + Write-LogMessage -Message ("Error sending to Sentinel. $_") -Level Error + } } else { - Write-LogMessage -Message ("Upload payload size is " + ($ResultLength/1024/1024).ToString("#.#") + "Mb, greater than $($Script:MaximalSentinelPacketSizeMb)Mb. It will be sent in $contentDivision segments") + Write-LogMessage -Message ("Upload payload size is " + ($ResultLength/1024/1024).ToString("#.#") + "Mb, greater than $($Script:MaximalSentinelPacketSizeMb)Mb. It will be sent in $contentDivision segments to $OutputSentinelAPI") $maxCount = [math]::Floor($ProcessedData.Count / $contentDivision) + $currentIndex = 0 - $maxSegmentCount = $maxCount - $CounterStart = 0 - $exitNextTime = $false - while ($exitNextTime -eq $false) - { - if ($maxSegmentCount -ge $ProcessedData.Count) - { - $maxSegmentCount = $ProcessedData.Count - $exitNextTime = $true + $allSucceeded = $true + + for ($i = 0; $i -lt $contentDivision; $i++) { + $endIndex = [math]::Min($currentIndex + $maxCount, $ProcessedData.Count) + + if ($i -eq ($contentDivision - 1)) { + $endIndex = $ProcessedData.Count } - Write-LogMessage -Message ("Sending Segment $CounterStart to $maxSegmentCount") - - $TempTable = @() - for ($Counter = $CounterStart; $Counter -lt $maxSegmentCount; $Counter++) + $segment = $ProcessedData[$currentIndex..($endIndex - 1)] + + Write-LogMessage "Sending segment $($i + 1)/$contentDivision (items $currentIndex to $($endIndex - 1)) for $OutputSentinelAPI" + try { - $TempTable += $ProcessedData[$Counter] - } - - $CounterStart = $maxSegmentCount - $maxSegmentCount += $maxCount - - $ResultInjsonFormat = $TempTable | ConvertTo-Json -Compress - - Write-LogMessage -Message ("Sending payload : $ResultInjsonFormat") - - try { - # Submit the data to the API endpoint - Post-LogAnalyticsData -customerId $Script:SentinelLogCollector.WorkspaceId ` - -sharedKey $Script:SentinelLogCollector.WorkspaceKey ` - -body ([System.Text.Encoding]::UTF8.GetBytes($ResultInjsonFormat)) ` - -logType $OutputSentinelAPI + $result = Send-SentinelSegment -SegmentData $segment -LogType $OutputSentinelAPI -MaxSize $maxSize -SegmentLabel "segment $($i + 1)/$contentDivision" + + if (-not $result) { + $allSucceeded = $false + Write-LogMessage "Failed to send segment $($i + 1) for $OutputSentinelAPI" -Level Error + } } - catch { - Write-LogMessage -Message ("Error sending to Sentinel. $_") -Level Error + catch + { + $allSucceeded = $false + Write-LogMessage ("Error sending segment $($i + 1) for $OutputSentinelAPI. $_") -Level Error } + $currentIndex = $endIndex } } } @@ -1500,7 +1588,7 @@ $Script:SupportedConfigurationVersion = "2.4" } } - $outputdirectorypath = Split-Path $script:outputpath + $outputdirectorypath = Split-Path $script:CSVOutputFile # If $OutputFileName does not end with .csv, add it if ($OutputFileName -notmatch ".csv$") @@ -1516,6 +1604,112 @@ $Script:SupportedConfigurationVersion = "2.4" $ProcessedData | Export-Csv -Path $OutputFileName -NoTypeInformation } + function SaveLogs-To-ForwarderPickup + { + Param( + $OutputName, + [switch] $PartialData, + $PartialDataName, + [switch] $PartialDataRawFormat, + $RawData, + [Parameter(Mandatory=$true)] + [string] $TargetPath + ) + + Write-LogMessage -Message "Forwarder Pickup - Saving logs to forwarder directory" + + # Validate and create target directory if needed + if (-not (Test-Path $TargetPath)) + { + try { + Write-LogMessage -Message "Creating forwarder pickup directory: $TargetPath" + New-Item -ItemType Directory -Path $TargetPath -Force | Out-Null + } + catch { + Write-LogMessage -Message "Failed to create forwarder pickup directory: $($_.Exception.Message)" -Level Error + throw "Unable to create forwarder pickup directory at $TargetPath" + } + } + + # Determine which data to process + if ($PartialDataRawFormat) + { + $ProcessedData = $RawData + } + elseif ($PartialData) + { + $ProcessedData = $script:Results[$OutputName][$PartialDataName] + } + else { + $ProcessedData = $script:Results[$OutputName] + } + + # Get output options and prepare filename + $OutputOptions = Get-OutputOptions -OutputName $OutputName + if ($OutputName -eq "Default") + { + $OutputOptions.OutputFileName = $OutputOptions.LogTypeName + } + else { $OutputFileName = $OutputOptions.OutputFileName } + + # If OutputFileName is a full/relative path, keep only the filename portion + # (the forwarder pickup directory is provided via $TargetPath and joined below) + if (-not [String]::IsNullOrEmpty($OutputFileName) -and ($OutputFileName -match '[\\/]')) + { + $OutputFileName = Split-Path -Path $OutputFileName -Leaf + } + + # Handle partial data naming + if ($PartialData -or $PartialDataRawFormat) + { + if ([String]::IsNullOrEmpty($PartialDataName)) + { + $Guid = [guid]::NewGuid().ToString() + $OutputFileName = $OutputFileName -replace ".csv|.json", "-$Guid.json" + } + else + { + $OutputFileName = $OutputFileName -replace ".csv|.json", "-$PartialDataName.json" + } + } + + # Ensure .json extension for forwarder pickup + if ($OutputFileName -notmatch ".json$") + { + $OutputFileName = $OutputFileName -replace ".csv$", ".json" + if ($OutputFileName -notmatch ".json$") + { + $OutputFileName = $OutputFileName + ".json" + } + } + + # Add timestamp if not present + if ((-not $ForceOutputWithoutDate -or $null -eq $ForceOutputWithoutDate) -and $OutputFileName -notmatch "-$DateSuffixForFile.json") + { + $OutputFileName = $OutputFileName -replace ".json", "-$DateSuffixForFile.json" + } + + # Build full path + $FullOutputPath = Join-Path -Path $TargetPath -ChildPath $OutputFileName + + try { + # Convert to JSON and save + Write-LogMessage -Message "Writing forwarder pickup file: $FullOutputPath" + + # For forwarder, we typically want an array of objects + $JsonOutput = $ProcessedData | ConvertTo-Json -Depth 10 -Compress + + # Write to file + $JsonOutput | Set-ESIContent -Path $FullOutputPath + + Write-LogMessage -Message "Successfully saved forwarder pickup file: $FullOutputPath (Size: $([Math]::Round(($JsonOutput.Length/1KB), 2)) KB)" + } + catch { + Write-LogMessage -Message "Failed to save forwarder pickup file: $($_.Exception.Message)" -Level Error + throw "Unable to save forwarder pickup file: $($_.Exception.Message)" + } + } + Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource) { $xHeaders = "x-ms-date:" + $date @@ -1563,7 +1757,7 @@ $Script:SupportedConfigurationVersion = "2.4" throw("Upload payload is too big and exceed the 32Mb limit for a single upload. Please reduce the payload size. Current payload size is: " + ($body.Length/1024/1024).ToString("#.#") + "Mb") } - Write-LogMessage -Message ("Upload payload size is " + ($body.Length/1024).ToString("#.#") + "Kb") + Write-LogMessage -Message ("Analytics API - Upload payload size is " + ($body.Length/1024).ToString("#.#") + "Kb - $logType") try { if ($Useproxy) @@ -1589,6 +1783,73 @@ $Script:SupportedConfigurationVersion = "2.4" else { throw ("Server returned an error response code:" + $response.StatusCode)} } + + Function Post-LogMonitorData + { + Param( + [string]$body, + [string]$logType + ) + + $method = "POST" + + $DceUri = $Script:SentinelLogCollector.DataCollectionEndpointURI + $DCRImmutableID = $Script:SentinelLogCollector.DCRImmutableId + $streamName = $logType + + # if StreamName doesn't begin with 'Custom-', add it + if (-not $streamName.StartsWith("Custom-")) { + $streamName = "Custom-$streamName" + } + + if ($null -eq $Script:SentinelLogIngestionToken) + { + Connect-LogIngestionAPI + } + + $bearerToken = $Script:SentinelLogIngestionToken.AccessToken + + $uri = "$DceUri/dataCollectionRules/$DCRImmutableID/streams/$($streamName)?api-version=2023-01-01" + + $headers = @{ + "Authorization" = "Bearer $bearerToken"; + "Content-Type"="application/json" + } + + #validate that payload data does not exceed limits + if ($body.Length -gt (0.9 *1024*1024)) + { + throw("Upload payload is too big and exceed the 1Mb limit for a single upload. Please reduce the payload size. Current payload size is: " + ($body.Length/1024/1024).ToString("#.#") + "Mb") + } + + Write-LogMessage -Message ("Upload payload size is " + ($body.Length/1024).ToString("#.#") + "Kb - Uri : $uri") + #Write-LogMessage -Message ("Body: $body") + + try { + if ($Useproxy) + { + $response = Invoke-WebRequest -Uri $uri -Method $method -Headers $headers -Body $body -UseBasicParsing -Proxy $Script:ProxyUrl + } + else { + $response = Invoke-WebRequest -Uri $uri -Method $method -Headers $headers -Body $body -UseBasicParsing + } + } + catch { + if ($_.Exception.Message.startswith('The remote name could not be resolved')) + { + throw ("Error - data could not be uploaded. Might be because workspace ID or private key are incorrect") + } + + throw ("Error - data could not be uploaded: " + $_.Exception.Message) + } + + # Present message according to the response code + if ($response.StatusCode -eq 200 -or $response.StatusCode -eq 204) + { Write-LogMessage "200 - Data was successfully uploaded" } + else + { throw ("Server returned an error response code:" + $response.StatusCode)} + } + #endregion Sentinel Upload Management #region Dynamic Cmdlet Management @@ -1673,6 +1934,25 @@ $Script:SupportedConfigurationVersion = "2.4" return $Object } + Function New-ExchangeSimulationInjection + { + Param( + [Parameter(Mandatory=$True)] [String] $InjectionInfo + ) + + + $Script:_InternalInjection = $true + $Script:Results["Default"] += New-Result -Section "InjectionMode" -PSCmdL "InjectionMode" -EntryDate $Script:DateSuffix -ScriptInstanceID $Script:ScriptInstanceID + + if ($InjectionInfo -match "Internal") + { + $Script:_InternalExchangeInformation = $Global:InjectionTest | ConvertFrom-Json + } + else { + $Script:_InternalExchangeInformation = Get-Content -Path $InjectionInfo | ConvertFrom-Json + } + } + #Function to construct the output file which depend on the section currently processing Function GetCmdletExec { @@ -1889,6 +2169,8 @@ $Script:SupportedConfigurationVersion = "2.4" if (-not [String]::IsNullOrEmpty($ServerProcessed)) { $Object | Add-Member Noteproperty -Name ProcessedByServer -value $ServerProcessed + }else { + $Object | Add-Member Noteproperty -Name ProcessedByServer -value $null } if ($EmptyCmdlet) @@ -1935,6 +2217,9 @@ $Script:SupportedConfigurationVersion = "2.4" { $Object | Add-Member Noteproperty -Name ProcessedByServer -value $ServerProcessed } + else { + $Object | Add-Member Noteproperty -Name ProcessedByServer -value $null + } # Compile other Attributes $Object | Add-Member Noteproperty -Name rawData -value ($Entry | ConvertTo-Json -Compress) @@ -2286,11 +2571,19 @@ $Script:SupportedConfigurationVersion = "2.4" Param( $NumberRunspace ) + $Script:RunspaceResults = [hashtable]::Synchronized(@{}) $Script:RunspaceResults.AvailableRunspaces = 0 $JobList = @() + + if ($Script:_InternalInjection) + { + Write-LogMessage -Message ("`tInternal Injection Mode - Runspace Creation - Ignored due to Injection Simulation") -NoOutput -Level Warning + return + } + Write-Host "Launching Runspace creation ..." for ($i = 0; $i -lt $NumberRunspace; $i++) @@ -3069,17 +3362,6 @@ $Script:SupportedConfigurationVersion = "2.4" $Script:GlobalParallelProcess = $false } - if ([string]::IsNullOrEmpty($jsonConfig.Output.DefaultOutputFile) -and -not $Global:isRunbook) {throw "No Output file in config, mandatory"} else {$Script:outputpath = $jsonConfig.Output.DefaultOutputFile} - if (-not [string]::IsNullOrEmpty($jsonConfig.Output.ExportDomainsInformation)) - { - $Script:ExportDomainsInformation = [Convert]::ToBoolean($jsonConfig.Output.ExportDomainsInformation) - } - else - { - $Script:ExportDomainsInformation = $false - } - - [int] $Script:ParralelWaitRunning if ($null -eq $jsonConfig.Advanced.ParralelWaitRunning) {[int] $Script:ParralelWaitRunning = 60} else {[int] $Script:ParralelWaitRunning = $jsonConfig.Advanced.ParralelWaitRunning} if ($null -eq $jsonConfig.Advanced.ParralelPingWaitRunning) {[int] $Script:ParralelPingWaitRunning = $Script:ParralelWaitRunning} else {[int] $Script:ParralelPingWaitRunning = $jsonConfig.Advanced.ParralelPingWaitRunning} @@ -3147,23 +3429,33 @@ $Script:SupportedConfigurationVersion = "2.4" } else { $Script:UpdateVersionCheckingDeactivated = $false } - - - if ($null -eq $jsonConfig.Advanced.MaximalSentinelPacketSizeMb) { - $script:MaximalSentinelPacketSizeMb = 31.9 - } - else { - if ($jsonConfig.Advanced.MaximalSentinelPacketSizeMb -ge 32) - { - Write-LogMessage -Message "Packet size $($jsonConfig.Advanced.MaximalSentinelPacketSizeMb) greater than 31.9Kb. Maximum size for Sentinel is 31.9Kb." - $script:MaximalSentinelPacketSizeMb = 31.9 + if (-not [string]::IsNullOrEmpty($jsonConfig.Advanced.ExplicitESIDataPath)) + { + $Script:ESIDataPath = $jsonConfig.Advanced.ExplicitESIDataPath + if (-not (Test-Path $Script:ESIDataPath)) + { + try { + New-Item -Path $Script:ESIDataPath -ItemType Directory -ErrorAction Stop + } + catch { + throw "Path $($Script:ESIDataPath) not found and impossible to create" + } } - else - { - [int] $Script:MaximalSentinelPacketSizeMb = $jsonConfig.Advanced.MaximalSentinelPacketSizeMb - 0.1 + } + else + { + $Script:ESIDataPath = $Script:scriptFolder + "\Data" + try { + if (-not (Test-Path $Script:ESIDataPath)) { New-Item -Path $Script:ESIDataPath -ItemType Directory -ErrorAction Stop } + } + catch { + throw "Path $($Script:ESIDataPath) not found and impossible to create" } } + + + if ($null -eq $jsonConfig.Advanced.PaginationErrorThreshold) { $script:PaginationErrorThreshold = 10 } @@ -3243,6 +3535,51 @@ $Script:SupportedConfigurationVersion = "2.4" $Script:MGGraphAzureRMAppId = "Unknown" } + if ($null -ne $jsonConfig.InternetAddonCollectionConfiguration) { + $Script:InternetAddonCollectionConfiguration = New-Object PSObject + + # Load properties UseGithubAPI, GithubRawUrlforOnPremises, GithubRawUrlforOnline, GithubAPIToken, GithubAPIConnectionType + if (-not [String]::IsNullOrEmpty($jsonConfig.InternetAddonCollectionConfiguration.UseGithubAPI)) { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name UseGithubAPI -value ([Convert]::ToBoolean($jsonConfig.InternetAddonCollectionConfiguration.UseGithubAPI)) + } + else { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name UseGithubAPI -value $false + } + if (-not [String]::IsNullOrEmpty($jsonConfig.InternetAddonCollectionConfiguration.GithubRawUrlforOnPremises)) { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnPremises -value $jsonConfig.InternetAddonCollectionConfiguration.GithubRawUrlforOnPremises + } + else { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnPremises -value "https://raw.githubusercontent.com/ESICollector/ESICollector-Addons/master" + } + if (-not [String]::IsNullOrEmpty($jsonConfig.InternetAddonCollectionConfiguration.GithubRawUrlforOnline)) { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnline -value $jsonConfig.InternetAddonCollectionConfiguration.GithubRawUrlforOnline + } + else { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnline -value "https://raw.githubusercontent.com/ESICollector/ESICollector-Addons/master" + } + if (-not [String]::IsNullOrEmpty($jsonConfig.InternetAddonCollectionConfiguration.GithubAPIToken)) { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIToken -value $jsonConfig.InternetAddonCollectionConfiguration.GithubAPIToken + } + else { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIToken -value "" + } + if (-not [String]::IsNullOrEmpty($jsonConfig.InternetAddonCollectionConfiguration.GithubAPIConnectionType)) { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIConnectionType -value $jsonConfig.InternetAddonCollectionConfiguration.GithubAPIConnectionType + } + else { + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIConnectionType -value "NoAuth" + } + } + else { + $Script:InternetAddonCollectionConfiguration = New-Object PSObject + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name UseGithubAPI -value $false + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnPremises -value "https://raw.githubusercontent.com/ESICollector/ESICollector-Addons/master" + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubRawUrlforOnline -value "https://raw.githubusercontent.com/ESICollector/ESICollector-Addons/master" + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIToken -value "" + $Script:InternetAddonCollectionConfiguration | Add-Member Noteproperty -Name GithubAPIConnectionType -value "NoAuth" + + } + if ($null -ne $jsonConfig.InstanceConfiguration) { if ($null -ne $jsonConfig.InstanceConfiguration.$InstanceName) { @@ -3338,14 +3675,113 @@ $Script:SupportedConfigurationVersion = "2.4" $Script:SentinelLogCollector | Add-Member Noteproperty -Name WorkspaceKey -value $jsonConfig.LogCollection.WorkspaceKey $Script:SentinelLogCollector | Add-Member Noteproperty -Name LogTypeName -value $jsonConfig.LogCollection.LogTypeName $Script:SentinelLogCollector | Add-Member Noteproperty -Name TogetherMode -value ([Convert]::ToBoolean($jsonConfig.LogCollection.TogetherMode)) + $Script:SentinelLogCollector | Add-Member Noteproperty -Name SentinelLogIngestionAPIActivated -value $false + $Script:SentinelLogCollector | Add-Member Noteproperty -Name DataCollectionEndpointURI -value $jsonConfig.LogCollection.DataCollectionEndpointURI + $Script:SentinelLogCollector | Add-Member Noteproperty -Name DCRImmutableId -value $jsonConfig.LogCollection.DCRImmutableId + $Script:SentinelLogCollector | Add-Member Noteproperty -Name TargetLogTenantID -value $jsonConfig.LogCollection.TargetLogTenantID + $Script:SentinelLogCollector | Add-Member Noteproperty -Name TargetLogAppID -value $jsonConfig.LogCollection.TargetLogAppID + $Script:SentinelLogCollector | Add-Member Noteproperty -Name TargetLogCertificateThumbprint -value $jsonConfig.LogCollection.TargetLogCertificateThumbprint + $Script:SentinelLogCollector | Add-Member Noteproperty -Name UseManagedIdentity -value ([Convert]::ToBoolean($jsonConfig.LogCollection.UseManagedIdentity)) + $Script:SentinelLogCollector | Add-Member Noteproperty -Name TargetLogAppSecretReference -value $jsonConfig.LogCollection.TargetLogAppSecretReference + $script:SentinelLogCollector | Add-Member Noteproperty -Name UseForwarder -value ([Convert]::ToBoolean($jsonConfig.LogCollection.UseForwarder)) + $Script:SentinelLogCollector | Add-Member Noteproperty -Name ForwarderPickupPath -value $jsonConfig.LogCollection.ForwarderPickupPath if ($Script:SentinelLogCollector.ActivateLogUpdloadToSentinel) { - if ([string]::IsNullOrEmpty($Script:SentinelLogCollector.WorkspaceId) -or - [string]::IsNullOrEmpty($Script:SentinelLogCollector.WorkspaceKey) -or - [string]::IsNullOrEmpty($Script:SentinelLogCollector.LogTypeName)) + if ([string]::IsNullOrEmpty($Script:SentinelLogCollector.LogTypeName)) + { + throw "Sentinel Log Collector configuration is activated and LogTypeName is missing" + } + } + + if (-not [string]::IsNullOrEmpty($jsonConfig.LogCollection.SentinelLogIngestionAPIActivated)) + { + $Script:SentinelLogCollector.SentinelLogIngestionAPIActivated = [Convert]::ToBoolean($jsonConfig.LogCollection.SentinelLogIngestionAPIActivated) + } + else { $Script:SentinelLogCollector.SentinelLogIngestionAPIActivated = $false } + + + if ($Script:SentinelLogCollector.SentinelLogIngestionAPIActivated) + { + + #throw "Sentinel Log Collector configuration with new Log Ingestion API is currently not supported." + + if ([string]::IsNullOrEmpty($Script:SentinelLogCollector.DataCollectionEndpointURI) -or + [string]::IsNullOrEmpty($Script:SentinelLogCollector.DCRImmutableId) -or + $null -eq $Script:SentinelLogCollector.UseManagedIdentity -or ( + $Script:SentinelLogCollector.UseManagedIdentity -eq $false -and ( + [string]::IsNullOrEmpty($Script:SentinelLogCollector.TargetLogTenantID) -or + [string]::IsNullOrEmpty($Script:SentinelLogCollector.TargetLogAppID) -or + [string]::IsNullOrEmpty($Script:SentinelLogCollector.TargetLogCertificateThumbprint) + ) + )) { - throw "Sentinel Log Collector configuration is activated and contains wrong values." + throw "Sentinel Log Collector configuration with new Log Ingestion API Endpoint is activated but DCE URI or DCR Immutable Id or Authentication information are missing." + } + + if ($script:IsRunbook -and -not $Script:SentinelLogCollector.UseManagedIdentity) + { + try { + Get-AutomationVariable -Name $Script:SentinelLogCollector.TargetLogAppSecretReference -ErrorAction Stop | Out-Null + } + catch { + throw "Sentinel Log Collector configuration with new Log Ingestion API Endpoint is activated but Secret information is missing. Exception : $($_.Exception.Message)" + } + } + + } + else + { + if ($Script:SentinelLogCollector.ActivateLogUpdloadToSentinel) + { + if ([string]::IsNullOrEmpty($Script:SentinelLogCollector.WorkspaceId) -or + [string]::IsNullOrEmpty($Script:SentinelLogCollector.WorkspaceKey)) + { + throw "Sentinel Log Collector configuration is activated and contains wrong values." + } + + # ------------------------------------------------------------------ + # Legacy Log Analytics HTTP Data Collector API deprecation warning + # ------------------------------------------------------------------ + # Displayed on every execution while the collector still targets the + # legacy Log Analytics API. Clears once SentinelLogIngestionAPIActivated + # is set to true and the DCE/DCR configuration is provided. + # See migration guide: + # https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "================================================================================" + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "!! LEGACY LOG ANALYTICS HTTP DATA COLLECTOR API IS STILL IN USE" + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "!! This API is deprecated by Microsoft and will be retired." + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "!! Migrate to the Azure Monitor Log Ingestion API BEFORE end of support." + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "!! Set 'SentinelLogIngestionAPIActivated' to 'true' after deploying DCE/DCR." + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "!! Migration guide: https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI" + Write-LogMessage -Level Warning -Category "APIDeprecation" -Message "================================================================================" + } + } + + + if ($null -eq $jsonConfig.Advanced.MaximalSentinelPacketSizeMb) { + if ($Script:SentinelLogCollector.SentinelLogIngestionAPIActivated) { + Write-LogMessage -Message "Sentinel Log Ingestion API activated. Maximum size for Sentinel Log Ingestion is 1Mb." + $script:MaximalSentinelPacketSizeMb = 0.9 + } + else { + $script:MaximalSentinelPacketSizeMb = 31.9 + } + } + else { + if ($Script:SentinelLogCollector.SentinelLogIngestionAPIActivated -and $jsonConfig.Advanced.MaximalSentinelPacketSizeMb -ge 1) + { + Write-LogMessage -Message "Packet size $($jsonConfig.Advanced.MaximalSentinelPacketSizeMb) greater than 1Mb. Maximum size for Sentinel is 1 Mb." + $script:MaximalSentinelPacketSizeMb = 0.9 + } + elseif ($jsonConfig.Advanced.MaximalSentinelPacketSizeMb -ge 32) + { + Write-LogMessage -Message "Packet size $($jsonConfig.Advanced.MaximalSentinelPacketSizeMb) greater than 31.9Mb. Maximum size for Sentinel is 31.9Mb." + $script:MaximalSentinelPacketSizeMb = 31.9 + } + else + { + [int] $Script:MaximalSentinelPacketSizeMb = $jsonConfig.Advanced.MaximalSentinelPacketSizeMb - 0.1 } } @@ -3357,6 +3793,29 @@ $Script:SupportedConfigurationVersion = "2.4" { $Script:SentinelLogCollector.LogTypeName -replace "ESIExchangeConfig", "ESIExchangeOnlineConfig" } + + if ([string]::IsNullOrEmpty($jsonConfig.LogCollection.CSVOutputFile) ) {$Script:CSVOutputFile = "ExchSecIns.csv"} else + { + $Script:CSVOutputFile = $jsonConfig.LogCollection.CSVOutputFile + } + + # if CSVOutputFile is not a full path, add the default path + if (-not $Script:CSVOutputFile.Contains(":")) { $Script:CSVOutputFile = $Script:scriptFolder + "\" + $Script:CSVOutputFile } + + # check if the output path is valid and create it if needed + if (-not $Script:SentinelLogCollector.ActivateLogUpdloadToSentinel -or $Script:SentinelLogCollector.TogetherMode) + { + if (-not (Test-Path (Split-Path $Script:CSVOutputFile))) { New-Item -Path (Split-Path $Script:CSVOutputFile) -ItemType Directory -ErrorAction Stop } + } + + if (-not [string]::IsNullOrEmpty($jsonConfig.LogCollection.ExportDomainsInformation)) + { + $Script:ExportDomainsInformation = [Convert]::ToBoolean($jsonConfig.LogCollection.ExportDomainsInformation) + } + else + { + $Script:ExportDomainsInformation = $true + } } } @@ -3367,6 +3826,100 @@ $Script:SupportedConfigurationVersion = "2.4" } } + + function InvokeGithubGetFileContent + { + Param ( + $GithubSourcePath, + $FileName, + [switch] $Raw + ) + + if (-not $Script:InternetAddonCollectionConfiguration.UseGithubAPI) + { + Write-LogMessage -Message "UseGithubAPI is set to False, no Github API call will be made, Raw Content will be used" -NoOutput -Level Warning + + $uri = "$GithubSourcePath/$($FileName)" + + try { + if ($Useproxy) + { + $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing -Proxy $Script:ProxyUrl + } + else { + $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing + } + } + catch { + Write-LogMessage -Message "Impossible to retrieve file $($FileName) from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; + Throw "Impossible to load Audit Functions, Critical for collection. Error :" + $_ + } + + if ($Raw) + { + return $WebResult.Content + } + else + { + return $WebResult.Content | ConvertFrom-Json + } + } + else + { + # Use Github API to retrieve file content + $uri = "$GithubSourcePath/$($FileName)" + $headers = @{} + + $headers.Add("Accept", "application/vnd.github.raw+json") + $headers.Add("X-GitHub-Api-Version","2022-11-28") + + if (-not [string]::IsNullOrEmpty($Script:InternetAddonCollectionConfiguration.GithubAPIToken) -and + $Script:InternetAddonCollectionConfiguration.GithubAPIConnectionType -like "Bearer") + { + $headers.Add("Authorization", "Bearer $($Script:InternetAddonCollectionConfiguration.GithubAPIToken)") + } + + try { + if ($Useproxy) + { + $WebResult = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -UseBasicParsing -Proxy $Script:ProxyUrl + } + else { + $WebResult = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -UseBasicParsing + } + } + catch { + Write-LogMessage -Message "Impossible to retrieve file $($FileName) from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; + Throw "Impossible to load Audit Functions, Critical for collection. Error :" + $_ + } + + if ($null -eq $WebResult -or $null -eq $WebResult.Content) + { + Write-LogMessage -Message "Impossible to retrieve file $($FileName) from Online Github. Content is null" -NoOutput -Level Warning; + Throw "Impossible to load Audit Functions, Critical for collection. Error : Content is null" + } + + $Content = $WebResult.Content + + if ($WebResult.Encoding -eq "base64") + { + # Content is base64 encoded, decode it + $Content = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Contentt)) + } + + if ($Raw) + { + return $Content + } + else + { + return $Content | ConvertFrom-Json + } + } + + + } + function LoadAuditFunctionsFromInternetRepository { Param ( @@ -3381,17 +3934,18 @@ $Script:SupportedConfigurationVersion = "2.4" # Verify the cache directory exists $scriptFolder = $Script:scriptFolder $ScriptAddonCachePath = $scriptFolder + '\Config\Add-Ons\OnlineCache\' - $GithubSourcePath = "https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/ESICollector-Addons" + $GithubSourcePath = $Script:InternetAddonCollectionConfiguration.GithubRawUrlforOnPremises + $GithubSourceRelativePath = ""; if ($Beta) { - $GithubSourcePath += "/Beta" + $GithubSourceRelativePath += "/Beta" } if ($Script:InstanceConfiguration.FileFilterType -match "Categorize") { $ScriptAddonCachePath += "Categories\$($Script:InstanceConfiguration.Category)\" - $GithubSourcePath += "/Categories/$($Script:InstanceConfiguration.Category)/" + $GithubSourceRelativePath += "/Categories/$($Script:InstanceConfiguration.Category)/" } Push-Location ($scriptFolder); @@ -3440,14 +3994,8 @@ $Script:SupportedConfigurationVersion = "2.4" # Retrieve File Checksum list try { - if ($Useproxy) - { - $WebResult = invoke-WebRequest -Uri "$GithubSourcePath/ESIChecksumFiles.json" -UseBasicParsing -Proxy $Script:ProxyUrl - } - else - { - $WebResult = invoke-WebRequest -Uri "$GithubSourcePath/ESIChecksumFiles.json" -UseBasicParsing - } + $GithubSourcePathFileName = $GithubSourceRelativePath + "ESIChecksumFiles.json" + $WebResult = InvokeGithubGetFileContent -GithubSourcePath $GithubSourcePath -FileName $GithubSourcePathFileName -Raw } catch { Write-LogMessage -Message "Impossible to retrieve files from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; @@ -3465,7 +4013,7 @@ $Script:SupportedConfigurationVersion = "2.4" # If not empty, check each file from checksum list if (-not $CacheEmpty) { - $localHash = Get-FileHash -Path $ScriptAddonCachePath + "ESIChecksumFiles.json" -Algorithm SHA256 + $localHash = Get-FileHash -Path ($ScriptAddonCachePath + "ESIChecksumFiles.json") -Algorithm SHA256 $stringAsStream = [System.IO.MemoryStream]::new() $writer = [System.IO.StreamWriter]::new($stringAsStream) @@ -3491,16 +4039,10 @@ $Script:SupportedConfigurationVersion = "2.4" # Add all file in the list foreach ($OnlineFile in $OnlineFiles.Files) { - $uri = "$GithubSourcePath/$($OnlineFile.FileName)" + $uri = "$GithubSourceRelativePath/$($OnlineFile.FileName)" try { - if ($Useproxy) - { - $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing -Proxy $Script:ProxyUrl - } - else - { - $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing - } + + $WebResult = InvokeGithubGetFileContent -GithubSourcePath $GithubSourcePath -FileName $uri -Raw } catch { Write-LogMessage -Message "Impossible to retrieve file $($OnlineFile.FileName) from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; @@ -3518,6 +4060,10 @@ $Script:SupportedConfigurationVersion = "2.4" } } + # Load Audit Functions from Online Github repository + # This function is used in Runbook mode + # It retrieves the list of Audit Functions from the Online Github repository + function LoadAuditFunctionsForRunBook { Param ( @@ -3527,29 +4073,25 @@ $Script:SupportedConfigurationVersion = "2.4" # Process in Memory without storage # Retrieve File Checksum list - $GithubSourcePath = "https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/ESICollector-Addons" + $GithubSourcePath = $Script:InternetAddonCollectionConfiguration.GithubRawUrlforOnline + $GithubSourceRelativePath = ""; if ($Beta) { - $GithubSourcePath += "/Beta" + $GithubSourceRelativePath += "/Beta" } Write-LogMessage "Filter $($Script:InstanceConfiguration.FileFilterType) - Category $($Script:InstanceConfiguration.Category)" -NoOutput if ($Script:InstanceConfiguration.FileFilterType -match "Categorize") { - $GithubSourcePath += "/Categories/$($Script:InstanceConfiguration.Category)/" + $GithubSourceRelativePath += "/Categories/$($Script:InstanceConfiguration.Category)/" } + + $GithubSourcePathFileName = $GithubSourceRelativePath + "ESIChecksumFiles.json" try { - if ($Useproxy) - { - $WebResult = invoke-WebRequest -Uri "$GithubSourcePath/ESIChecksumFiles.json" -UseBasicParsing -Proxy $Script:ProxyUrl - } - else - { - $WebResult = invoke-WebRequest -Uri "$GithubSourcePath/ESIChecksumFiles.json" -UseBasicParsing - } + $OnlineFiles = InvokeGithubGetFileContent -GithubSourcePath $GithubSourcePath -FileName $GithubSourcePathFileName } catch { Write-LogMessage -Message "Impossible to retrieve files from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; @@ -3557,7 +4099,6 @@ $Script:SupportedConfigurationVersion = "2.4" } # Retrieve all list - $OnlineFiles = $WebResult.Content | ConvertFrom-Json $AuditFunctionList = @() foreach ($OnlineFile in $OnlineFiles.Files) @@ -3593,21 +4134,14 @@ $Script:SupportedConfigurationVersion = "2.4" if ($FileToIgnore) {continue;} - $uri = "$GithubSourcePath/$($OnlineFile.FileName)" + $urifilename = $GithubSourceRelativePath + $OnlineFile.FileName try { - if ($Useproxy) - { - $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing -Proxy $Script:ProxyUrl - } - else { - $WebResult = invoke-WebRequest -Uri $uri -UseBasicParsing - } + $OnlineAuditFunctionsFile = InvokeGithubGetFileContent -GithubSourcePath $GithubSourcePath -FileName $urifilename } catch { Write-LogMessage -Message "Impossible to retrieve file $($OnlineFile.FileName) from Online Github. Error : $($_.Exception)" -NoOutput -Level Warning; Throw "Impossible to load Audit Functions, Critical for collection. Error :" + $_ } - $OnlineAuditFunctionsFile = $WebResult.Content | ConvertFrom-Json Write-LogMessage -Message "Nb Audit Functions found :$($OnlineAuditFunctionsFile.AuditFunctions.count) for $($OnlineFile.FileName)" -NoOutput -Level Info; $AuditFunctionList += $OnlineAuditFunctionsFile.AuditFunctions } @@ -3887,6 +4421,13 @@ if (-not $NoDateTracing) Get-LastLaunchTime } +if ($ExchangeSimulationInjection) +{ + Write-LogMessage -Message "Injection mode activated, no real data will be collected" + + New-ExchangeSimulationInjection -InjectionInfo $SimulationInformation +} + Write-LogMessage -Message "Launching Capability analysis" Set-Capabilities -CapabilitiesList $Script:InstanceConfiguration.Capabilities @@ -3897,16 +4438,6 @@ if ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess) { [System.Collections.ArrayList] $script:RunningProcesses = @() -if (-not $Global:isRunbook) -{ - Write-Host ("Create/Validate Output file path") - if (-not (Test-Path (Split-Path $script:outputpath))) {mkdir (Split-Path $script:outputpath)} - if (-not $ForceOutputWithoutDate -or $null -eq $ForceOutputWithoutDate) - { - $script:outputpath = $script:outputpath -replace ".csv", "-$DateSuffixForFile.csv" - } -} - if (-not $Script:FunctionsListWithoutInternet) { $FunctionList = LoadAuditFunctionsFromInternetRepository -ProcessingType $Script:ESIProcessingType -Beta:$Script:BetaActivated @@ -3915,130 +4446,134 @@ else { $FunctionList = LoadAuditFunctions -AuditFunctionList $Script:JSonAuditFunctionList -ProcessingType $Script:ESIProcessingType -FromAddOnFolder:(-not $Script:FunctionsListInline) } -Write-LogMessage -Message ("Launch Data collection ...") -$inc = 1 - -Write-LogMessage -Message ("Launch Audit Function loop Collection ...") -foreach ($Entry in $FunctionList) +if (-not $Script:_InternalInjection) { - if ([string]::IsNullOrEmpty($Entry.Section)) - { - try { - Write-LogMessage -Message ("`tNo Section found for $($Entry.ToString()) / Collection aborted for this object due to security issue, continue to next object") -NoOutput -Level Error; - } - catch { - Write-LogMessage -Message ("`tNo Section found and Entry format can't be displayed for analysis / Collection aborted for this object due to security issue, continue to next object") -NoOutput -Level Error; - } - $inc++ - continue - } - - Write-LogMessage -Message ("`tLaunch collection $inc on $($FunctionList.count) for $($Entry.Section)") - $PaginationExecution = $false - if ($null -ne $Entry.PaginationInformation -and $Entry.PaginationInformation.PaginationActivated) { + Write-LogMessage -Message ("Launch Data collection ...") + $inc = 1 - Write-LogMessage -Message ("`tPagination information found for $($Entry.Section) $($Entry.PaginationInformation)") + Write-LogMessage -Message ("Launch Audit Function loop Collection ...") + foreach ($Entry in $FunctionList) + { + if ([string]::IsNullOrEmpty($Entry.Section)) + { + try { + Write-LogMessage -Message ("`tNo Section found for $($Entry.ToString()) / Collection aborted for this object due to security issue, continue to next object") -NoOutput -Level Error; + } + catch { + Write-LogMessage -Message ("`tNo Section found and Entry format can't be displayed for analysis / Collection aborted for this object due to security issue, continue to next object") -NoOutput -Level Error; + } + $inc++ + continue + } + + Write-LogMessage -Message ("`tLaunch collection $inc on $($FunctionList.count) for $($Entry.Section)") - $PaginationExecution = $true - $EntryOutStream = $Entry.OutputStream + "_Page" + $PaginationExecution = $false + if ($null -ne $Entry.PaginationInformation -and $Entry.PaginationInformation.PaginationActivated) { - if ($Entry.PaginationInformation.PartialDataUpload) - { - $EntryOutStream = $EntryOutStream + "_SentDuringExecution" - } + Write-LogMessage -Message ("`tPagination information found for $($Entry.Section) $($Entry.PaginationInformation)") - } - else { - Write-LogMessage -Message ("`tNo Pagination information found for $($Entry.Section)") - $EntryOutStream = $Entry.OutputStream - } + $PaginationExecution = $true + $EntryOutStream = $Entry.OutputStream + "_Page" - if ($Entry.DateStorageInformation.DateStorageActivated -and $Entry.DateStorageInformation.DateStorageMode -eq "DateFromAttribute") - { - $SaveDate = $True - $DateStorageAttribute = $Entry.DateStorageInformation.DateAttribute - } - else { - $SaveDate = $False - } + if ($Entry.PaginationInformation.PartialDataUpload) + { + $EntryOutStream = $EntryOutStream + "_SentDuringExecution" + } - if ($EntryOutStream -notin $Script:Results.Keys) { - Write-LogMessage -Message ("`tCreating Output Table for $($EntryOutStream)") - if ($PaginationExecution) { - $Script:Results[$EntryOutStream] = @{} } else { - $Script:Results[$EntryOutStream] = @() + Write-LogMessage -Message ("`tNo Pagination information found for $($Entry.Section)") + $EntryOutStream = $Entry.OutputStream } - } - $EntryCmdlet = $Entry.PSCmdL + if ($Entry.DateStorageInformation.DateStorageActivated -and $Entry.DateStorageInformation.DateStorageMode -eq "DateFromAttribute") + { + $SaveDate = $True + $DateStorageAttribute = $Entry.DateStorageInformation.DateAttribute + } + else { + $SaveDate = $False + } - if ($EntryCmdlet -match "#LastDateOfSection#" -and $Entry.DateStorageInformation.DateStorageActivated) - { - $EntryCmdlet = $EntryCmdlet -replace "#LastDateOfSection#", $Entry.DateStorageInformation.LastDateTracking - } + if ($EntryOutStream -notin $Script:Results.Keys) { + Write-LogMessage -Message ("`tCreating Output Table for $($EntryOutStream)") + if ($PaginationExecution) { + $Script:Results[$EntryOutStream] = @{} + } + else { + $Script:Results[$EntryOutStream] = @() + } + } - if ($Entry.ProcessPerServer) - { - if ($script:CapabilityLoaded -notcontains "OP") { - Write-LogMessage -Message ("`tImpossible to launch a Per Server action without OP capability") -Level Warning - $ErrorMessage = "`tImpossible to launch a Per Server action without OP capability" - - $Script:Results[$EntryOutStream] += New-Result -Section $Entry.Section -PSCmdL $EntryCmdlet -ErrorText $ErrorMessage -EntryDate $Script:DateSuffix -ScriptInstanceID $Script:ScriptInstanceID - continue; + $EntryCmdlet = $Entry.PSCmdL + + if ($EntryCmdlet -match "#LastDateOfSection#" -and $Entry.DateStorageInformation.DateStorageActivated) + { + $EntryCmdlet = $EntryCmdlet -replace "#LastDateOfSection#", $Entry.DateStorageInformation.LastDateTracking } - foreach ($ExchangeServer in $script:ExchangeServerList.ListSRVUp) + if ($Entry.ProcessPerServer) { - if (($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess) -and $Entry.NoRunpace -eq $false -and -not $PaginationExecution) { - processParallel -Entry $Entry -TargetServer $ExchangeServer -EntryCmdlet $EntryCmdlet -EntryOutStream $EntryOutStream -EntrySuboutputPage $EntrySuboutputPage -IsPaginated:$PaginationExecution + if ($script:CapabilityLoaded -notcontains "OP") { + Write-LogMessage -Message ("`tImpossible to launch a Per Server action without OP capability") -Level Warning + $ErrorMessage = "`tImpossible to launch a Per Server action without OP capability" + + $Script:Results[$EntryOutStream] += New-Result -Section $Entry.Section -PSCmdL $EntryCmdlet -ErrorText $ErrorMessage -EntryDate $Script:DateSuffix -ScriptInstanceID $Script:ScriptInstanceID + continue; + } + + foreach ($ExchangeServer in $script:ExchangeServerList.ListSRVUp) + { + if (($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess) -and $Entry.NoRunpace -eq $false -and -not $PaginationExecution) { + processParallel -Entry $Entry -TargetServer $ExchangeServer -EntryCmdlet $EntryCmdlet -EntryOutStream $EntryOutStream -EntrySuboutputPage $EntrySuboutputPage -IsPaginated:$PaginationExecution + } + else { + if ($PaginationExecution -and ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess)) + { + Write-LogMessage -Message ("`tParallel process for pagination not supported, fallback to sequential process for the section $($Entry.Section)") -Level Warning + } + + $Script:Results[$EntryOutStream] += GetCmdletExec -Section $Entry.Section -PSCmdL $EntryCmdlet -Select $Entry.Select -TransformationFunction $Entry.TransformationFunction -TargetServer $ExchangeServer -TransformationForeach:$Entry.TransformationForeach -SaveDate:$SaveDate -SaveDateAttribute $DateStorageAttribute -Entry $Entry -PaginationExecution:$PaginationExecution + } + } + } + else + { + if ($Script:GlobalParallelProcess -and $Entry.NoRunpace -eq $false -and -not $PaginationExecution) { + processParallel -Entry $Entry -EntryCmdlet $EntryCmdlet -EntryOutStream $EntryOutStream -EntrySuboutputPage $EntrySuboutputPage -IsPaginated:$PaginationExecution } else { if ($PaginationExecution -and ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess)) { Write-LogMessage -Message ("`tParallel process for pagination not supported, fallback to sequential process for the section $($Entry.Section)") -Level Warning } - - $Script:Results[$EntryOutStream] += GetCmdletExec -Section $Entry.Section -PSCmdL $EntryCmdlet -Select $Entry.Select -TransformationFunction $Entry.TransformationFunction -TargetServer $ExchangeServer -TransformationForeach:$Entry.TransformationForeach -SaveDate:$SaveDate -SaveDateAttribute $DateStorageAttribute -Entry $Entry -PaginationExecution:$PaginationExecution - } - } - } - else - { - if ($Script:GlobalParallelProcess -and $Entry.NoRunpace -eq $false -and -not $PaginationExecution) { - processParallel -Entry $Entry -EntryCmdlet $EntryCmdlet -EntryOutStream $EntryOutStream -EntrySuboutputPage $EntrySuboutputPage -IsPaginated:$PaginationExecution - } - else { - if ($PaginationExecution -and ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess)) - { - Write-LogMessage -Message ("`tParallel process for pagination not supported, fallback to sequential process for the section $($Entry.Section)") -Level Warning - } - $Script:Results[$EntryOutStream] += GetCmdletExec -Section $Entry.Section -PSCmdL $EntryCmdlet -Select $Entry.Select -TransformationFunction $Entry.TransformationFunction -TransformationForeach:$Entry.TransformationForeach -SaveDate:$SaveDate -SaveDateAttribute $DateStorageAttribute -Entry $Entry -PaginationExecution:$PaginationExecution + $Script:Results[$EntryOutStream] += GetCmdletExec -Section $Entry.Section -PSCmdL $EntryCmdlet -Select $Entry.Select -TransformationFunction $Entry.TransformationFunction -TransformationForeach:$Entry.TransformationForeach -SaveDate:$SaveDate -SaveDateAttribute $DateStorageAttribute -Entry $Entry -PaginationExecution:$PaginationExecution + } } - } - if ($Entry.DateStorageInformation.DateStorageActivated -and -not $SaveDate) - { - switch ($Entry.DateStorageInformation.DateStorageMode) + if ($Entry.DateStorageInformation.DateStorageActivated -and -not $SaveDate) { - "LastDate" { - Set-CurrentLaunchTime -SpecificFunction -FunctionName $Entry.Section -DateSuffix (Get-Date -Format "yyyy-MM-dd HH:mm:ss K") - } - "StartDateScript" { - Set-CurrentLaunchTime -SpecificFunction -FunctionName $Entry.Section -DateSuffix $Script:DateSuffix + switch ($Entry.DateStorageInformation.DateStorageMode) + { + "LastDate" { + Set-CurrentLaunchTime -SpecificFunction -FunctionName $Entry.Section -DateSuffix (Get-Date -Format "yyyy-MM-dd HH:mm:ss K") + } + "StartDateScript" { + Set-CurrentLaunchTime -SpecificFunction -FunctionName $Entry.Section -DateSuffix $Script:DateSuffix + } } } - } - $inc++ -} + $inc++ + } -if ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess) { - WaitAndProcess + if ($Script:ParallelProcessPerServer -or $Script:GlobalParallelProcess) { + WaitAndProcess + } } Write-LogMessage -Message ("Launch CSV Creation / Sentinel Payload uploading ...") diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/OnlineDeployment/README.md b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/OnlineDeployment/README.md index 3bc25e4fd77..1144042211c 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/OnlineDeployment/README.md +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/OnlineDeployment/README.md @@ -41,6 +41,20 @@ **.NOTES** Developed by ksangui@microsoft.com and Nicolas Lepagnez + + + Version : 8.0.0.0 - Released : IN DEV - nilepagn + - Implement Log Ingestion API for Sentinel (DCE/DCR-based ingestion replacing the legacy Log Analytics HTTP Data Collector API). + - New LogCollection settings : SentinelLogIngestionAPIActivated, DataCollectionEndpointURI, DCRImmutableId, UseManagedIdentity, TargetLogTenantID, TargetLogAppID, TargetLogCertificateThumbprint, TargetLogAppSecretReference. + - Both APIs are supported simultaneously, controlled by the SentinelLogIngestionAPIActivated toggle, to enable a phased migration. + - Adding runtime warning banner when the collector still uses the legacy Log Analytics HTTP Data Collector API. + See [ESI-PublicContent/Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md](https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI) for the migration procedure. + - New Identity sub-property columns exposed by the DCR transformKql : Identity_Depth_d, Identity_DistinguishedName_s, Identity_DomainId_s, Identity_IsDeleted_b, Identity_IsRelativeDn_b, Identity_Name_s, Identity_ObjectGuid_g, Identity_Parent_s, Identity_PartitionFQDN_s, Identity_PartitionGuid_g, Identity_Rdn_s. + - Create $Script:ESIDataPath to store data in a specific folder and become independant from CSV configuration. + - Move ExportDomainsInformation to LogCollection Section in configuration. If set to true, the Domain Information will be exported in the Log Collection. Default Value is True as before. + - Change GitHub link for Configuration file to use the new repository. + - Adding possibility to use GitHub API instead of direct download for configuration file. + Version : 7.6.0.1 - Released : 26/07/2024 - nilepagn - Adding Try-Catch on Get-AutomationVariable Test - Correct a bug on Get-LastVersion with Write-LogMessage diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Parameters.md b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Parameters.md index 7d985d4582f..2e141883702 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Parameters.md +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/Parameters.md @@ -1,6 +1,6 @@ # ExchSecIns Configuration -Actual Parameter version : 2.5 +Actual Parameter version : 3.0 ## Table of Contents @@ -8,18 +8,21 @@ Actual Parameter version : 2.5 - [Table of Contents](#table-of-contents) - [Parameters](#parameters) - [Global](#global) - - [Output](#output) - [Advanced](#advanced) - [LogCollection](#logcollection) + - [InternetAddonCollectionConfiguration](#internetaddoncollectionconfiguration) - [MGGraphAPIConnection](#mggraphapiconnection) - [InstanceConfiguration](#instanceconfiguration) - [AuditFunctionsFiles](#auditfunctionsfiles) - [AuditFunctionProtectedArea](#auditfunctionprotectedarea) - [Description](#description) - [UDSLogProcessor](#udslogprocessor) + - [Azure Monitor Log Ingestion API parameters](#azure-monitor-log-ingestion-api-parameters) + - [InternetAddonCollectionConfiguration](#internetaddoncollectionconfiguration-1) - [InstanceConfiguration](#instanceconfiguration-1) - [AuditFunctionsFiles](#auditfunctionsfiles-1) - [other parameters](#other-parameters) + - [Migration from configuration version 2.5 to 3.0](#migration-from-configuration-version-25-to-30) ## Parameters @@ -27,83 +30,107 @@ Parameters can be found in the "CollectExchSecConfiguration.json" file for On-Pr ### Global -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| ParallelTimeoutMinutes | Int | Maximum time in minutes to wait for a parallel job to finish | 5 | False | -| MaxParallelRunningJobs | Int | Maximum number of parallel jobs running at the same time | 8 | False | -| GlobalParallelProcessing | Boolean | Activate the collection of information by using paralleling mechanism. Recommanded | true | False | -| PerServerParallelProcessing | Boolean | Activate the collection of information concerning a specific server by using paralleling mechanism. Recommanded | true | False | -| DefaultDurationTracking | Int | Default duration tracking in days | 30 | False | -| ESIProcessingType | String | Type of processing, online or offline | Online | False | -| EnvironmentIdentification | String | Identification of the environment. Could be any text, the name of the tenant or AD domain | MyOwnEnvironment | False | +| Parameter | Type | Description | Default | Required | +|-----------------------------|---------|--------------------------------------------------------------------------------------------|------------------|----------| +| ParallelTimeoutMinutes | Int | Maximum time in minutes to wait for a parallel job to finish | 5 | False | +| MaxParallelRunningJobs | Int | Maximum number of parallel jobs running at the same time | 8 | False | +| GlobalParallelProcessing | Boolean | Activate the collection of information by using paralleling mechanism. Recommanded | true | False | +| PerServerParallelProcessing | Boolean | Activate the collection of information concerning a specific server by using paralleling | true | False | +| DefaultDurationTracking | Int | Default duration tracking in days | 30 | False | +| ESIProcessingType | String | Type of processing, online or offline | Online | False | +| EnvironmentIdentification | String | Identification of the environment. Could be any text, the name of the tenant or AD domain | MyOwnEnvironment | False | -### Output - -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| DefaultOutputFile | String | Default output file where data will be written if log collection by API is not activated. | C:\ExchSecIns\data\ExchSecIns.csv | False | -| ExportDomainsInformation | Boolean | Export AD Domain Information in Sentinel Table | True | False | +> [!NOTE] +> The `Output` section has been removed in configuration version 3.0. `ExportDomainsInformation` and the output file setting are now part of the [LogCollection](#logcollection) section. ### Advanced -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| ParralelWaitRunning | Int | Time in seconds to wait for parallel processing before considering a timeout | 10 | False | -| ParralelPingWaitRunning | Int | Time in seconds to wait for parallel ping processing before considering a timeout | 10 | False | -| OnlyExplicitActivation | Boolean | Only the explicit activation of the functions are processed. In this mode, each function needs to be taggued for processig | false | False | -| ExchangeServerBinPath | String | Path of the Exchange Server Binaries. Could be changed if Exchange is not installed in the default folder path | c:\Program Files\Microsoft\Exchange Server\V15\bin | False | -| BypassServerAvailabilityTest | Boolean | Bypass the server availability test. If this feature is activated, the collector will try to work with all servers including inaccessible servers. | false | False | -| ExplicitExchangeServerList | Array | List of explicit Exchange servers. If the previous parameter is activated, it could be good to build a static list of server to use | [] | False | -| FunctionsListInline | Boolean | Functions list inline. The functions will be read in the main config file. This option is more for retrocompatibility | false | False | -| FunctionsListWithoutInternet | Boolean | Functions list without internet. If this option is activated, the collector will use the local files instead of files in the Github repository | false | False | -| Beta | Boolean | Activating Beta feature, collecting Beta version of functions to execute. | false | False | -| Useproxy | Boolean | Use Proxy boolean if you need it. The next option need to be filled. | false | False | -| ProxyUrl | String | Proxy URL | http://proxy.dom.net:8080 | False | -| MaximalSentinelPacketSizeMb | Int | Max Packet size for Sentinel in Mb | 32 | False | -| PaginationErrorThreshold | Int | Pagination Error Threshold when an executed function use a pagination | 5 | False | -| UpdateVersionCheckingDeactivated | Boolean | Deactivate the version checking | false | False | -| DeactivateUDSLogs | Boolean | Deactivate the log summary (Called USD Logs) at the end of the script. | false | False | -| LogVerboseActivated | Boolean | Log Verbose Activated | true | False | -| UDSLogProcessor | Array | UDS Log Processor definition. By Default USD logs are displayed at the end. It could stored in a file or an Azure Storage account if needed. See Below description. | [{Activated:true, StorageType:Output}] | False | +| Parameter | Type | Description | Default | Required | +|----------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|----------| +| ParralelWaitRunning | Int | Time in seconds to wait for parallel processing before considering a timeout | 10 | False | +| ParralelPingWaitRunning | Int | Time in seconds to wait for parallel ping processing before considering a timeout | 10 | False | +| OnlyExplicitActivation | Boolean | Only the explicit activation of the functions are processed. In this mode, each function needs to be taggued for processig | false | False | +| ExchangeServerBinPath | String | Path of the Exchange Server Binaries. Could be changed if Exchange is not installed in the default folder path | c:\Program Files\Microsoft\Exchange Server\V15\bin | False | +| BypassServerAvailabilityTest | Boolean | Bypass the server availability test. If activated, the collector will try to work with all servers including inaccessible servers. | false | False | +| ExplicitExchangeServerList | Array | List of explicit Exchange servers. If the previous parameter is activated, it could be good to build a static list of servers to use | [] | False | +| FunctionsListInline | Boolean | Functions list inline. The functions will be read in the main config file. This option is more for retrocompatibility | false | False | +| FunctionsListWithoutInternet | Boolean | Functions list without internet. If activated, the collector will use the local files instead of files in the GitHub repository | false | False | +| Beta | Boolean | Activating Beta feature, collecting Beta version of functions to execute. | false | False | +| Useproxy | Boolean | Use Proxy boolean if you need it. The next option needs to be filled. | false | False | +| ProxyUrl | String | Proxy URL | http://proxy.dom.net:8080 | False | +| MaximalSentinelPacketSizeMb | Int | Max Packet size for Sentinel in Mb. **Recommended value with the Log Ingestion API is `0.9`** (payload limit of 1 MB per POST). | 32 | False | +| PaginationErrorThreshold | Int | Pagination Error Threshold when an executed function uses a pagination | 5 | False | +| UpdateVersionCheckingDeactivated | Boolean | Deactivate the version checking | false | False | +| ExplicitESIDataPath | String | Explicit path where the collector stores its data (CSV, tracking, cache). If empty, the default location is computed automatically from the script context. | (empty) | False | +| DeactivateUDSLogs | Boolean | Deactivate the log summary (called UDS Logs) at the end of the script. | false | False | +| LogVerboseActivated | Boolean | Log Verbose Activated | true | False | +| UDSLogProcessor | Array | UDS Log Processor definition. By default UDS logs are displayed at the end. They can be stored in a file or an Azure Storage account if needed. | [{Activated:true, StorageType:Output}] | False | ### LogCollection -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| ActivateLogUpdloadToSentinel | Boolean | Activate the log upload to Sentinel. If not activated, results are stored in a file. | true | False | -| WorkspaceId | String | Workspace Id | e15121b8-fc25-4ec2-8d21-44532bfd219a | False | -| WorkspaceKey | String | Workspace Key | WKey | False | -| LogTypeName | String | Name of the Table to store data. ESIExchangeConfig is the default table used by Sentinel Solution | ESIExchangeConfig | False | -| TogetherMode | Boolean | Together Mode can be activated to store results in a file in addition to sentinel upload | false | False | +| Parameter | Type | Description | Default | Required | +|----------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------|-------------------------------------------------| +| ActivateLogUpdloadToSentinel | Boolean | Activate the log upload to Sentinel. If not activated, results are stored in a file only. | true | False | +| WorkspaceId | String | Workspace ID. **Used only when `SentinelLogIngestionAPIActivated` is `false`** (legacy Log Analytics HTTP Data Collector API). | (guid) | Legacy API only | +| WorkspaceKey | String | Workspace Key. **Used only when `SentinelLogIngestionAPIActivated` is `false`** (legacy Log Analytics HTTP Data Collector API). | (key) | Legacy API only | +| LogTypeName | String | Name of the target table (legacy API) or stream suffix (new API — `Custom-` is sent to the DCR). | ESIExchangeConfig | False | +| TogetherMode | Boolean | If true, results are stored in a file **in addition to** the Sentinel upload. | false | False | +| SentinelLogIngestionAPIActivated | Boolean | Activate the **Azure Monitor Log Ingestion API** (DCE / DCR / Entra ID identity). When `true`, the legacy `WorkspaceId` / `WorkspaceKey` are ignored. | false | False | +| DataCollectionEndpointURI | String | URI of the Data Collection Endpoint (DCE) produced by the ARM template `azuredeploy_ESI_LogIngestionAPI.json`. | (empty) | Yes, if new API activated | +| DCRImmutableId | String | Immutable ID of the target Data Collection Rule (DCR). One of the outputs of the ARM template (`OnPremises`, `Online`, or `MessageTracking`). | (empty) | Yes, if new API activated | +| UseManagedIdentity | Boolean | If `true`, the collector uses the system-assigned managed identity (recommended for Azure Automation). If `false`, uses a certificate-based Entra ID service principal. | false | False | +| TargetLogTenantID | String | Tenant ID hosting the Entra ID application used for ingestion. | (empty) | Yes, if `UseManagedIdentity` = `false` | +| TargetLogAppID | String | Application (client) ID of the Entra ID application used for ingestion. | (empty) | Yes, if `UseManagedIdentity` = `false` | +| TargetLogCertificateThumbprint | String | Thumbprint of the certificate authenticating the Entra ID application (local certificate store). | (empty) | Yes, if `UseManagedIdentity` = `false` | +| TargetLogAppSecretReference | String | Name of the Automation variable referencing the certificate (Azure Automation, certificate mode). | (empty) | Azure Automation with certificate mode | +| CSVOutputFile | String | Default output file when `ActivateLogUpdloadToSentinel` is `false` or when `TogetherMode` is `true`. Replaces the previous `Output.DefaultOutputFile`. | ExchSecIns.csv | False | +| ExportDomainsInformation | Boolean | Export AD Domain Information in Sentinel Table. Moved from the removed `Output` section. | True | False | + +> [!IMPORTANT] +> The Log Ingestion API replaces the legacy Log Analytics HTTP Data Collector API. If `SentinelLogIngestionAPIActivated` is `false`, the collector emits a runtime warning banner at each execution. Full migration guide: [Migrate from the Log Analytics HTTP Data Collector API to the Log Ingestion API](../../Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md). + +### InternetAddonCollectionConfiguration + +Controls how the collector downloads Add-On files from GitHub. It replaces the historical implicit download through the raw content endpoint by giving the choice between the raw endpoint (unauthenticated) and the GitHub REST API (authenticated for higher rate limits or private repositories). + +| Parameter | Type | Description | Default | Required | +|-------------------------------|---------|--------------------------------------------------------------------------------------------------------------------------|----------------------------|--------------------------------------------------------------| +| UseGithubAPI | Boolean | If `true`, download Add-Ons through the GitHub REST API. If `false`, use the raw content endpoint (no authentication). | false | False | +| GithubRawUrlforOnPremises | String | Base URL of the raw endpoint for On-Premises Add-Ons. | (Azure-Sentinel raw URL) | Used when `UseGithubAPI` = `false` | +| GithubRawUrlforOnline | String | Base URL of the raw endpoint for Exchange Online Add-Ons. | (Azure-Sentinel raw URL) | Used when `UseGithubAPI` = `false` | +| GithubAPIToken | String | GitHub Personal Access Token used when `GithubAPIConnectionType` = `Token` and the token is embedded in the config file. | (empty) | With `GithubAPIConnectionType` = `Token` | +| GithubAPITokenVariableName | String | Name of the environment variable holding the GitHub token when using `EnvironmentVariable` mode. | (empty) | With `GithubAPIConnectionType` = `EnvironmentVariable` | +| GithubAPITokenSecretReference | String | Name of the Azure Automation variable referencing the GitHub token when running as a runbook. | (empty) | With `GithubAPIConnectionType` = `AutomationVariable` | +| GithubAPIConnectionType | String | Authentication mode for the GitHub API. Allowed values: `NoAuth`, `Token`, `EnvironmentVariable`, `AutomationVariable`. | NoAuth | Yes, if `UseGithubAPI` = `true` | ### MGGraphAPIConnection -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| MGGraphAzureRMCertificate | String | MGGraph Azure RM Certificate | | False | -| MGGraphAzureRMAppId | String | MGGraph Azure RM App Id | | False | +| Parameter | Type | Description | Default | Required | +|---------------------------|--------|------------------------------|---------|----------| +| MGGraphAzureRMCertificate | String | MGGraph Azure RM Certificate | | False | +| MGGraphAzureRMAppId | String | MGGraph Azure RM App Id | | False | ### InstanceConfiguration -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| Default | Object | Default configuration, see details below. | {All:true, Capabilities:OP\|OL\|MGGRAPH\|ADINFOS} | False | -| IIS-IoCs | Object | IIS IoCs configuration, see details below. | {All:true, Category:IIS-IoCs, Capabilities:IIS, OutputName:ESIIISIoCs} | False | -| ExchangeOnlineMessageTracking | Object | Exchange Online Message Tracking configuration, see details below. | {All:true, Category:OnlineMessageTracking, Capabilities:OL, OutputName:ExchangeOnlineMessageTracking} | False | -| InstanceExample | Object | Instance Example configuration, see details below. | {SelectedAddons:[Filename1, Filename2], FileteredAddons:[Filename1, Filename2]} | False | +| Parameter | Type | Description | Default | Required | +|-------------------------------|--------|-------------------------------------------------|------------------------------------------------------------------------------------------------------|----------| +| Default | Object | Default configuration, see details below. | {All:true, Capabilities:OP\|OL\|MGGRAPH\|ADINFOS} | False | +| IIS-IoCs | Object | IIS IoCs configuration, see details below. | {All:true, Category:IIS-IoCs, Capabilities:IIS, OutputName:ESIIISIoCs} | False | +| ExchangeOnlineMessageTracking | Object | Exchange Online Message Tracking configuration. | {All:true, Category:OnlineMessageTracking, Capabilities:OL, OutputName:ExchangeOnlineMessageTracking} | False | +| InstanceExample | Object | Instance Example configuration. | {SelectedAddons:[Filename1, Filename2], FileteredAddons:[Filename1, Filename2]} | False | ### AuditFunctionsFiles -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| Filename | String | Filename | FiletoIgnore | False | -| Deactivated | Boolean | Deactivated | false | False | +| Parameter | Type | Description | Default | Required | +|-------------|---------|-------------|--------------|----------| +| Filename | String | Filename | FiletoIgnore | False | +| Deactivated | Boolean | Deactivated | false | False | ### AuditFunctionProtectedArea -| Parameter | Type | Description | Default | Required | -| --- | --- | --- | --- | --- | -| ContentCheckSum | String | Content CheckSum | | False | +| Parameter | Type | Description | Default | Required | +|-----------------|--------|------------------|---------|----------| +| ContentCheckSum | String | Content CheckSum | | False | ## Description @@ -112,7 +139,7 @@ Below are specific parameters and their description. ### UDSLogProcessor -The USDLogProcessor allows to describe the way the USD logs are displayed or stored. It could be stored in a file or an Azure Storage account if needed or only displayed. +The UDSLogProcessor allows to describe the way the UDS logs are displayed or stored. It could be stored in a file or an Azure Storage account if needed or only displayed. The UDSLogProcessor is an array of object. Each object contains the following parameters: - Activated: Boolean. If true, the log will be processed. @@ -127,9 +154,50 @@ The UDSLogProcessor is an array of object. Each object contains the following pa - ApplicationID: String. The application id. If the StorageType is AzureStorageAccount and ConnexionType is Certificate, this parameter is required. - CertificateThumbprint: String. The certificate thumbprint. If the StorageType is AzureStorageAccount and ConnexionType is Certificate, this parameter is required. +### Azure Monitor Log Ingestion API parameters + +Starting with configuration version 3.0, the collector natively supports the Azure Monitor **Log Ingestion API** in addition to the legacy Log Analytics HTTP Data Collector API. The switch between the two APIs is controlled by `SentinelLogIngestionAPIActivated` in the `LogCollection` section. Both APIs are supported simultaneously to enable a phased migration. + +Prerequisites when `SentinelLogIngestionAPIActivated` is `true`: + +- A **Data Collection Endpoint (DCE)** and one or several **Data Collection Rules (DCR)** must be deployed. The ARM template [azuredeploy_ESI_LogIngestionAPI.json](/Deployments/azuredeploy_ESI_LogIngestionAPI.json) provisions everything needed (DCE + 3 tables + 3 DCRs, each optional). +- An **identity** must exist for the collector: + - **System-assigned Managed Identity** on the Automation Account (`UseManagedIdentity = true`), or + - **Entra ID application** with a **certificate** in the local certificate store (`UseManagedIdentity = false`). +- The identity must hold the **Monitoring Metrics Publisher** role on the target DCR. + +Parameter selection matrix: + +| Scenario | Required parameters | +|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Legacy Log Analytics API | `SentinelLogIngestionAPIActivated = false`, `WorkspaceId`, `WorkspaceKey`, `LogTypeName` | +| New Log Ingestion API — Azure Automation with Managed Identity | `SentinelLogIngestionAPIActivated = true`, `DataCollectionEndpointURI`, `DCRImmutableId`, `UseManagedIdentity = true` | +| New Log Ingestion API — Entra ID application with local certificate | `SentinelLogIngestionAPIActivated = true`, `DataCollectionEndpointURI`, `DCRImmutableId`, `UseManagedIdentity = false`, `TargetLogTenantID`, `TargetLogAppID`, `TargetLogCertificateThumbprint` | +| New Log Ingestion API — Azure Automation with certificate | Same as above **plus** `TargetLogAppSecretReference` (Automation variable name storing the certificate reference) | + +Recommended companion setting: set `MaximalSentinelPacketSizeMb` to `0.9` when the Log Ingestion API is activated (payload limit is 1 MB per POST). + +Full end-to-end setup: [README_AzureMonitorSetup.md](../../Documentations/README_AzureMonitorSetup.md). Migration procedure from the legacy API: [Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md](../../Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md). + +### InternetAddonCollectionConfiguration + +The `InternetAddonCollectionConfiguration` section controls how the collector retrieves Add-On configuration files (JSON) from GitHub. + +Two modes are supported: + +- **Raw content endpoint** (`UseGithubAPI = false`) — unauthenticated download from `raw.githubusercontent.com`. Recommended for public repositories and low-frequency runs. Requires the `GithubRawUrlforOnPremises` and `GithubRawUrlforOnline` base URLs to point at the target branch. +- **GitHub REST API** (`UseGithubAPI = true`) — authenticated download from the GitHub API. Provides higher rate limits and supports private repositories. Authentication mode is chosen through `GithubAPIConnectionType`: + - `NoAuth` — anonymous API call. Subject to strict rate limits. + - `Token` — reads the PAT from `GithubAPIToken` (embedded in the config). + - `EnvironmentVariable` — reads the PAT from the environment variable whose name is `GithubAPITokenVariableName`. + - `AutomationVariable` — reads the PAT from the Azure Automation variable whose name is `GithubAPITokenSecretReference` (recommended for Runbook deployments). + +> [!TIP] +> For Runbook deployments accessing a private repository, use `UseGithubAPI = true` + `GithubAPIConnectionType = AutomationVariable` and store the PAT as an **encrypted** Automation variable. + ### InstanceConfiguration -The InstanceConfiguration allows to configure multiple instances to collect different data. 3 main instances are available: Default, IIS-IoCs and ExchangeOnlineMessageTracking. It's possible to configure more instances by using the InstanceExample example where InstanceExample is the name of the instance to configure. +The InstanceConfiguration allows to configure multiple instances to collect different data. 3 main instances are available: Default, IIS-IoCs and ExchangeOnlineMessageTracking. It's possible to configure more instances by using the InstanceExample example where InstanceExample is the name of the instance to configure. The InstanceConfiguration is an object. It contains the following parameters: - Default: Object. Default configuration, mandatory. It contains the following parameters: @@ -169,3 +237,17 @@ The AuditFunctionsFiles is an array of object. It's used to ignore a specific se The parameter AuditFunctionProtectedArea is not used for the moment. They are reserved for future use. The parameter AuditFunctions is not used anymore, only present for backward comptability. + +## Migration from configuration version 2.5 to 3.0 + +Configuration version 3.0 introduces the following structural changes: + +- The whole **`Output` section has been removed**. + - `Output.DefaultOutputFile` → `LogCollection.CSVOutputFile` + - `Output.ExportDomainsInformation` → `LogCollection.ExportDomainsInformation` +- New parameters in **`LogCollection`** to enable the Azure Monitor Log Ingestion API — see [LogCollection](#logcollection) and [Azure Monitor Log Ingestion API parameters](#azure-monitor-log-ingestion-api-parameters). +- New **`InternetAddonCollectionConfiguration`** section to control Add-On downloads through the GitHub raw endpoint or the authenticated GitHub REST API. +- New optional parameter **`ExplicitESIDataPath`** in `Advanced` to override the default data folder used by the collector. +- The default of **`MaximalSentinelPacketSizeMb`** should be lowered to `0.9` when the new API is used. + +Legacy configurations continue to work — the collector keeps supporting the legacy Log Analytics HTTP Data Collector API and displays a runtime warning banner at each execution until the migration is completed. See the dedicated migration guide: [Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md](../../Documentations/Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI.md). diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/README.md b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/README.md index 213978ad0c7..c6dd59c3eb2 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/README.md +++ b/Solutions/Microsoft Exchange Security - Exchange Online/# - General Content/Solutions/ESICollector/README.md @@ -10,10 +10,52 @@ Parameters are described in the Configuration file. Explanation of the parameter ## Versioning -## Actual Version : 7.6.0.1 +## Actual Version : 8.0.0.0 ## Upgrade paths +### From 7.6.0.1 to 8.0.0.0 + +> [!IMPORTANT] +> Version 8.0.0.0 introduces native support for the **Azure Monitor Log Ingestion API** (DCE/DCR based) that replaces the legacy Log Analytics HTTP Data Collector API. The collector still supports both APIs, controlled by the `SentinelLogIngestionAPIActivated` toggle, so the upgrade can be performed in two phases (script upgrade first, cutover later). +> +> Full migration guide : [Migrate from the Log Analytics HTTP Data Collector API to the Log Ingestion API](https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI). + +#### **Configuration File** + +The configuration schema is backward compatible. Legacy configurations keep working with no change and will trigger a runtime warning banner to remind operators to migrate. + +New or updated settings: + +- **`LogCollection` section** — new keys required only if you switch to the Log Ingestion API: + - `SentinelLogIngestionAPIActivated` : `true` to activate the new API. Default `false`. + - `DataCollectionEndpointURI` : DCE URI produced by the ARM template (`azuredeploy_ESI_LogIngestionAPI.json`). + - `DCRImmutableId` : Immutable ID of the target DCR (Online, OnPremises, or MessageTracking). + - `UseManagedIdentity` : `true` for Azure Automation with system-assigned managed identity. + - `TargetLogTenantID` / `TargetLogAppID` / `TargetLogCertificateThumbprint` : required when `UseManagedIdentity` is `false` (certificate-based service principal). + - `TargetLogAppSecretReference` : Automation variable name referencing the certificate (Azure Automation, certificate mode). +- **`ExportDomainsInformation`** has moved from the `Global` section to the `LogCollection` section. Default value stays `true`. +- **`Advanced` section**: + - `MaximalSentinelPacketSizeMb` default lowered to `0.9` when the Log Ingestion API is used (payload limit of 1 MB per POST). + - New GitHub download settings for configuration retrieval via the GitHub API instead of raw download. + +Update the file manually or use the **WinformConfig editor** (`ExchSecIns/WinformConfig/SetupCollectExchSecConfiguration.ps1`), which validates the payload and hides the legacy `WorkspaceId` / `WorkspaceKey` fields once the Log Ingestion API is activated. + +#### **ESI Collector Script** + +Replace the old script version with the new one. Additional tasks depending on your target API: + +- **Staying on the legacy API temporarily** : nothing else to do. The collector will display a runtime warning banner at each execution until the migration is completed. +- **Switching to the Log Ingestion API** : + 1. Deploy the ARM template `Deployments/azuredeploy_ESI_LogIngestionAPI.json` (in Zip) or `Data Connectors/azuredeploy_ESI_LogIngestionAPI.json` (In Sentinel Solution) to provision the DCE, tables (`ESIAPIExchangeOnPremConfig_CL`, `ESIAPIExchangeOnlineConfig_CL`, `ExchangeOnlineMessageTracking_CL`) and DCRs. + 2. Assign **Monitoring Metrics Publisher** on each target DCR to the identity used by the collector (managed identity or Entra ID service principal). + 3. Update the configuration keys listed above. + 4. Trigger a manual run and verify the new `_CL` tables are populated. + +#### **Data model changes** + +The new tables include the `Identity` sub-property columns extracted at ingestion by the DCR `transformKql` : `Identity_Depth_d`, `Identity_DistinguishedName_s`, `Identity_DomainId_s`, `Identity_IsDeleted_b`, `Identity_IsRelativeDn_b`, `Identity_Name_s`, `Identity_ObjectGuid_g`, `Identity_Parent_s`, `Identity_PartitionFQDN_s`, `Identity_PartitionGuid_g`, `Identity_Rdn_s`. The original `Identity_s` string column is preserved. Analytic rules, hunting queries and workbooks that already rely on `Identity_s` remain valid. + ### From 7.6.0.0 to 7.6.0.1 #### **Configuration File** diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/Data Connectors/ESI-ExchangeOnlineCollector.json b/Solutions/Microsoft Exchange Security - Exchange Online/Data Connectors/ESI-ExchangeOnlineCollector.json index 244f91c89db..2fafef6eab0 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/Data Connectors/ESI-ExchangeOnlineCollector.json +++ b/Solutions/Microsoft Exchange Security - Exchange Online/Data Connectors/ESI-ExchangeOnlineCollector.json @@ -58,6 +58,10 @@ } ], "customs": [ + { + "name": "Azure Log Analytics HTTP Data Collector API will be deprecated", + "description": "Azure Log Analytics HTTP Data Collector API will be deprecated, Update to Log Monitor API is REQUIRED. [Learn more](https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI)" + }, { "name": "Microsoft.Web/sites permissions", "description": "Read and write permissions to Azure Functions to create a Function App is required. [See the documentation to learn more about Azure Functions](https://docs.microsoft.com/azure/azure-functions/)." @@ -86,7 +90,7 @@ "instructions": [ { "parameters": { - "text": "

NOTE - UPDATE:

We recommend to Update the Collector to Version 7.6.0.0 or highier.
The Collector Script Update procedure could be found here : ESI Online Collector Update", + "text": "

NOTE - UPDATE:

We recommend to Update the Collector to Version 8.0.0.0 or higher AND switching to new Log Monitor API.
The Collector Script Update procedure could be found here : ESI Online Collector Update, Update to Log Monitor API procedure could be found here : Migrate From Log Analytics API To Log Ingestion API.
The new version of the Collector is using DCR (Data Collection Rules) for data collection. You need to configure DCR before running the script.", "visible": true, "inline": false }, @@ -95,7 +99,10 @@ ] }, { - "description": ">**NOTE:** This data connector depends on a parser based on a Kusto Function to work as expected. Follow the steps for each Parser to create the Kusto Functions alias : [**ExchangeConfiguration**](https://aka.ms/sentinel-ESI-ExchangeConfiguration-Online-parser) and [**ExchangeEnvironmentList**](https://aka.ms/sentinel-ESI-ExchangeEnvironmentList-Online-parser) \n\n**STEP 1 - Parsers deployment**", + "description": "**STEP 1 - Deploy DCE/DCR for Azure Monitor using Azure Resource Manager (ARM) Template**\n\n1. Click the **Deploy to Azure** button below. \n\n\t[![Deploy To Azure](https://aka.ms/deploytoazurebutton)](https://aka.ms/sentinel-ESI-LogMonitor-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the required fields'. \n>4. Mark the checkbox labeled **I agree to the terms and conditions stated above**. \n5. Click **Purchase** to deploy." + }, + { + "description": ">**NOTE:** This data connector depends on a parser based on a Kusto Function to work as expected. Follow the steps for each Parser to create the Kusto Functions alias : [**ExchangeConfiguration**](https://aka.ms/sentinel-ESI-ExchangeConfiguration-Online-parser) and [**ExchangeEnvironmentList**](https://aka.ms/sentinel-ESI-ExchangeEnvironmentList-Online-parser) \n\n**STEP 2 - Parsers deployment**", "instructions": [ { "parameters": { @@ -127,7 +134,7 @@ "description": ">**NOTE:** This connector uses Azure Automation to connect to 'Exchange Online' to pull its Security analysis into Microsoft Sentinel. This might result in additional data ingestion costs. Check the [Azure Automation pricing page](https://azure.microsoft.com/pricing/details/automation/) for details." }, { - "description": "**STEP 2 - Choose ONE from the following two deployment options to deploy the connector and the associated Azure Automation**\n\n>**IMPORTANT:** Before deploying the 'ESI Exchange Online Security Configuration' connector, have the Workspace ID and Workspace Primary Key (can be copied from the following), as well as the Exchange Online tenant name (contoso.onmicrosoft.com), readily available.", + "description": "**STEP 3 - Choose ONE from the following two deployment options to deploy the connector and the associated Azure Automation**\n\n>**IMPORTANT:** Before deploying the 'ESI Exchange Online Security Configuration' connector, have the Workspace ID and Workspace Primary Key (can be copied from the following), as well as the Exchange Online tenant name (contoso.onmicrosoft.com), readily available.", "instructions": [ { "parameters": { @@ -197,7 +204,7 @@ ] }, { - "description": "**STEP 3 - Assign Microsoft Graph Permission and Exchange Online Permission to Managed Identity Account** \n\nTo be able to collect Exchange Online information and to be able to retrieve User information and memberlist of admin groups, the automation account need multiple permission.", + "description": "**STEP 4 - Assign Microsoft Graph Permission and Exchange Online Permission to Managed Identity Account** \n\nTo be able to collect Exchange Online information and to be able to retrieve User information and memberlist of admin groups, the automation account need multiple permission.", "instructions": [ { "parameters": { @@ -228,7 +235,7 @@ ], "metadata": { "id": "fe7ccc48-e21b-4b90-b83e-9c8a6cb17d2f", - "version": "1.1.1", + "version": "2.0.0", "kind": "dataConnector", "source": { "kind": "solution", diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/Data Connectors/azuredeploy_ESI_LogIngestionAPI.json b/Solutions/Microsoft Exchange Security - Exchange Online/Data Connectors/azuredeploy_ESI_LogIngestionAPI.json new file mode 100644 index 00000000000..f44928d60a9 --- /dev/null +++ b/Solutions/Microsoft Exchange Security - Exchange Online/Data Connectors/azuredeploy_ESI_LogIngestionAPI.json @@ -0,0 +1,970 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "deployTables": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Master switch: deploy the custom Log Analytics tables. Set to false to deploy only the Data Collection Endpoint/Rules (assumes tables already exist)." + } + }, + "deployDataCollection": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Master switch: deploy the Data Collection Endpoint and Data Collection Rules. Set to false to deploy only the custom tables." + } + }, + "deployOnlineConfigTable": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Deploy the Online Configuration (ESIExchangeOnlineConfig_CL and its associated data flow)" + } + } + , + "deployOnPremConfigTable": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Deploy the On-Premises Configuration (ESIExchangeConfig_CL and its associated data flow)" + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace (has to be in same subscription, resource group, and region as the Data Collection Endpoint)" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources : Data Collection Endpoint and Log Analytics workspace (has to be in same subscription, resource group, and region as the Data Collection Endpoint)" + } + }, + "dataCollectionEndpointName": { + "type": "string", + "defaultValue": "DCE-ESI-LogIngestion", + "metadata": { + "description": "Name of the Data Collection Endpoint" + } + }, + "dataCollectionRuleOnlineConfigName": { + "type": "string", + "defaultValue": "DCR-ESI-OnlineConfig", + "metadata": { + "description": "Name of the Data Collection Rule for Online Config table" + } + }, + "dataCollectionRuleOnPremisesConfigName": { + "type": "string", + "defaultValue": "DCR-ESI-OnPremisesConfig", + "metadata": { + "description": "Name of the Data Collection Rule for On-Premises Config table" + } + }, + "dataCollectionRuleMessageTrackingName": { + "type": "string", + "defaultValue": "DCR-ESI-MessageTracking", + "metadata": { + "description": "Name of the Data Collection Rule for Message Tracking table" + } + }, + "retentionInDays": { + "type": "int", + "defaultValue": 90, + "minValue": 30, + "maxValue": 730, + "metadata": { + "description": "Retention period in days for the custom tables" + } + }, + "deployMessageTrackingTable": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Deploy the Message Tracking table (ExchangeOnlineMessageTracking_CL and its associated data flow)" + } + } + }, + "variables": { + "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaceName'))]", + "dataCollectionEndpointId": "[resourceId('Microsoft.Insights/dataCollectionEndpoints', parameters('dataCollectionEndpointName'))]", + "configTableName": "ESIAPIExchangeOnlineConfig_CL", + "onPremConfigTableName": "ESIAPIExchangeOnPremisesConfig_CL", + "messageTrackingTableName": "ExchangeOnlineMessageTracking_CL", + "onPremConfigoutputStream": "Custom-ESIAPIExchangeOnPremisesConfig_CL", + "onlineConfigoutputStream": "Custom-ESIAPIExchangeOnlineConfig_CL" + }, + "resources": [ + { + "condition": "[parameters('deployDataCollection')]", + "type": "Microsoft.Insights/dataCollectionEndpoints", + "apiVersion": "2024-03-11", + "name": "[parameters('dataCollectionEndpointName')]", + "location": "[parameters('location')]", + "properties": { + "networkAcls": { + "publicNetworkAccess": "Enabled" + } + } + }, + { + "condition": "[and(parameters('deployTables'), parameters('deployOnPremConfigTable'))]", + "type": "Microsoft.OperationalInsights/workspaces/tables", + "apiVersion": "2022-10-01", + "name": "[concat(parameters('workspaceName'), '/', variables('onPremConfigTableName'))]", + "properties": { + "totalRetentionInDays": "[parameters('retentionInDays')]", + "plan": "Analytics", + "schema": { + "name": "[variables('onPremConfigTableName')]", + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime", + "description": "The timestamp when the log was generated" + }, + { + "name": "EntryDate_s", + "type": "string", + "description": "Date of the configuration entry" + }, + { + "name": "GenerationInstanceID_g", + "type": "string", + "description": "Unique identifier for the generation instance" + }, + { + "name": "ESIEnvironment_s", + "type": "string", + "description": "Exchange environment identifier" + }, + { + "name": "Section_s", + "type": "string", + "description": "Configuration section name" + }, + { + "name": "ExecutionResult_s", + "type": "string", + "description": "Execution result" + }, + { + "name": "Identity_s", + "type": "string", + "description": "Identity" + }, + { + "name": "Identity_Depth_d", + "type": "real", + "description": "Identity depth in the directory hierarchy" + }, + { + "name": "Identity_DistinguishedName_s", + "type": "string", + "description": "Identity distinguished name (LDAP DN)" + }, + { + "name": "Identity_DomainId_s", + "type": "string", + "description": "Identity domain identifier (serialized when object)" + }, + { + "name": "Identity_IsDeleted_b", + "type": "boolean", + "description": "Indicates whether the identity is deleted" + }, + { + "name": "Identity_IsRelativeDn_b", + "type": "boolean", + "description": "Indicates whether the DN is relative" + }, + { + "name": "Identity_Name_s", + "type": "string", + "description": "Identity name" + }, + { + "name": "Identity_ObjectGuid_g", + "type": "string", + "description": "Identity object GUID" + }, + { + "name": "Identity_Parent_s", + "type": "string", + "description": "Identity parent (serialized when object)" + }, + { + "name": "Identity_PartitionFQDN_s", + "type": "string", + "description": "Identity partition FQDN" + }, + { + "name": "Identity_PartitionGuid_g", + "type": "string", + "description": "Identity partition GUID" + }, + { + "name": "Identity_Rdn_s", + "type": "string", + "description": "Identity relative distinguished name (RDN, serialized when object)" + }, + { + "name": "IdentityString_s", + "type": "string", + "description": "Identity string" + }, + { + "name": "RawData_s", + "type": "string", + "description": "Raw configuration data in JSON format" + }, + { + "name": "Name_s", + "type": "string", + "description": "Name" + }, + { + "name": "ProcessedByServer_s", + "type": "string", + "description": "Processed by server" + }, + { + "name": "PSCmdL_s", + "type": "string", + "description": "PowerShell cmdlet" + }, + { + "name": "WhenChanged_t", + "type": "datetime", + "description": "When changed timestamp" + }, + { + "name": "WhenCreated_t", + "type": "datetime", + "description": "When created timestamp" + } + ] + }, + "retentionInDays": "[parameters('retentionInDays')]" + } + }, + { + "condition": "[and(parameters('deployTables'), parameters('deployOnlineConfigTable'))]", + "type": "Microsoft.OperationalInsights/workspaces/tables", + "apiVersion": "2022-10-01", + "name": "[concat(parameters('workspaceName'), '/', variables('configTableName'))]", + "properties": { + "totalRetentionInDays": "[parameters('retentionInDays')]", + "plan": "Analytics", + "schema": { + "name": "[variables('configTableName')]", + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime", + "description": "The timestamp when the log was generated" + }, + { + "name": "EntryDate_s", + "type": "string", + "description": "Date of the configuration entry" + }, + { + "name": "GenerationInstanceID_g", + "type": "string", + "description": "Unique identifier for the generation instance" + }, + { + "name": "ESIEnvironment_s", + "type": "string", + "description": "Exchange environment identifier" + }, + { + "name": "Section_s", + "type": "string", + "description": "Configuration section name" + }, + { + "name": "ExecutionResult_s", + "type": "string", + "description": "Execution result" + }, + { + "name": "Identity_s", + "type": "string", + "description": "Identity" + }, + { + "name": "Identity_Depth_d", + "type": "real", + "description": "Identity depth in the directory hierarchy" + }, + { + "name": "Identity_DistinguishedName_s", + "type": "string", + "description": "Identity distinguished name (LDAP DN)" + }, + { + "name": "Identity_DomainId_s", + "type": "string", + "description": "Identity domain identifier (serialized when object)" + }, + { + "name": "Identity_IsDeleted_b", + "type": "boolean", + "description": "Indicates whether the identity is deleted" + }, + { + "name": "Identity_IsRelativeDn_b", + "type": "boolean", + "description": "Indicates whether the DN is relative" + }, + { + "name": "Identity_Name_s", + "type": "string", + "description": "Identity name" + }, + { + "name": "Identity_ObjectGuid_g", + "type": "string", + "description": "Identity object GUID" + }, + { + "name": "Identity_Parent_s", + "type": "string", + "description": "Identity parent (serialized when object)" + }, + { + "name": "Identity_PartitionFQDN_s", + "type": "string", + "description": "Identity partition FQDN" + }, + { + "name": "Identity_PartitionGuid_g", + "type": "string", + "description": "Identity partition GUID" + }, + { + "name": "Identity_Rdn_s", + "type": "string", + "description": "Identity relative distinguished name (RDN, serialized when object)" + }, + { + "name": "IdentityString_s", + "type": "string", + "description": "Identity string" + }, + { + "name": "RawData_s", + "type": "string", + "description": "Raw configuration data in JSON format" + }, + { + "name": "Name_s", + "type": "string", + "description": "Name" + }, + { + "name": "ProcessedByServer_s", + "type": "string", + "description": "Processed by server" + }, + { + "name": "PSCmdL_s", + "type": "string", + "description": "PowerShell cmdlet" + }, + { + "name": "WhenChanged_t", + "type": "datetime", + "description": "When changed timestamp" + }, + { + "name": "WhenCreated_t", + "type": "datetime", + "description": "When created timestamp" + } + ] + }, + "retentionInDays": "[parameters('retentionInDays')]" + } + }, + { + "condition": "[and(parameters('deployTables'), parameters('deployMessageTrackingTable'))]", + "type": "Microsoft.OperationalInsights/workspaces/tables", + "apiVersion": "2022-10-01", + "name": "[concat(parameters('workspaceName'), '/', variables('messageTrackingTableName'))]", + "properties": { + "totalRetentionInDays": "[parameters('retentionInDays')]", + "plan": "Analytics", + "schema": { + "name": "[variables('messageTrackingTableName')]", + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime", + "description": "The timestamp when the log was generated" + }, + { + "name": "schemaVersion_s", + "type": "string", + "description": "Schema version of the log entry" + }, + { + "name": "clientIp_s", + "type": "string", + "description": "Client IP address" + }, + { + "name": "clientHostname_s", + "type": "string", + "description": "Client hostname" + }, + { + "name": "serverIp_s", + "type": "string", + "description": "Server IP address" + }, + { + "name": "senderHostname_s", + "type": "string", + "description": "Sender hostname" + }, + { + "name": "sourceContext_s", + "type": "string", + "description": "Source context information" + }, + { + "name": "connectorId_s", + "type": "string", + "description": "Connector identifier" + }, + { + "name": "source_s", + "type": "string", + "description": "Message source" + }, + { + "name": "eventId_s", + "type": "string", + "description": "Event identifier" + }, + { + "name": "internalMessageId_s", + "type": "string", + "description": "Internal message identifier" + }, + { + "name": "messageId_s", + "type": "string", + "description": "Message identifier" + }, + { + "name": "networkMessageId_s", + "type": "string", + "description": "Network message identifier" + }, + { + "name": "recipientAddress_s", + "type": "string", + "description": "Recipient email address" + }, + { + "name": "recipientStatus_s", + "type": "string", + "description": "Recipient delivery status" + }, + { + "name": "totalBytes_l", + "type": "long", + "description": "Total message size in bytes" + }, + { + "name": "recipientCount_i", + "type": "int", + "description": "Number of recipients" + }, + { + "name": "relatedRecipientAddress_s", + "type": "string", + "description": "Related recipient address" + }, + { + "name": "reference_s", + "type": "string", + "description": "Message reference" + }, + { + "name": "messageSubject_s", + "type": "string", + "description": "Email subject line" + }, + { + "name": "senderAddress_s", + "type": "string", + "description": "Sender email address" + }, + { + "name": "returnPath_s", + "type": "string", + "description": "Return path address" + }, + { + "name": "directionality_s", + "type": "string", + "description": "Message directionality (Originating/Incoming)" + }, + { + "name": "messageInfo_s", + "type": "string", + "description": "Additional message information" + }, + { + "name": "originalClientIp_s", + "type": "string", + "description": "Original client IP address" + }, + { + "name": "originalServerIp_s", + "type": "string", + "description": "Original server IP address" + }, + { + "name": "customData_s", + "type": "string", + "description": "Custom metadata" + }, + { + "name": "transportTrafficType_s", + "type": "string", + "description": "Transport traffic type" + }, + { + "name": "FilePath_s", + "type": "string", + "description": "File path of the log entry" + }, + { + "name": "logId_s", + "type": "string", + "description": "Log identifier" + }, + { + "name": "messageTrackingTenantId_s", + "type": "string", + "description": "Tenant identifier for the message tracking log" + } + ] + }, + "retentionInDays": "[parameters('retentionInDays')]" + } + }, + { + "condition": "[and(parameters('deployDataCollection'), parameters('deployOnPremConfigTable'))]", + "type": "Microsoft.Insights/dataCollectionRules", + "apiVersion": "2024-03-11", + "name": "[parameters('dataCollectionRuleOnPremisesConfigName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[variables('dataCollectionEndpointId')]", + "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), variables('onPremConfigTableName'))]" + ], + "properties": { + "dataCollectionEndpointId": "[variables('dataCollectionEndpointId')]", + "streamDeclarations": { + "Custom-ESIExchangeConfig": { + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime" + }, + { + "name": "EntryDate", + "type": "string" + }, + { + "name": "GenerationInstanceID", + "type": "string" + }, + { + "name": "ESIEnvironment", + "type": "string" + }, + { + "name": "Section", + "type": "string" + }, + { + "name": "ExecutionResult", + "type": "string" + }, + { + "name": "Identity", + "type": "string" + }, + { + "name": "IdentityString", + "type": "string" + }, + { + "name": "rawData", + "type": "string" + }, + { + "name": "Name", + "type": "string" + }, + { + "name": "ProcessedByServer", + "type": "string" + }, + { + "name": "PSCmdL", + "type": "string" + }, + { + "name": "WhenChanged", + "type": "datetime" + }, + { + "name": "WhenCreated", + "type": "datetime" + } + ] + } + }, + "destinations": { + "logAnalytics": [ + { + "workspaceResourceId": "[variables('workspaceResourceId')]", + "name": "ESIWorkspace" + } + ] + }, + "dataFlows": [ + { + "streams": [ + "Custom-ESIExchangeConfig" + ], + "destinations": [ + "ESIWorkspace" + ], + "transformKql": "source | extend TimeGenerated = now() | extend _id = parse_json(Identity) | extend Identity_Depth_d = toreal(_id.Depth), Identity_DistinguishedName_s = tostring(_id.DistinguishedName), Identity_DomainId_s = tostring(_id.DomainId), Identity_IsDeleted_b = tobool(_id.IsDeleted), Identity_IsRelativeDn_b = tobool(_id.IsRelativeDn), Identity_Name_s = tostring(_id.Name), Identity_ObjectGuid_g = tostring(_id.ObjectGuid), Identity_Parent_s = tostring(_id.Parent), Identity_PartitionFQDN_s = tostring(_id.PartitionFQDN), Identity_PartitionGuid_g = tostring(_id.PartitionGuid), Identity_Rdn_s = tostring(_id.Rdn) | project-away _id | project-rename EntryDate_s = EntryDate, GenerationInstanceID_g = GenerationInstanceID, ESIEnvironment_s = ESIEnvironment, Section_s = Section, ExecutionResult_s = ExecutionResult, Identity_s = Identity, IdentityString_s = IdentityString, RawData_s = rawData, Name_s = Name, ProcessedByServer_s = ProcessedByServer, PSCmdL_s = PSCmdL, WhenChanged_t = WhenChanged, WhenCreated_t = WhenCreated", + "outputStream": "[variables('onPremConfigoutputStream')]" + } + ] + } + }, + { + "condition": "[and(parameters('deployDataCollection'), parameters('deployOnlineConfigTable'))]", + "type": "Microsoft.Insights/dataCollectionRules", + "apiVersion": "2024-03-11", + "name": "[parameters('dataCollectionRuleOnlineConfigName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[variables('dataCollectionEndpointId')]", + "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), variables('configTableName'))]" + ], + "properties": { + "dataCollectionEndpointId": "[variables('dataCollectionEndpointId')]", + "streamDeclarations": { + "Custom-ESIExchangeOnlineConfig": { + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime" + }, + { + "name": "EntryDate", + "type": "string" + }, + { + "name": "GenerationInstanceID", + "type": "string" + }, + { + "name": "ESIEnvironment", + "type": "string" + }, + { + "name": "Section", + "type": "string" + }, + { + "name": "ExecutionResult", + "type": "string" + }, + { + "name": "Identity", + "type": "string" + }, + { + "name": "IdentityString", + "type": "string" + }, + { + "name": "rawData", + "type": "string" + }, + { + "name": "Name", + "type": "string" + }, + { + "name": "ProcessedByServer", + "type": "string" + }, + { + "name": "PSCmdL", + "type": "string" + }, + { + "name": "WhenChanged", + "type": "datetime" + }, + { + "name": "WhenCreated", + "type": "datetime" + } + ] + } + }, + "destinations": { + "logAnalytics": [ + { + "workspaceResourceId": "[variables('workspaceResourceId')]", + "name": "ESIWorkspace" + } + ] + }, + "dataFlows": [ + { + "streams": [ + "Custom-ESIExchangeOnlineConfig" + ], + "destinations": [ + "ESIWorkspace" + ], + "transformKql": "source | extend TimeGenerated = now() | extend _id = parse_json(Identity) | extend Identity_Depth_d = toreal(_id.Depth), Identity_DistinguishedName_s = tostring(_id.DistinguishedName), Identity_DomainId_s = tostring(_id.DomainId), Identity_IsDeleted_b = tobool(_id.IsDeleted), Identity_IsRelativeDn_b = tobool(_id.IsRelativeDn), Identity_Name_s = tostring(_id.Name), Identity_ObjectGuid_g = tostring(_id.ObjectGuid), Identity_Parent_s = tostring(_id.Parent), Identity_PartitionFQDN_s = tostring(_id.PartitionFQDN), Identity_PartitionGuid_g = tostring(_id.PartitionGuid), Identity_Rdn_s = tostring(_id.Rdn) | project-away _id | project-rename EntryDate_s = EntryDate, GenerationInstanceID_g = GenerationInstanceID, ESIEnvironment_s = ESIEnvironment, Section_s = Section, ExecutionResult_s = ExecutionResult, Identity_s = Identity, IdentityString_s = IdentityString, RawData_s = rawData, Name_s = Name, ProcessedByServer_s = ProcessedByServer, PSCmdL_s = PSCmdL, WhenChanged_t = WhenChanged, WhenCreated_t = WhenCreated", + "outputStream": "[variables('onlineConfigoutputStream')]" + } + ] + } + }, + { + "condition": "[and(parameters('deployDataCollection'), parameters('deployMessageTrackingTable'))]", + "type": "Microsoft.Insights/dataCollectionRules", + "apiVersion": "2024-03-11", + "name": "[parameters('dataCollectionRuleMessageTrackingName')]", + "location": "[parameters('location')]", + "dependsOn": [ + "[variables('dataCollectionEndpointId')]", + "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), variables('messageTrackingTableName'))]" + ], + "properties": { + "dataCollectionEndpointId": "[variables('dataCollectionEndpointId')]", + "streamDeclarations": { + "Custom-ExchangeOnlineMessageTracking": { + "columns": [ + { + "name": "TimeGenerated", + "type": "datetime" + }, + { + "name": "schemaVersion", + "type": "string" + }, + { + "name": "clientIp", + "type": "string" + }, + { + "name": "clientHostname", + "type": "string" + }, + { + "name": "serverIp", + "type": "string" + }, + { + "name": "senderHostname", + "type": "string" + }, + { + "name": "sourceContext", + "type": "string" + }, + { + "name": "connectorId", + "type": "string" + }, + { + "name": "source", + "type": "string" + }, + { + "name": "eventId", + "type": "string" + }, + { + "name": "internalMessageId", + "type": "string" + }, + { + "name": "messageId", + "type": "string" + }, + { + "name": "networkMessageId", + "type": "string" + }, + { + "name": "recipientAddress", + "type": "string" + }, + { + "name": "recipientStatus", + "type": "string" + }, + { + "name": "totalBytes", + "type": "long" + }, + { + "name": "recipientCount", + "type": "int" + }, + { + "name": "relatedRecipientAddress", + "type": "string" + }, + { + "name": "reference", + "type": "string" + }, + { + "name": "messageSubject", + "type": "string" + }, + { + "name": "senderAddress", + "type": "string" + }, + { + "name": "returnPath", + "type": "string" + }, + { + "name": "directionality", + "type": "string" + }, + { + "name": "messageInfo", + "type": "string" + }, + { + "name": "originalClientIp", + "type": "string" + }, + { + "name": "originalServerIp", + "type": "string" + }, + { + "name": "customData", + "type": "string" + }, + { + "name": "transportTrafficType", + "type": "string" + }, + { + "name": "FilePath", + "type": "string" + }, + { + "name": "logId", + "type": "string" + }, + { + "name": "messageTrackingTenantId", + "type": "string" + } + ] + } + }, + "destinations": { + "logAnalytics": [ + { + "workspaceResourceId": "[variables('workspaceResourceId')]", + "name": "ESIWorkspace" + } + ] + }, + "dataFlows": [ + { + "streams": [ + "Custom-ExchangeOnlineMessageTracking" + ], + "destinations": [ + "ESIWorkspace" + ], + "transformKql": "source | extend TimeGenerated = now() | project-rename schemaVersion_s = schemaVersion, clientIp_s = clientIp, clientHostname_s = clientHostname, serverIp_s = serverIp, senderHostname_s = senderHostname, sourceContext_s = sourceContext, connectorId_s = connectorId, source_s = source, eventId_s = eventId, internalMessageId_s = internalMessageId, messageId_s = messageId, networkMessageId_s = networkMessageId, recipientAddress_s = recipientAddress, recipientStatus_s = recipientStatus, totalBytes_l = totalBytes, recipientCount_i = recipientCount, relatedRecipientAddress_s = relatedRecipientAddress, reference_s = reference, messageSubject_s = messageSubject, senderAddress_s = senderAddress, returnPath_s = returnPath, directionality_s = directionality, messageInfo_s = messageInfo, originalClientIp_s = originalClientIp, originalServerIp_s = originalServerIp, customData_s = customData, transportTrafficType_s = transportTrafficType, FilePath_s = FilePath, logId_s = logId, messageTrackingTenantId_s = messageTrackingTenantId", + "outputStream": "Custom-ExchangeOnlineMessageTracking_CL" + } + ] + } + } + ], + "outputs": { + "dataCollectionEndpointId": { + "type": "string", + "value": "[if(parameters('deployDataCollection'), variables('dataCollectionEndpointId'), 'Not deployed')]" + }, + "dataCollectionEndpointUri": { + "type": "string", + "value": "[if(parameters('deployDataCollection'), reference(variables('dataCollectionEndpointId'), '2024-03-11').logsIngestion.endpoint, 'Not deployed')]" + }, + "dataCollectionRuleOnPremisesConfigId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployOnPremConfigTable')), resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleOnPremisesConfigName')), 'Not deployed')]" + }, + "dataCollectionRuleOnPremisesConfigImmutableId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployOnPremConfigTable')), reference(resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleOnPremisesConfigName')), '2024-03-11').immutableId, 'Not deployed')]" + }, + "dataCollectionRuleOnlineConfigId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployOnlineConfigTable')), resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleOnlineConfigName')), 'Not deployed')]" + }, + "dataCollectionRuleOnlineConfigImmutableId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployOnlineConfigTable')), reference(resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleOnlineConfigName')), '2024-03-11').immutableId, 'Not deployed')]" + }, + "dataCollectionRuleMessageTrackingId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployMessageTrackingTable')), resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleMessageTrackingName')), 'Not deployed')]" + }, + "dataCollectionRuleMessageTrackingImmutableId": { + "type": "string", + "value": "[if(and(parameters('deployDataCollection'), parameters('deployMessageTrackingTable')), reference(resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleMessageTrackingName')), '2024-03-11').immutableId, 'Not deployed')]" + }, + "configTableName": { + "type": "string", + "value": "[if(and(parameters('deployTables'), parameters('deployOnlineConfigTable')), variables('configTableName'), 'Not deployed')]" + }, + "onPremConfigTableName": { + "type": "string", + "value": "[if(and(parameters('deployTables'), parameters('deployOnPremConfigTable')), variables('onPremConfigTableName'), 'Not deployed')]" + }, + "messageTrackingTableName": { + "type": "string", + "value": "[if(and(parameters('deployTables'), parameters('deployMessageTrackingTable')), variables('messageTrackingTableName'), 'Not deployed')]" + } + } +} diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/Data/Solution_MicrosoftExchangeSecurityExchangeOnline.json b/Solutions/Microsoft Exchange Security - Exchange Online/Data/Solution_MicrosoftExchangeSecurityExchangeOnline.json index 3e7ac71c50f..68d70d1333a 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/Data/Solution_MicrosoftExchangeSecurityExchangeOnline.json +++ b/Solutions/Microsoft Exchange Security - Exchange Online/Data/Solution_MicrosoftExchangeSecurityExchangeOnline.json @@ -4,20 +4,20 @@ "Logo": "", "Description": "The Exchange Security Audit and Configuration Insight solution analyze Exchange Online configuration and logs from a security lens to provide insights and alerts.\n\n**Underlying Microsoft Technologies used:**\n\nThis solution takes a dependency on the following technologies, and some of these dependencies either may be in [Preview](https://azure.microsoft.com/support/legal/preview-supplemental-terms/) state or might result in additional ingestion or operational costs:\n\na. [Custom logs ingestion via Data Collector REST API](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api?tabs=powershell)", "Data Connectors": [ - "Data Connectors/ESI-ExchangeOnlineCollector.json" + "Data Connectors/ESI-ExchangeOnlineCollector.json" ], "Parsers": [ - "Parsers/ExchangeConfiguration.yaml", - "Parsers/ExchangeEnvironmentList.yaml", - "Parsers/MESCheckOnlineVIP.yaml", - "Parsers/MESCompareDataMRA.yaml", - "Parsers/MESOfficeActivityLogs.yaml" + "Parsers/ExchangeConfiguration.yaml", + "Parsers/ExchangeEnvironmentList.yaml", + "Parsers/MESCheckOnlineVIP.yaml", + "Parsers/MESCompareDataMRA.yaml", + "Parsers/MESOfficeActivityLogs.yaml" ], - "Workbooks": [ + "Workbooks": [ "Workbooks/Microsoft Exchange Least Privilege with RBAC - Online.json", - "Workbooks/Microsoft Exchange Security Review - Online.json", - "Workbooks/Microsoft Exchange Admin Activity - Online.json", - "Workbooks/Microsoft Exchange Search AdminAuditLog - Online.json" + "Workbooks/Microsoft Exchange Security Review - Online.json", + "Workbooks/Microsoft Exchange Admin Activity - Online.json", + "Workbooks/Microsoft Exchange Search AdminAuditLog - Online.json" ], "Analytic Rules": [], "Watchlists": [ @@ -25,8 +25,8 @@ ], "WatchlistDescription": "ExchOnlineVIP Watchlists contains a list of VIP users identified in Exchange Online that would be more monitored than others. This watchlist is used in the Audit log workbooks to filter activities on those users.", "BasePath": "C:\\Github\\Azure-Sentinel\\Solutions\\Microsoft Exchange Security - Exchange Online", - "Version": "3.1.7", + "Version": "4.0.0", "Metadata": "SolutionMetadata.json", "TemplateSpec": true, "Is1Pconnector": false -} \ No newline at end of file +} diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/Package/4.0.0.zip b/Solutions/Microsoft Exchange Security - Exchange Online/Package/4.0.0.zip new file mode 100644 index 00000000000..9002f639fd7 Binary files /dev/null and b/Solutions/Microsoft Exchange Security - Exchange Online/Package/4.0.0.zip differ diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/Package/mainTemplate.json b/Solutions/Microsoft Exchange Security - Exchange Online/Package/mainTemplate.json index adc9e5725c3..377a74fd9fa 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/Package/mainTemplate.json +++ b/Solutions/Microsoft Exchange Security - Exchange Online/Package/mainTemplate.json @@ -73,7 +73,7 @@ "email": "support@microsoft.com", "_email": "[variables('email')]", "_solutionName": "Microsoft Exchange Security - Exchange Online", - "_solutionVersion": "3.1.7", + "_solutionVersion": "4.0.0", "solutionId": "microsoftsentinelcommunity.azure-sentinel-solution-esionline", "_solutionId": "[variables('solutionId')]", "uiConfigId1": "ESI-ExchangeOnlineCollector", @@ -83,7 +83,7 @@ "dataConnectorId1": "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/dataConnectors', variables('_dataConnectorContentId1'))]", "_dataConnectorId1": "[variables('dataConnectorId1')]", "dataConnectorTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-dc-',uniquestring(variables('_dataConnectorContentId1'))))]", - "dataConnectorVersion1": "1.1.1", + "dataConnectorVersion1": "2.0.0", "_dataConnectorcontentProductId1": "[concat(take(variables('_solutionId'),50),'-','dc','-', uniqueString(concat(variables('_solutionId'),'-','DataConnector','-',variables('_dataConnectorContentId1'),'-', variables('dataConnectorVersion1'))))]", "parserObject1": { "_parserName1": "[concat(parameters('workspace'),'/','ExchangeConfiguration Data Parser')]", @@ -159,7 +159,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security - Exchange Online data connector with template version 3.1.7", + "description": "Microsoft Exchange Security - Exchange Online data connector with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('dataConnectorVersion1')]", @@ -175,7 +175,7 @@ "properties": { "connectorUiConfig": { "id": "[variables('_uiConfigId1')]", - "title": "Exchange Security Insights Online Collector (using Azure Functions)", + "title": "Exchange Security Insights Online Collector", "publisher": "Microsoft", "descriptionMarkdown": "Connector used to push Exchange Online Security configuration for Microsoft Sentinel Analysis", "graphQueries": [ @@ -233,6 +233,10 @@ } ], "customs": [ + { + "name": "Azure Log Analytics HTTP Data Collector API will be deprecated", + "description": "Azure Log Analytics HTTP Data Collector API will be deprecated, Update to Log Monitor API is REQUIRED. [Learn more](https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI)" + }, { "name": "Microsoft.Web/sites permissions", "description": "Read and write permissions to Azure Functions to create a Function App is required. [See the documentation to learn more about Azure Functions](https://docs.microsoft.com/azure/azure-functions/)." @@ -261,7 +265,7 @@ "instructions": [ { "parameters": { - "text": "

NOTE - UPDATE:

We recommend to Update the Collector to Version 7.6.0.0 or highier.
The Collector Script Update procedure could be found here : ESI Online Collector Update", + "text": "

NOTE - UPDATE:

We recommend to Update the Collector to Version 8.0.0.0 or higher AND switching to new Log Monitor API.
The Collector Script Update procedure could be found here : ESI Online Collector Update, Update to Log Monitor API procedure could be found here : Migrate From Log Analytics API To Log Ingestion API.
The new version of the Collector is using DCR (Data Collection Rules) for data collection. You need to configure DCR before running the script.", "visible": true, "inline": false }, @@ -270,7 +274,10 @@ ] }, { - "description": ">**NOTE:** This data connector depends on a parser based on a Kusto Function to work as expected. Follow the steps for each Parser to create the Kusto Functions alias : [**ExchangeConfiguration**](https://aka.ms/sentinel-ESI-ExchangeConfiguration-Online-parser) and [**ExchangeEnvironmentList**](https://aka.ms/sentinel-ESI-ExchangeEnvironmentList-Online-parser) \n\n**STEP 1 - Parsers deployment**", + "description": "**STEP 1 - Deploy DCE/DCR for Azure Monitor using Azure Resource Manager (ARM) Template**\n\n1. Click the **Deploy to Azure** button below. \n\n\t[![Deploy To Azure](https://aka.ms/deploytoazurebutton)](https://aka.ms/sentinel-ESI-LogMonitor-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the required fields'. \n>4. Mark the checkbox labeled **I agree to the terms and conditions stated above**. \n5. Click **Purchase** to deploy." + }, + { + "description": ">**NOTE:** This data connector depends on a parser based on a Kusto Function to work as expected. Follow the steps for each Parser to create the Kusto Functions alias : [**ExchangeConfiguration**](https://aka.ms/sentinel-ESI-ExchangeConfiguration-Online-parser) and [**ExchangeEnvironmentList**](https://aka.ms/sentinel-ESI-ExchangeEnvironmentList-Online-parser) \n\n**STEP 2 - Parsers deployment**", "instructions": [ { "parameters": { @@ -302,7 +309,7 @@ "description": ">**NOTE:** This connector uses Azure Automation to connect to 'Exchange Online' to pull its Security analysis into Microsoft Sentinel. This might result in additional data ingestion costs. Check the [Azure Automation pricing page](https://azure.microsoft.com/pricing/details/automation/) for details." }, { - "description": "**STEP 2 - Choose ONE from the following two deployment options to deploy the connector and the associated Azure Automation**\n\n>**IMPORTANT:** Before deploying the 'ESI Exchange Online Security Configuration' connector, have the Workspace ID and Workspace Primary Key (can be copied from the following), as well as the Exchange Online tenant name (contoso.onmicrosoft.com), readily available.", + "description": "**STEP 3 - Choose ONE from the following two deployment options to deploy the connector and the associated Azure Automation**\n\n>**IMPORTANT:** Before deploying the 'ESI Exchange Online Security Configuration' connector, have the Workspace ID and Workspace Primary Key (can be copied from the following), as well as the Exchange Online tenant name (contoso.onmicrosoft.com), readily available.", "instructions": [ { "parameters": { @@ -372,7 +379,7 @@ ] }, { - "description": "**STEP 3 - Assign Microsoft Graph Permission and Exchange Online Permission to Managed Identity Account** \n\nTo be able to collect Exchange Online information and to be able to retrieve User information and memberlist of admin groups, the automation account need multiple permission.", + "description": "**STEP 4 - Assign Microsoft Graph Permission and Exchange Online Permission to Managed Identity Account** \n\nTo be able to collect Exchange Online information and to be able to retrieve User information and memberlist of admin groups, the automation account need multiple permission.", "instructions": [ { "parameters": { @@ -403,7 +410,7 @@ ], "metadata": { "id": "fe7ccc48-e21b-4b90-b83e-9c8a6cb17d2f", - "version": "1.1.1", + "version": "2.0.0", "kind": "dataConnector", "source": { "kind": "solution", @@ -455,7 +462,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('_dataConnectorContentId1')]", "contentKind": "DataConnector", - "displayName": "Exchange Security Insights Online Collector (using Azure Functions)", + "displayName": "Exchange Security Insights Online Collector", "contentProductId": "[variables('_dataConnectorcontentProductId1')]", "id": "[variables('_dataConnectorcontentProductId1')]", "version": "[variables('dataConnectorVersion1')]" @@ -498,7 +505,7 @@ "kind": "GenericUI", "properties": { "connectorUiConfig": { - "title": "Exchange Security Insights Online Collector (using Azure Functions)", + "title": "Exchange Security Insights Online Collector", "publisher": "Microsoft", "descriptionMarkdown": "Connector used to push Exchange Online Security configuration for Microsoft Sentinel Analysis", "graphQueries": [ @@ -556,6 +563,10 @@ } ], "customs": [ + { + "name": "Azure Log Analytics HTTP Data Collector API will be deprecated", + "description": "Azure Log Analytics HTTP Data Collector API will be deprecated, Update to Log Monitor API is REQUIRED. [Learn more](https://aka.ms/MES-Migrate_From_LogAnalyticsAPI_To_LogIngestionAPI)" + }, { "name": "Microsoft.Web/sites permissions", "description": "Read and write permissions to Azure Functions to create a Function App is required. [See the documentation to learn more about Azure Functions](https://docs.microsoft.com/azure/azure-functions/)." @@ -584,7 +595,7 @@ "instructions": [ { "parameters": { - "text": "

NOTE - UPDATE:

We recommend to Update the Collector to Version 7.6.0.0 or highier.
The Collector Script Update procedure could be found here : ESI Online Collector Update", + "text": "

NOTE - UPDATE:

We recommend to Update the Collector to Version 8.0.0.0 or higher AND switching to new Log Monitor API.
The Collector Script Update procedure could be found here : ESI Online Collector Update, Update to Log Monitor API procedure could be found here : Migrate From Log Analytics API To Log Ingestion API.
The new version of the Collector is using DCR (Data Collection Rules) for data collection. You need to configure DCR before running the script.", "visible": true, "inline": false }, @@ -593,7 +604,10 @@ ] }, { - "description": ">**NOTE:** This data connector depends on a parser based on a Kusto Function to work as expected. Follow the steps for each Parser to create the Kusto Functions alias : [**ExchangeConfiguration**](https://aka.ms/sentinel-ESI-ExchangeConfiguration-Online-parser) and [**ExchangeEnvironmentList**](https://aka.ms/sentinel-ESI-ExchangeEnvironmentList-Online-parser) \n\n**STEP 1 - Parsers deployment**", + "description": "**STEP 1 - Deploy DCE/DCR for Azure Monitor using Azure Resource Manager (ARM) Template**\n\n1. Click the **Deploy to Azure** button below. \n\n\t[![Deploy To Azure](https://aka.ms/deploytoazurebutton)](https://aka.ms/sentinel-ESI-LogMonitor-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the required fields'. \n>4. Mark the checkbox labeled **I agree to the terms and conditions stated above**. \n5. Click **Purchase** to deploy." + }, + { + "description": ">**NOTE:** This data connector depends on a parser based on a Kusto Function to work as expected. Follow the steps for each Parser to create the Kusto Functions alias : [**ExchangeConfiguration**](https://aka.ms/sentinel-ESI-ExchangeConfiguration-Online-parser) and [**ExchangeEnvironmentList**](https://aka.ms/sentinel-ESI-ExchangeEnvironmentList-Online-parser) \n\n**STEP 2 - Parsers deployment**", "instructions": [ { "parameters": { @@ -625,7 +639,7 @@ "description": ">**NOTE:** This connector uses Azure Automation to connect to 'Exchange Online' to pull its Security analysis into Microsoft Sentinel. This might result in additional data ingestion costs. Check the [Azure Automation pricing page](https://azure.microsoft.com/pricing/details/automation/) for details." }, { - "description": "**STEP 2 - Choose ONE from the following two deployment options to deploy the connector and the associated Azure Automation**\n\n>**IMPORTANT:** Before deploying the 'ESI Exchange Online Security Configuration' connector, have the Workspace ID and Workspace Primary Key (can be copied from the following), as well as the Exchange Online tenant name (contoso.onmicrosoft.com), readily available.", + "description": "**STEP 3 - Choose ONE from the following two deployment options to deploy the connector and the associated Azure Automation**\n\n>**IMPORTANT:** Before deploying the 'ESI Exchange Online Security Configuration' connector, have the Workspace ID and Workspace Primary Key (can be copied from the following), as well as the Exchange Online tenant name (contoso.onmicrosoft.com), readily available.", "instructions": [ { "parameters": { @@ -695,7 +709,7 @@ ] }, { - "description": "**STEP 3 - Assign Microsoft Graph Permission and Exchange Online Permission to Managed Identity Account** \n\nTo be able to collect Exchange Online information and to be able to retrieve User information and memberlist of admin groups, the automation account need multiple permission.", + "description": "**STEP 4 - Assign Microsoft Graph Permission and Exchange Online Permission to Managed Identity Account** \n\nTo be able to collect Exchange Online information and to be able to retrieve User information and memberlist of admin groups, the automation account need multiple permission.", "instructions": [ { "parameters": { @@ -737,7 +751,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "ExchangeConfiguration Data Parser with template version 3.1.7", + "description": "ExchangeConfiguration Data Parser with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject1').parserVersion1]", @@ -746,7 +760,7 @@ "resources": [ { "name": "[variables('parserObject1')._parserName1]", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "type": "Microsoft.OperationalInsights/workspaces/savedSearches", "location": "[parameters('workspace-location')]", "properties": { @@ -810,7 +824,7 @@ }, { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "name": "[variables('parserObject1')._parserName1]", "location": "[parameters('workspace-location')]", "properties": { @@ -867,7 +881,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "ExchangeEnvironmentList Data Parser with template version 3.1.7", + "description": "ExchangeEnvironmentList Data Parser with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject2').parserVersion2]", @@ -876,7 +890,7 @@ "resources": [ { "name": "[variables('parserObject2')._parserName2]", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "type": "Microsoft.OperationalInsights/workspaces/savedSearches", "location": "[parameters('workspace-location')]", "properties": { @@ -940,7 +954,7 @@ }, { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "name": "[variables('parserObject2')._parserName2]", "location": "[parameters('workspace-location')]", "properties": { @@ -997,7 +1011,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "MESCheckOnlineVIP Data Parser with template version 3.1.7", + "description": "MESCheckOnlineVIP Data Parser with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject3').parserVersion3]", @@ -1006,7 +1020,7 @@ "resources": [ { "name": "[variables('parserObject3')._parserName3]", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "type": "Microsoft.OperationalInsights/workspaces/savedSearches", "location": "[parameters('workspace-location')]", "properties": { @@ -1070,7 +1084,7 @@ }, { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "name": "[variables('parserObject3')._parserName3]", "location": "[parameters('workspace-location')]", "properties": { @@ -1127,7 +1141,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "MESCompareDataMRA Data Parser with template version 3.1.7", + "description": "MESCompareDataMRA Data Parser with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject4').parserVersion4]", @@ -1136,7 +1150,7 @@ "resources": [ { "name": "[variables('parserObject4')._parserName4]", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "type": "Microsoft.OperationalInsights/workspaces/savedSearches", "location": "[parameters('workspace-location')]", "properties": { @@ -1200,7 +1214,7 @@ }, { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "name": "[variables('parserObject4')._parserName4]", "location": "[parameters('workspace-location')]", "properties": { @@ -1257,7 +1271,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "MESOfficeActivityLogs Data Parser with template version 3.1.7", + "description": "MESOfficeActivityLogs Data Parser with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('parserObject5').parserVersion5]", @@ -1266,7 +1280,7 @@ "resources": [ { "name": "[variables('parserObject5')._parserName5]", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "type": "Microsoft.OperationalInsights/workspaces/savedSearches", "location": "[parameters('workspace-location')]", "properties": { @@ -1330,7 +1344,7 @@ }, { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2022-10-01", + "apiVersion": "2025-07-01", "name": "[variables('parserObject5')._parserName5]", "location": "[parameters('workspace-location')]", "properties": { @@ -1387,7 +1401,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Least Privilege with RBAC - Online Workbook with template version 3.1.7", + "description": "Microsoft Exchange Least Privilege with RBAC - Online Workbook with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion1')]", @@ -1405,7 +1419,7 @@ }, "properties": { "displayName": "[parameters('workbook1-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"e59f0f7f-fd05-4ec8-9f59-e4d9c3b589f2\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Current RBAC Delegation\",\"subTarget\":\"RBACDelegation\",\"preText\":\"RBAC Delegation\",\"postText\":\"\",\"style\":\"link\"},{\"id\":\"26056188-7abf-4913-a927-806099e616eb\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Custom Roles\",\"subTarget\":\"CustomRole\",\"style\":\"link\"},{\"id\":\"5eeebe10-be67-4f8a-9d91-4bc6c70c3e16\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Workbook Help\",\"subTarget\":\"start\",\"style\":\"link\"}]},\"name\":\"links - 3\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"9ae328d6-99c8-4c44-8d59-42ca4d999098\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"EnvironmentList\",\"label\":\"Environment\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"ExchangeEnvironmentList(Target=\\\"Online\\\") | where ESIEnvironment != \\\"\\\"\",\"typeSettings\":{\"limitSelectTo\":1,\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"a88b4e41-eb2f-41bf-92d8-27c83650a4b8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"DateOfConfiguration\",\"label\":\"Collection time\",\"type\":2,\"isRequired\":true,\"query\":\"let _configurationEnv = split(iff(isnull({EnvironmentList}) or isempty({EnvironmentList}) or tolower({EnvironmentList}) == \\\"all\\\",\\\"All\\\",tostring({EnvironmentList})),',');\\r\\nESIExchangeOnlineConfig_CL\\r\\n| extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n| where ScopedEnvironment in (_configurationEnv)\\r\\n| extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n| summarize Collection = max(Collection)\\r\\n| project Collection = \\\"lastdate\\\", Selected = true\\r\\n| join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | summarize by Collection \\r\\n | join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm ')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | join kind=leftouter (\\r\\n ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | summarize count() by Collection\\r\\n ) on Collection\\r\\n ) on Collection\\r\\n) on Collection\\r\\n| project Value = iif(Selected,Collection,iif(count_ > 1,PreciseCollection,Collection1)), Label = iif(Selected,\\\"Last Known date\\\",iif(count_ > 1,PreciseCollection,Collection1)), Selected\\r\\n| sort by Selected, Value desc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"8ac96eb3-918b-4a36-bcc4-df50d8f46175\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Help\",\"label\":\"Show Help\",\"type\":10,\"isRequired\":true,\"query\":\"{\\\"version\\\":\\\"1.0.0\\\",\\\"content\\\":\\\"[\\\\r\\\\n { \\\\\\\"value\\\\\\\": \\\\\\\"Yes\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"Yes\\\\\\\"},\\\\r\\\\n {\\\\\\\"value\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"selected\\\\\\\":true }\\\\r\\\\n]\\\\r\\\\n\\\"}\\r\\n\",\"timeContext\":{\"durationMs\":2592000000},\"queryType\":8}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"TimeRange\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Custom Delegation\",\"items\":[{\"type\":1,\"content\":{\"json\":\"The current delegation are compared to an export of default delegation available on Exchange Online.\\r\\n\\r\\nTo find which is used for the comparaison please follow this link.\\r\\nThe export is located on the public GitHub of the project.\\r\\n\\r\\ncheck this link : https://aka.ms/esiwatchlist\\r\\n\\r\\nIt will be updated by the team project.\",\"style\":\"info\"},\"name\":\"text - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Custom Delegation on User Accounts\",\"items\":[{\"type\":1,\"content\":{\"json\":\" Custom Delegation on User Accounts\"},\"name\":\"text - 2 - Copy\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"d9d4e0a2-b75d-4825-9f4e-7606516500e1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"RoleAssignee\",\"type\":2,\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://aka.ms/standardMRAOnline\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"User\\\"\\r\\n| project CmdletResultValue\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| distinct RoleAssigneeName\\r\\n\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"cf5959fa-a833-4bb2-90bd-d4c90dca5506\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Role\",\"type\":2,\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://aka.ms/standardMRAOnline\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"User\\\"\\r\\n| project CmdletResultValue\\r\\n| extend Role=tostring (CmdletResultValue.Role)\\r\\n| distinct Role\\r\\n| sort by Role asc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://aka.ms/standardMRAOnline\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.RoleAssigneeName endswith \\\"{RoleAssignee}\\\" \\r\\n| where CmdletResultValue.Role contains \\\"{Role}\\\"\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"User\\\" and CmdletResultValue.Name !contains \\\"Deleg\\\"\\r\\n| project CmdletResultValue\\r\\n| extend Name = tostring(CmdletResultValue.Name)\\r\\n| extend Role = tostring(CmdletResultValue.Role)\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| project Name, Role, RoleAssigneeName,Status,CustomRecipientWriteScope,CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope\\r\\n| sort by RoleAssigneeName asc\\r\\n\",\"size\":3,\"showAnalytics\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"CmdletName\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"31.5ch\"}},{\"columnMatch\":\"Total\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"9.3ch\"}},{\"columnMatch\":\"Count\",\"formatter\":21,\"formatOptions\":{\"palette\":\"blue\",\"customColumnWidthSetting\":\"330px\"}},{\"columnMatch\":\"Anomalies\",\"formatter\":10,\"formatOptions\":{\"palette\":\"redBright\",\"customColumnWidthSetting\":\"330px\"}}],\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"RoleAssigneeName\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"RoleAssigneeName\",\"sortOrder\":1}]},\"name\":\"query - 2\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Custom Delegation on User Accounts\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Explanations\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section displays all the nonstandard delegations done directly to a user account.\\r\\n\\r\\nDetailed information for the user accounts will be displayed.\\r\\n\\r\\nThis status is done by comparing current delegation with the default delegation for Exchange 2019 CU11.\\r\\n\\r\\nThese types of delegations are not available on the Exchange Admin Center.\\r\\n\\r\\nUsual results :\\r\\n\\r\\n - Delegations done directly to service account. Being able to see this delegation will help to sanityze the environment as some delegations may be no more necessary\\r\\n\\r\\n - Delegation done by mistake directly to Administrator Accounts\\r\\n\\r\\n - Suspicious delegations\\r\\n\\r\\n\\r\\nDetailed information for the user accounts will be displayed in below sections\\r\\n\"},\"name\":\"text - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"group - 3\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Custom Delegation on Groups\",\"items\":[{\"type\":1,\"content\":{\"json\":\"Custom Delegation on Groups\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"c548eb09-54e3-41bf-a99d-be3534f7018b\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"RoleAssignee\",\"type\":2,\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://aka.ms/standardMRAOnline\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"RoleGroup\\\" and CmdletResultValue.RoleAssigneeName !contains \\\"RIM-MailboxAdmins\\\"\\r\\n| project CmdletResultValue\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| distinct RoleAssigneeName\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"f5511a2b-9bf6-48ae-a968-2d1f879c8bfa\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Role\",\"type\":2,\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://aka.ms/standardMRAOnline\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"RoleGroup\\\" and CmdletResultValue.RoleAssigneeName !contains \\\"RIM-MailboxAdmins\\\"\\r\\n| project CmdletResultValue\\r\\n| extend Role=tostring (CmdletResultValue.Role)\\r\\n| distinct Role\\r\\n| sort by Role asc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"MR-CustMailRecipients\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://aka.ms/standardMRAOnline\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nlet RoleG = ExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n | project RoleAssigneeName=tostring(CmdletResultValue.Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.RoleAssigneeName endswith \\\"{RoleAssignee}\\\" \\r\\n| where CmdletResultValue.Role contains \\\"{Role}\\\"\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"RoleGroup\\\" and CmdletResultValue.RoleAssigneeName !contains \\\"RIM-MailboxAdmins\\\" and CmdletResultValue.Name !contains \\\"Deleg\\\"\\r\\n| project CmdletResultValue\\r\\n| extend ManagementRoleAssignment = tostring(CmdletResultValue.Name)\\r\\n| extend Role = tostring(CmdletResultValue.Role)\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n|lookup RoleG on RoleAssigneeName \\r\\n| project-away CmdletResultValue\\r\\n| sort by RoleAssigneeName asc\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"RoleAssigneeName\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"RoleAssigneeName\",\"sortOrder\":1}]},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Custom Delegation on Groups\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Explanations\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section displays all the nonstandard delegations done for standard and non standard groups. Indeed, default groups have a list of default delegations but an Exchange administrators can add also new roles to the default groups.\\r\\n\\r\\nThis status is done by comparing current delegation with the default delegation for Exchange 2019 CU11.\\r\\n\\r\\nUsual results :\\r\\n\\r\\n - Delegations done for Organization Management to role like Mailbox Import Export or Mailbox Search\\r\\n\\r\\n - Delegation done by mistake\\r\\n\\r\\n - Suspicious delegations\\r\\n\\r\\nDetailed information for the user accounts present in the groups will be displayed in below sections\\r\\n\"},\"name\":\"text - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"group - 4\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"RBACDelegation\"},\"name\":\"Custom Delegation\",\"styleSettings\":{\"showBorder\":true}},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"### How to user this tab\\r\\n**1 - Select an account** : All the Cmdlet launched by the account during the selected time frame will be displayer.\\r\\n\\r\\n**2 - Select a cmdlet** : All the roles that contain will be displayed\\r\\n\\r\\n**3 - Review the list of roles** : This table contains all the roles that contain the selected Cmdlet\\r\\n\\r\\n\",\"style\":\"info\"},\"name\":\"text - 1\"},{\"type\":1,\"content\":{\"json\":\"### How to undertand the \\\"List of Roles with this CmdLet\\\" table ? \\r\\n\\r\\n**WeightRole :** Display the wieight of this role based on its importance in terms of security risk\\r\\n\\r\\n**SumRole :** Among all the Cmdlet launched by the account during the defined time frame, this role available for x cmdlet. This role include x cmdlet run by the user.\\r\\n\\r\\n**OrgMgmtRole :** This role is really in the scope of Organization Management group. If the selected Cmdlet is not included is any other role, it make sense that this user is member of the Organization Management group\\r\\n\\r\\n \",\"style\":\"upsell\"},\"name\":\"text - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let CounUserCmdlet = (ExchangeAdminAuditLogs\\r\\n| where Status == \\\"Success\\\"\\r\\n| extend Caller = tostring(split(Caller,\\\"/\\\")[countof(Caller,\\\"/\\\")])\\r\\n| summarize Count=count() by Caller);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"ExGroup\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| search CmdletResultValue.Parentgroup == \\\"Organization Management\\\"\\r\\n| where CmdletResultValue.Level != 0\\r\\n| where CmdletResultValue.ObjectClass == \\\"user\\\"\\r\\n//| project CmdletResultValue,Count\\r\\n| extend Account = tostring(CmdletResultValue.SamAccountName)\\r\\n| join kind=leftouter (CounUserCmdlet) on $left.Account == $right.Caller\\r\\n| project Account,Count\\r\\n//| project-away CmdletResultValue\\r\\n| sort by Account asc\",\"size\":3,\"title\":\"Organization Management Members\",\"exportFieldName\":\"Account\",\"exportParameterName\":\"Account\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"formatters\":[{\"columnMatch\":\"Count\",\"formatter\":3,\"formatOptions\":{\"palette\":\"purple\"}}]}},\"customWidth\":\"20\",\"name\":\"query - 1\",\"styleSettings\":{\"maxWidth\":\"100%\",\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeAdminAuditLogs\\r\\n| where Caller contains \\\"{Account}\\\"\\r\\n| where Status == \\\"Success\\\"\\r\\n| distinct CmdletName\\r\\n| sort by CmdletName asc\",\"size\":3,\"title\":\"List of CmdLet run by the account\",\"exportFieldName\":\"CmdletName\",\"exportParameterName\":\"CmdletName\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"CmdletName\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"CmdletName\",\"sortOrder\":1}]},\"customWidth\":\"33\",\"name\":\"query - 3\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let RBACRoleCmdlet = _GetWatchlist('RBACRoleCmdlet');\\r\\nlet UserRoleList = ExchangeAdminAuditLogs | where Caller contains \\\"{Account}\\\" | where Status == \\\"Success\\\" | distinct CmdletName;\\r\\nlet countRole = (RBACRoleCmdlet | where Name has_any (UserRoleList)| summarize SumRole = count()by Role);\\r\\nlet RolevsCmdlet = (RBACRoleCmdlet | where Name has_any (UserRoleList)| summarize make_set(Name) by Role);\\r\\nRolevsCmdlet\\r\\n| join kind=leftouter ( countRole ) on Role\\r\\n| project Role,CmdletList=set_Name,SumRole\\r\\n| join kind=leftouter ( RBACRoleCmdlet ) on Role\\r\\n| where Name has \\\"{CmdletName}\\\"\\r\\n| extend PossibleRoles = Role\\r\\n| extend OrgMgmtRole = OrgM\\r\\n| extend RoleWeight = Priority\\r\\n|distinct PossibleRoles,RoleWeight,tostring(SumRole),OrgMgmtRole,tostring(CmdletList)\\r\\n|sort by SumRole,RoleWeight\\r\\n\",\"size\":3,\"title\":\"List of Roles with this CmdLet\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"PossibleRoles\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"PossibleRoles\",\"sortOrder\":1}]},\"customWidth\":\"40\",\"name\":\"query - 3\",\"styleSettings\":{\"margin\":\"0\",\"maxWidth\":\"100%\",\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let RBACRoleCmdlet = _GetWatchlist('RBACRoleCmdlet');\\r\\nlet UserRoleList = ExchangeAdminAuditLogs | where TimeGenerated {TimeRange} | where Caller contains \\\"{Account}\\\" | where Status == \\\"Success\\\" | distinct CmdletName;\\r\\nlet countRole = (RBACRoleCmdlet | where Name has_any (UserRoleList)| summarize SumRole = count()by Role);\\r\\nlet RolevsCmdlet = (RBACRoleCmdlet | where Name has_any (UserRoleList)| summarize make_set(Name) by Role);\\r\\nRolevsCmdlet\\r\\n| join kind=leftouter ( countRole ) on Role\\r\\n| project Role,CmdletList=set_Name,SumRole\\r\\n| join kind=leftouter ( RBACRoleCmdlet ) on Role\\r\\n| extend Roles = Role\\r\\n| extend OrgMgmtRole = OrgM\\r\\n| extend RoleWeight = Priority\\r\\n| extend CmdletList=tostring(CmdletList)\\r\\n| summarize by Roles,CmdletList,RoleWeight,tostring(SumRole),OrgMgmtRole\\r\\n| distinct Roles,RoleWeight,tostring(SumRole),OrgMgmtRole,tostring(CmdletList)\\r\\n|sort by Roles asc\",\"size\":0,\"title\":\"Recommended Roles for selected users\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"Roles\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"Roles\",\"sortOrder\":1}]},\"name\":\"query - 3\"}]},\"name\":\"group - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Leastprivileges\"},\"name\":\"group - 5\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Custom Role details\",\"items\":[{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"List of Custom Roles\",\"items\":[{\"type\":1,\"content\":{\"json\":\"List of existing custom Roles\"},\"customWidth\":\"50\",\"name\":\"text - 3\"},{\"type\":1,\"content\":{\"json\":\"List of Custom with a Management Role Assignement (associated with a group or a user). Display the target account and scope if set\"},\"customWidth\":\"50\",\"name\":\"text - 4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend Identity = CmdletResultValue.Name\\r\\n| extend ParentRole =split(tostring(CmdletResultValue.Parent),\\\"\\\\\\\\\\\")[1]\\r\\n| project Identity, ParentRole, WhenCreated, WhenChanged\",\"size\":3,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"customWidth\":\"50\",\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MRcustomRoles = (ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project Role = tostring(CmdletResultValue.Name));\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend Role = tostring(CmdletResultValue.Role)\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend Scope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| project Role, Scope, RoleAssigneeName\\r\\n| join kind=inner (MRcustomRoles) on Role\\r\\n| project Role,RoleAssigneeName,Scope\",\"size\":1,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"customWidth\":\"50\",\"name\":\"query - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MRcustomRoles = (ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project Role = tostring(CmdletResultValue.Name));\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\", SpecificConfigurationDate=\\\"lastdate\\\", SpecificConfigurationEnv='ITSY', Target = \\\"Online\\\")\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend Scope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| project Role= tostring(CmdletResultValue.Role), Scope, RoleAssigneeName\\r\\n| join kind=rightouter (MRcustomRoles) on Role\\r\\n| project Role = Role1, Scope, RoleAssigneeName,Comment = iff(Role == \\\"\\\", \\\"⚠️ No existing delegation for this role\\\", \\\"✅ This role is delegated with a Management Role Assignment\\\")\",\"size\":0,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"customWidth\":\"50\",\"name\":\"query - 4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MRcustomRoles = (ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project Role = tostring(CmdletResultValue.Name));\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project Role = tostring(CmdletResultValue.Role)\\r\\n| join kind=rightouter (MRcustomRoles) on Role\\r\\n| summarize acount = count() by iff( Role==\\\"\\\",\\\"Number of non assigned roles\\\", Role)\",\"size\":0,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 5\"}]},\"name\":\"List of Custom Roles\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Custom Roles delegation on group\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section shows delegation associated with the Custom Roles\"},\"name\":\"text - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MRcustomRoles = (ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project Role = tostring(CmdletResultValue.Name));\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend Role = tostring(CmdletResultValue.Role)\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n| project RoleAssigneeName, Role, Status,CustomRecipientWriteScope, CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope,WhenCreated, WhenChanged\\r\\n| join kind=inner (MRcustomRoles) on Role\\r\\n| project RoleAssigneeName, Role, Status,CustomRecipientWriteScope, CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope,WhenCreated, WhenChanged\",\"size\":3,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 1\"}]},\"name\":\"group - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Details for Custom Roles Cmdlets \",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section displays for the chosen custom management roles all Cmdlets and their parameters associated with this custom role.\\r\\nRemember that for a cmdlet, some parameters can be removed.\"},\"name\":\"text - 0\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"07c8ac83-371d-4702-ab66-72aeb2a20053\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"CustomRole\",\"type\":2,\"isRequired\":true,\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend Identity = CmdletResultValue.Name\\r\\n| project Identity\",\"typeSettings\":{\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"MR-CustPF\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRCustomDetails\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Role contains \\\"{CustomRole}\\\"\\r\\n| extend CmdletName = CmdletResultValue.Name\\r\\n| extend Parameters = CmdletResultValue.Parameters\\r\\n| project CmdletName,Parameters\",\"size\":1,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Details for Custom Roles Cmdlets \"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"CustomRole\"},\"name\":\"Custom Role\"}],\"fromTemplateId\":\"sentinel-MicrosoftExchangeLeastPrivilegewithRBAC-Online\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"e59f0f7f-fd05-4ec8-9f59-e4d9c3b589f2\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Current RBAC Delegation\",\"subTarget\":\"RBACDelegation\",\"preText\":\"RBAC Delegation\",\"postText\":\"\",\"style\":\"link\"},{\"id\":\"26056188-7abf-4913-a927-806099e616eb\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Custom Roles\",\"subTarget\":\"CustomRole\",\"style\":\"link\"},{\"id\":\"5eeebe10-be67-4f8a-9d91-4bc6c70c3e16\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Workbook Help\",\"subTarget\":\"start\",\"style\":\"link\"}]},\"name\":\"links - 3\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"9ae328d6-99c8-4c44-8d59-42ca4d999098\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"EnvironmentList\",\"label\":\"Environment\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"ExchangeEnvironmentList(Target=\\\"Online\\\") | where ESIEnvironment != \\\"\\\"\",\"typeSettings\":{\"limitSelectTo\":1,\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"a88b4e41-eb2f-41bf-92d8-27c83650a4b8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"DateOfConfiguration\",\"label\":\"Collection time\",\"type\":2,\"isRequired\":true,\"query\":\"let _configurationEnv = split(iff(isnull({EnvironmentList}) or isempty({EnvironmentList}) or tolower({EnvironmentList}) == \\\"all\\\",\\\"All\\\",tostring({EnvironmentList})),',');\\r\\nESIExchangeOnlineConfig_CL\\r\\n| extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n| where ScopedEnvironment in (_configurationEnv)\\r\\n| extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n| summarize Collection = max(Collection)\\r\\n| project Collection = \\\"lastdate\\\", Selected = true\\r\\n| join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | summarize by Collection \\r\\n | join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm ')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | join kind=leftouter (\\r\\n ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | summarize count() by Collection\\r\\n ) on Collection\\r\\n ) on Collection\\r\\n) on Collection\\r\\n| project Value = iif(Selected,Collection,iif(count_ > 1,PreciseCollection,Collection1)), Label = iif(Selected,\\\"Last Known date\\\",iif(count_ > 1,PreciseCollection,Collection1)), Selected\\r\\n| sort by Selected, Value desc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"2026-07-30\"},{\"id\":\"8ac96eb3-918b-4a36-bcc4-df50d8f46175\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Help\",\"label\":\"Show Help\",\"type\":10,\"isRequired\":true,\"query\":\"{\\\"version\\\":\\\"1.0.0\\\",\\\"content\\\":\\\"[\\\\r\\\\n { \\\\\\\"value\\\\\\\": \\\\\\\"Yes\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"Yes\\\\\\\"},\\\\r\\\\n {\\\\\\\"value\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"selected\\\\\\\":true }\\\\r\\\\n]\\\\r\\\\n\\\"}\\r\\n\",\"timeContext\":{\"durationMs\":2592000000},\"queryType\":8}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"TimeRange\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Custom Delegation\",\"items\":[{\"type\":1,\"content\":{\"json\":\"The current delegation are compared to an export of default delegation available on Exchange Online.\\r\\n\\r\\nTo find which is used for the comparaison please follow this link.\\r\\nThe export is located on the public GitHub of the project.\\r\\n\\r\\ncheck this link : https://aka.ms/esiwatchlist\\r\\n\\r\\nIt will be updated by the team project.\",\"style\":\"info\"},\"name\":\"text - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Custom Delegation on User Accounts\",\"items\":[{\"type\":1,\"content\":{\"json\":\" Custom Delegation on User Accounts\"},\"name\":\"text - 2 - Copy\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"d9d4e0a2-b75d-4825-9f4e-7606516500e1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"RoleAssignee\",\"type\":2,\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"User\\\"\\r\\n| project CmdletResultValue\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| distinct RoleAssigneeName\\r\\n\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"cf5959fa-a833-4bb2-90bd-d4c90dca5506\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Role\",\"type\":2,\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"User\\\"\\r\\n| project CmdletResultValue\\r\\n| extend Role=tostring (CmdletResultValue.Role)\\r\\n| distinct Role\\r\\n| sort by Role asc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.RoleAssigneeName endswith \\\"{RoleAssignee}\\\" \\r\\n| where CmdletResultValue.Role contains \\\"{Role}\\\"\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"User\\\" and CmdletResultValue.Name !contains \\\"Deleg\\\"\\r\\n| project CmdletResultValue\\r\\n| extend Name = tostring(CmdletResultValue.Name)\\r\\n| extend Role = tostring(CmdletResultValue.Role)\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| project Name, Role, RoleAssigneeName,Status,CustomRecipientWriteScope,CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope\\r\\n| sort by RoleAssigneeName asc\\r\\n\",\"size\":3,\"showAnalytics\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"CmdletName\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"31.5ch\"}},{\"columnMatch\":\"Total\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"9.3ch\"}},{\"columnMatch\":\"Count\",\"formatter\":21,\"formatOptions\":{\"palette\":\"blue\",\"customColumnWidthSetting\":\"330px\"}},{\"columnMatch\":\"Anomalies\",\"formatter\":10,\"formatOptions\":{\"palette\":\"redBright\",\"customColumnWidthSetting\":\"330px\"}}],\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"RoleAssigneeName\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"RoleAssigneeName\",\"sortOrder\":1}]},\"name\":\"query - 2\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Custom Delegation on User Accounts\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Explanations\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section displays all the nonstandard delegations done directly to a user account.\\r\\n\\r\\nDetailed information for the user accounts will be displayed.\\r\\n\\r\\nThis status is done by comparing current delegation with the default delegation for Exchange 2019 CU11.\\r\\n\\r\\nThese types of delegations are not available on the Exchange Admin Center.\\r\\n\\r\\nUsual results :\\r\\n\\r\\n - Delegations done directly to service account. Being able to see this delegation will help to sanityze the environment as some delegations may be no more necessary\\r\\n\\r\\n - Delegation done by mistake directly to Administrator Accounts\\r\\n\\r\\n - Suspicious delegations\\r\\n\\r\\n\\r\\nDetailed information for the user accounts will be displayed in below sections\\r\\n\"},\"name\":\"text - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"group - 3\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Custom Delegation on Groups\",\"items\":[{\"type\":1,\"content\":{\"json\":\"Custom Delegation on Groups\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"c548eb09-54e3-41bf-a99d-be3534f7018b\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"RoleAssignee\",\"type\":2,\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"RoleGroup\\\" and CmdletResultValue.RoleAssigneeName !contains \\\"RIM-MailboxAdmins\\\"\\r\\n| project CmdletResultValue\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| distinct RoleAssigneeName\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"f5511a2b-9bf6-48ae-a968-2d1f879c8bfa\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Role\",\"type\":2,\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"RoleGroup\\\" and CmdletResultValue.RoleAssigneeName !contains \\\"RIM-MailboxAdmins\\\"\\r\\n| project CmdletResultValue\\r\\n| extend Role=tostring (CmdletResultValue.Role)\\r\\n| distinct Role\\r\\n| sort by Role asc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let DefMRA = externaldata (Name:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| summarize make_list(Name);\\r\\nlet RoleG = ExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n | project RoleAssigneeName=tostring(CmdletResultValue.Name);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.RoleAssigneeName endswith \\\"{RoleAssignee}\\\" \\r\\n| where CmdletResultValue.Role contains \\\"{Role}\\\"\\r\\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \\\"RoleGroup\\\" and CmdletResultValue.RoleAssigneeName !contains \\\"RIM-MailboxAdmins\\\" and CmdletResultValue.Name !contains \\\"Deleg\\\"\\r\\n| project CmdletResultValue\\r\\n| extend ManagementRoleAssignment = tostring(CmdletResultValue.Name)\\r\\n| extend Role = tostring(CmdletResultValue.Role)\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n|lookup RoleG on RoleAssigneeName \\r\\n| project-away CmdletResultValue\\r\\n| sort by RoleAssigneeName asc\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"RoleAssigneeName\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"RoleAssigneeName\",\"sortOrder\":1}]},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Custom Delegation on Groups\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Explanations\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section displays all the nonstandard delegations done for standard and non standard groups. Indeed, default groups have a list of default delegations but an Exchange administrators can add also new roles to the default groups.\\r\\n\\r\\nThis status is done by comparing current delegation with the default delegation for Exchange 2019 CU11.\\r\\n\\r\\nUsual results :\\r\\n\\r\\n - Delegations done for Organization Management to role like Mailbox Import Export or Mailbox Search\\r\\n\\r\\n - Delegation done by mistake\\r\\n\\r\\n - Suspicious delegations\\r\\n\\r\\nDetailed information for the user accounts present in the groups will be displayed in below sections\\r\\n\"},\"name\":\"text - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"group - 4\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"RBACDelegation\"},\"name\":\"Custom Delegation\",\"styleSettings\":{\"showBorder\":true}},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"### How to user this tab\\r\\n**1 - Select an account** : All the Cmdlet launched by the account during the selected time frame will be displayer.\\r\\n\\r\\n**2 - Select a cmdlet** : All the roles that contain will be displayed\\r\\n\\r\\n**3 - Review the list of roles** : This table contains all the roles that contain the selected Cmdlet\\r\\n\\r\\n\",\"style\":\"info\"},\"name\":\"text - 1\"},{\"type\":1,\"content\":{\"json\":\"### How to undertand the \\\"List of Roles with this CmdLet\\\" table ? \\r\\n\\r\\n**WeightRole :** Display the wieight of this role based on its importance in terms of security risk\\r\\n\\r\\n**SumRole :** Among all the Cmdlet launched by the account during the defined time frame, this role available for x cmdlet. This role include x cmdlet run by the user.\\r\\n\\r\\n**OrgMgmtRole :** This role is really in the scope of Organization Management group. If the selected Cmdlet is not included is any other role, it make sense that this user is member of the Organization Management group\\r\\n\\r\\n \",\"style\":\"upsell\"},\"name\":\"text - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let CounUserCmdlet = (ExchangeAdminAuditLogs\\r\\n| where Status == \\\"Success\\\"\\r\\n| extend Caller = tostring(split(Caller,\\\"/\\\")[countof(Caller,\\\"/\\\")])\\r\\n| summarize Count=count() by Caller);\\r\\nExchangeConfiguration(SpecificSectionList=\\\"ExGroup\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| search CmdletResultValue.Parentgroup == \\\"Organization Management\\\"\\r\\n| where CmdletResultValue.Level != 0\\r\\n| where CmdletResultValue.ObjectClass == \\\"user\\\"\\r\\n//| project CmdletResultValue,Count\\r\\n| extend Account = tostring(CmdletResultValue.SamAccountName)\\r\\n| join kind=leftouter (CounUserCmdlet) on $left.Account == $right.Caller\\r\\n| project Account,Count\\r\\n//| project-away CmdletResultValue\\r\\n| sort by Account asc\",\"size\":3,\"title\":\"Organization Management Members\",\"exportFieldName\":\"Account\",\"exportParameterName\":\"Account\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"formatters\":[{\"columnMatch\":\"Count\",\"formatter\":3,\"formatOptions\":{\"palette\":\"purple\"}}]}},\"customWidth\":\"20\",\"name\":\"query - 1\",\"styleSettings\":{\"maxWidth\":\"100%\",\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeAdminAuditLogs\\r\\n| where Caller contains \\\"{Account}\\\"\\r\\n| where Status == \\\"Success\\\"\\r\\n| distinct CmdletName\\r\\n| sort by CmdletName asc\",\"size\":3,\"title\":\"List of CmdLet run by the account\",\"exportFieldName\":\"CmdletName\",\"exportParameterName\":\"CmdletName\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"CmdletName\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"CmdletName\",\"sortOrder\":1}]},\"customWidth\":\"33\",\"name\":\"query - 3\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let RBACRoleCmdlet = _GetWatchlist('RBACRoleCmdlet');\\r\\nlet UserRoleList = ExchangeAdminAuditLogs | where Caller contains \\\"{Account}\\\" | where Status == \\\"Success\\\" | distinct CmdletName;\\r\\nlet countRole = (RBACRoleCmdlet | where Name has_any (UserRoleList)| summarize SumRole = count()by Role);\\r\\nlet RolevsCmdlet = (RBACRoleCmdlet | where Name has_any (UserRoleList)| summarize make_set(Name) by Role);\\r\\nRolevsCmdlet\\r\\n| join kind=leftouter ( countRole ) on Role\\r\\n| project Role,CmdletList=set_Name,SumRole\\r\\n| join kind=leftouter ( RBACRoleCmdlet ) on Role\\r\\n| where Name has \\\"{CmdletName}\\\"\\r\\n| extend PossibleRoles = Role\\r\\n| extend OrgMgmtRole = OrgM\\r\\n| extend RoleWeight = Priority\\r\\n|distinct PossibleRoles,RoleWeight,tostring(SumRole),OrgMgmtRole,tostring(CmdletList)\\r\\n|sort by SumRole,RoleWeight\\r\\n\",\"size\":3,\"title\":\"List of Roles with this CmdLet\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"PossibleRoles\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"PossibleRoles\",\"sortOrder\":1}]},\"customWidth\":\"40\",\"name\":\"query - 3\",\"styleSettings\":{\"margin\":\"0\",\"maxWidth\":\"100%\",\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let RBACRoleCmdlet = _GetWatchlist('RBACRoleCmdlet');\\r\\nlet UserRoleList = ExchangeAdminAuditLogs | where TimeGenerated {TimeRange} | where Caller contains \\\"{Account}\\\" | where Status == \\\"Success\\\" | distinct CmdletName;\\r\\nlet countRole = (RBACRoleCmdlet | where Name has_any (UserRoleList)| summarize SumRole = count()by Role);\\r\\nlet RolevsCmdlet = (RBACRoleCmdlet | where Name has_any (UserRoleList)| summarize make_set(Name) by Role);\\r\\nRolevsCmdlet\\r\\n| join kind=leftouter ( countRole ) on Role\\r\\n| project Role,CmdletList=set_Name,SumRole\\r\\n| join kind=leftouter ( RBACRoleCmdlet ) on Role\\r\\n| extend Roles = Role\\r\\n| extend OrgMgmtRole = OrgM\\r\\n| extend RoleWeight = Priority\\r\\n| extend CmdletList=tostring(CmdletList)\\r\\n| summarize by Roles,CmdletList,RoleWeight,tostring(SumRole),OrgMgmtRole\\r\\n| distinct Roles,RoleWeight,tostring(SumRole),OrgMgmtRole,tostring(CmdletList)\\r\\n|sort by Roles asc\",\"size\":0,\"title\":\"Recommended Roles for selected users\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"Roles\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"Roles\",\"sortOrder\":1}]},\"name\":\"query - 3\"}]},\"name\":\"group - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Leastprivileges\"},\"name\":\"group - 5\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Custom Role details\",\"items\":[{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"List of Custom Roles\",\"items\":[{\"type\":1,\"content\":{\"json\":\"List of existing custom Roles\"},\"customWidth\":\"50\",\"name\":\"text - 3\"},{\"type\":1,\"content\":{\"json\":\"List of Custom with a Management Role Assignement (associated with a group or a user). Display the target account and scope if set\"},\"customWidth\":\"50\",\"name\":\"text - 4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend Identity = CmdletResultValue.Name\\r\\n| extend ParentRole =split(tostring(CmdletResultValue.Parent),\\\"\\\\\\\\\\\")[1]\\r\\n| project Identity, ParentRole, WhenCreated, WhenChanged\",\"size\":3,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"customWidth\":\"50\",\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MRcustomRoles = (ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project Role = tostring(CmdletResultValue.Name));\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend Role = tostring(CmdletResultValue.Role)\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend Scope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| project Role, Scope, RoleAssigneeName\\r\\n| join kind=inner (MRcustomRoles) on Role\\r\\n| project Role,RoleAssigneeName,Scope\",\"size\":1,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"customWidth\":\"50\",\"name\":\"query - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MRcustomRoles = (ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project Role = tostring(CmdletResultValue.Name));\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\", SpecificConfigurationDate=\\\"lastdate\\\", SpecificConfigurationEnv='ITSY', Target = \\\"Online\\\")\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend Scope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| project Role= tostring(CmdletResultValue.Role), Scope, RoleAssigneeName\\r\\n| join kind=rightouter (MRcustomRoles) on Role\\r\\n| project Role = Role1, Scope, RoleAssigneeName,Comment = iff(Role == \\\"\\\", \\\"⚠️ No existing delegation for this role\\\", \\\"✅ This role is delegated with a Management Role Assignment\\\")\",\"size\":0,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"customWidth\":\"50\",\"name\":\"query - 4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MRcustomRoles = (ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project Role = tostring(CmdletResultValue.Name));\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project Role = tostring(CmdletResultValue.Role)\\r\\n| join kind=rightouter (MRcustomRoles) on Role\\r\\n| summarize acount = count() by iff( Role==\\\"\\\",\\\"Number of non assigned roles\\\", Role)\",\"size\":0,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 5\"}]},\"name\":\"List of Custom Roles\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Custom Roles delegation on group\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section shows delegation associated with the Custom Roles\"},\"name\":\"text - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MRcustomRoles = (ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project Role = tostring(CmdletResultValue.Name));\\r\\nExchangeConfiguration(SpecificSectionList=\\\"MRA\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend Role = tostring(CmdletResultValue.Role)\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n| project RoleAssigneeName, Role, Status,CustomRecipientWriteScope, CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope,WhenCreated, WhenChanged\\r\\n| join kind=inner (MRcustomRoles) on Role\\r\\n| project RoleAssigneeName, Role, Status,CustomRecipientWriteScope, CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope,WhenCreated, WhenChanged\",\"size\":3,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 1\"}]},\"name\":\"group - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Details for Custom Roles Cmdlets \",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section displays for the chosen custom management roles all Cmdlets and their parameters associated with this custom role.\\r\\nRemember that for a cmdlet, some parameters can be removed.\"},\"name\":\"text - 0\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"07c8ac83-371d-4702-ab66-72aeb2a20053\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"CustomRole\",\"type\":2,\"isRequired\":true,\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRCustom\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend Identity = CmdletResultValue.Name\\r\\n| project Identity\",\"typeSettings\":{\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRCustomDetails\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Role contains \\\"{CustomRole}\\\"\\r\\n| extend CmdletName = CmdletResultValue.Name\\r\\n| extend Parameters = CmdletResultValue.Parameters\\r\\n| project CmdletName,Parameters\",\"size\":1,\"showAnalytics\":true,\"timeContext\":{\"durationMs\":86400000},\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Details for Custom Roles Cmdlets \"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"CustomRole\"},\"name\":\"Custom Role\"}],\"fallbackResourceIds\":[\"/subscriptions/6e1d3e11-a87d-4467-bb2a-c6b2d791fc03/resourcegroups/rg_sent-soc/providers/microsoft.operationalinsights/workspaces/sent-socdev\"],\"fromTemplateId\":\"sentinel-MicrosoftExchangeLeastPrivilegewithRBAC-Online\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -1474,7 +1488,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Security Review - Online Workbook with template version 3.1.7", + "description": "Microsoft Exchange Security Review - Online Workbook with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion2')]", @@ -1492,7 +1506,7 @@ }, "properties": { "displayName": "[parameters('workbook2-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Microsoft Exchange Security Review Online\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"9ae328d6-99c8-4c44-8d59-42ca4d999098\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"EnvironmentList\",\"label\":\"Environment\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"ExchangeEnvironmentList(Target=\\\"Online\\\") | where ESIEnvironment != \\\"\\\"\",\"typeSettings\":{\"limitSelectTo\":1,\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"a88b4e41-eb2f-41bf-92d8-27c83650a4b8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"DateOfConfiguration\",\"label\":\"Collection time\",\"type\":2,\"isRequired\":true,\"query\":\"let _configurationEnv = split(iff(isnull({EnvironmentList}) or isempty({EnvironmentList}) or tolower({EnvironmentList}) == \\\"all\\\",\\\"All\\\",tostring({EnvironmentList})),',');\\r\\nESIExchangeOnlineConfig_CL\\r\\n| extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n| where ScopedEnvironment in (_configurationEnv)\\r\\n| extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n| summarize Collection = max(Collection)\\r\\n| project Collection = \\\"lastdate\\\", Selected = true\\r\\n| join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | summarize by Collection \\r\\n | join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm ')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | join kind=leftouter (\\r\\n ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | summarize count() by Collection\\r\\n ) on Collection\\r\\n ) on Collection\\r\\n) on Collection\\r\\n| project Value = iif(Selected,Collection,iif(count_ > 1,PreciseCollection,Collection1)), Label = iif(Selected,\\\"Last Known date\\\",iif(count_ > 1,PreciseCollection,Collection1)), Selected\\r\\n| sort by Selected, Value desc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"181fa282-a002-42f1-ad57-dfb86df3194e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Compare_Collect\",\"type\":10,\"description\":\"If this button is checked, two collections will be compared\",\"isRequired\":true,\"jsonData\":\"[\\r\\n { \\\"value\\\": \\\"True\\\", \\\"label\\\": \\\"Yes\\\" },\\r\\n { \\\"value\\\": \\\"True,False\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true }\\r\\n]\"},{\"id\":\"8ac96eb3-918b-4a36-bcc4-df50d8f46175\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Help\",\"label\":\"Show Help\",\"type\":10,\"isRequired\":true,\"query\":\"{\\\"version\\\":\\\"1.0.0\\\",\\\"content\\\":\\\"[\\\\r\\\\n { \\\\\\\"value\\\\\\\": \\\\\\\"Yes\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"Yes\\\\\\\"},\\\\r\\\\n {\\\\\\\"value\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"selected\\\\\\\":true }\\\\r\\\\n]\\\\r\\\\n\\\"}\\r\\n\",\"timeContext\":{\"durationMs\":2592000000},\"queryType\":8}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"TimeRange\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"a9e0099e-5eb1-43b8-915c-587aa05bccf0\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"DateCompare\",\"type\":2,\"description\":\"Date to Comapre\",\"isRequired\":true,\"query\":\"let _configurationEnv = split(iff(isnull({EnvironmentList}) or isempty({EnvironmentList}) or tolower({EnvironmentList}) == \\\"all\\\",\\\"All\\\",tostring({EnvironmentList})),',');\\r\\nESIExchangeOnlineConfig_CL\\r\\n| extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n| where ScopedEnvironment in (_configurationEnv)\\r\\n| extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n| summarize Collection = max(Collection)\\r\\n| project Collection = \\\"lastdate\\\", Selected = true\\r\\n| join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | summarize by Collection \\r\\n | join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm ')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | join kind=leftouter (\\r\\n ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | summarize count() by Collection\\r\\n ) on Collection\\r\\n ) on Collection\\r\\n) on Collection\\r\\n| project Value = iif(Selected,Collection,iif(count_ > 1,PreciseCollection,Collection1)), Label = iif(Selected,\\\"Last Known date\\\",iif(count_ > 1,PreciseCollection,Collection1)), Selected\\r\\n| sort by Selected, Value desc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"parameters - 0\"},{\"type\":1,\"content\":{\"json\":\"This workbook helps review your Exchange Security configuration.\\r\\nAdjust the time range, and when needed select an item in the dropdownlist\",\"style\":\"info\"},\"name\":\"text - 9\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"34188faf-7a02-4697-9b36-2afa986afc0f\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Mailbox Access\",\"subTarget\":\"Delegation\",\"postText\":\"t\",\"style\":\"link\",\"icon\":\"3\",\"linkIsContextBlade\":true},{\"id\":\"be02c735-6150-4b6e-a386-b2b023e754e5\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"EXO & Azure AD Groups\",\"subTarget\":\"ExchAD\",\"style\":\"link\"},{\"id\":\"26c68d90-925b-4c3c-a837-e3cecd489b2d\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Transport Configuration\",\"subTarget\":\"Transport\",\"style\":\"link\"},{\"id\":\"eb2888ca-7fa6-4e82-88db-1bb3663a801e\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Workbook Help\",\"subTarget\":\"Start\",\"style\":\"link\"}]},\"name\":\"TopMenuTabs\"},{\"type\":1,\"content\":{\"json\":\"To compare collects, select **Yes** and choose the initial date.\\r\\nFor each role, a new table will be displayed with **all** the modifications (Add, Remove, Modifications) beetween the two dates.\\r\\n\\r\\n**Important notes** : Some information are limited are may be not 100% accurate :\\r\\n - Date\\r\\n - GUID of user instead of the name\\r\\n - Fusion of modifications when a role assisgnment is changed within the same collect \\r\\n - ... \\r\\n\\r\\nThis is due to some restrictions in the collect. For more details information, please check the workbook **\\\"Microsoft Exchange Search AdminAuditLog - Online\\\"**\\r\\n.\\r\\n\\r\\nThe compare functionnality is not available for all sections in this workbook.\\r\\n\"},\"name\":\"text - 9\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Workbook goals\\r\\n\\r\\nThe goal of this workbook is to outline key security configurations of your Exchange on-premises environment.\\r\\n\\r\\nMost of Exchange organizations have were installed years ago (sometimes more than 10 years). Many configurations have been done and might not have been documented. For most environments, the core commitment was maintaining a high availability of the users’ mailboxes putting aside other consideration (even security considerations). Recommended security practices have also evolved since the first released and a regular review is necessary.\\r\\n\\r\\nThis workbook is designed to show your Exchange organization is configured with a security point of view. Indeed, some configurations easy to display as there are no UI available.\\r\\n\\r\\nFor each configuration, you will find explanations and recommendations when applicable.\\r\\n\\r\\n- This workbook does not pretend to show you every weak Security configurations, but the most common issues and known to be used by attackers. \\r\\n- It will not show you if you have been comprised, but will help you identify unexpected configuration.\\r\\n\\r\\n----\\r\\n\\r\\n## Quick reminder of how Exchange works\\r\\n\\r\\nDuring Exchange installation two very important groups are created :\\r\\n- Exchange Trusted Subsystem : Contain all the computer accounts for Exchange Server\\r\\n- Exchange Windows Permissions : Contain the group Exchange trusted Subsystem\\r\\n\\r\\nThese groups have :\\r\\n- Very high privileges in ALL AD domains including the root domain\\r\\n- Right on any Exchange including mailboxes\\r\\n\\r\\nAs each Exchange server computer account is member of Exchange Trusted Subsystem, it means by taking control of the computer account or being System on an Exchange server you will gain access to all the permissions granted to Exchange Trusted Subsystem and Exchange Windows Permissions.\\r\\n\\r\\nTo protect AD and Exchange, it is very important to ensure the following:\\r\\n- There is a very limited number of persons that are local Administrator on Exchange server\\r\\n- To protect user right like : Act part of the operating System, Debug\\r\\n\\r\\nEvery service account or application that have high privileges on Exchange need to be considered as sensitive\\r\\n\\r\\n** 💡 Exchange servers need to be considered as very sensitive servers**\\r\\n\\r\\n-----\\r\\n\\r\\n\\r\\n## Tabs\\r\\n\\r\\n### Mailbox Access\\r\\n\\r\\nThis tab will show you several top sensitive delegations that allow an account to access, modify, act as another user, search, export the content of a mailbox.\\r\\n\\r\\n### Exchange & AD Groups\\r\\n\\r\\nThis tab will show you the members of Exchange groups and Sensitive AD groups.\\r\\n\\r\\n### Local Administrators\\r\\n\\r\\nThis tab will show you the non standard content of the local Administrators group. Remember that a member of the local Administrators group can take control of the computer account of the server and then it will have all the permissions associated with Exchange Trusted Subsytem and Exchange Windows Permissions\\r\\n\\r\\nThe information is displayed with different views : \\r\\n- List of nonstandard users\\r\\n- Number of servers with a nonstandard a user\\r\\n- Nonstandard groups content\\r\\n- For each user important information are displayed like last logon, last password set, enabled\\r\\n\\r\\n### Exchange Security configuration\\r\\n\\r\\nThis tab will show you some important configuration for your Exchange Organization\\r\\n- Status of Admin Audit Log configuration\\r\\n- Status of POP and IMAP configuration : especially, is Plaintext Authentication configured ?\\r\\n- Nonstandard permissions on the Exchange container in the Configuration Partition\\r\\n\\r\\n### Transport Configuration\\r\\n\\r\\nThis tab will show you the configuration of the main Transport components\\r\\n- Receive Connectors configured with Anonymous and/or Open Relay\\r\\n- Remote Domain Autoforward configuration\\r\\n- Transport Rules configured with BlindCopyTo, SendTo, RedirectTo\\r\\n- Journal Rule and Journal Recipient configurations\\r\\n- Accepted Domains with *\\r\\n\\r\\n\"},\"name\":\"WorkbookInfo\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Start\"},\"name\":\"InformationTab\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Display important security configurations that allow to access mailboxes' content. Direct delegations on mailboxes are not listed (Full Access permission mailboxes or direct delegations on mailboxes folders)\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Name !contains \\\"Deleg\\\" and CmdletResultValue.RoleAssigneeName != \\\"Hygiene Management\\\" and CmdletResultValue.RoleAssigneeName != \\\"Exchange Online-ApplicationAccount\\\" and CmdletResultValue.RoleAssigneeName != \\\"Discovery Management\\\" and CmdletResultValue.RoleAssigneeName !contains \\\"RIM-MailboxAdmins\\\"\\r\\n| where CmdletResultValue.Role contains \\\"Export\\\" or CmdletResultValue.Role contains \\\"Impersonation\\\" or CmdletResultValue.Role contains \\\"Search\\\"\\r\\n| summarize dcount(tostring(CmdletResultValue.RoleAssigneeName)) by role=tostring(CmdletResultValue.Role)\",\"size\":3,\"title\":\"Number of accounts with sensitive RBAC roles\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"role\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"dcount_CmdletResultValue_RoleAssigneeName\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2,\"maximumSignificantDigits\":3}}},\"showBorder\":true,\"sortCriteriaField\":\"role\",\"sortOrderField\":1}},\"name\":\"MRAQuery\"},{\"type\":1,\"content\":{\"json\":\"**ApplicationImpersonation** is a RBAC role that allows access (read and modify) to the content of all mailboxes. This role is very powerfull and should be carefully delegated. When a delegation is necessary, RBAC scopes should be configured to limit the list of impacted mailboxes.\\r\\n\\r\\nIt is common to see service accounts for backup solution, antivirus software, MDM...\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"SensitiveRBACHelp\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Application Impersonation Role\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This delegation allows the delegated account to access and modify the content of every mailboxes using EWS.\\r\\nExcluded from the result as it is a default configuration :\\r\\nDelegating delegation to Organization Management\"},\"name\":\"text - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Role contains \\\"Impersonation\\\" and CmdletResultValue.RoleAssigneeName != \\\"Hygiene Management\\\" and CmdletResultValue.RoleAssigneeName !contains \\\"RIM-MailboxAdmins\\\" and CmdletResultValue.Name !contains \\\"Deleg\\\"\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend RoleAssigneeType = iff(CmdletResultValue.RoleAssigneeType== \\\"User\\\" , \\\"User\\\", \\\"RoleGroup\\\")\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n| extend ManagementRoleAssignement = tostring(CmdletResultValue.Name)\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| extend RoleAssigneeName = iff( RoleAssigneeType == \\\"User\\\", strcat(\\\"🧑‍🦰 \\\",RoleAssigneeName), strcat(\\\"👪 \\\", RoleAssigneeName) )\\r\\n| project RoleAssigneeName, RoleAssigneeType, Status,CustomRecipientWriteScope,CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope,ManagementRoleAssignement,WhenChanged,WhenCreated\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true}},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExclusionsAcctValue = dynamic([\\\"Hygiene Management\\\", \\\"RIM-MailboxAdmins\\\"]);\\r\\nMESCompareDataMRA(SectionCompare=\\\"MRA\\\",DateCompare=\\\"{DateCompare:value}\\\",CurrentDate = \\\"{DateOfConfiguration:value}\\\",EnvList ={EnvironmentList},TypeEnv = \\\"Online\\\",ExclusionsAcct = ExclusionsAcctValue ,CurrentRole=\\\"Impersonation\\\")\",\"size\":3,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"ManagementRoleAssignement\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 2\"}]},\"name\":\"Application Impersonation Role\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Mailbox Import Export Role\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This delegation allows to import contents in all mailboxes.\\r\\nExcluded from the result as it is a default configuration :\\r\\nDelegating delegation to Organization Management\\r\\n\"},\"name\":\"text - 0\"},{\"type\":1,\"content\":{\"json\":\"**Mailbox Import Export** is an RBAC role that allows an account to import (export is not available online) contant in a user mailbox. It also allows searches in all mailboxes.\\r\\n\\r\\n⚡ This role is very powerfull.\\r\\n\\r\\nBy default, this role is not delegated to any user or group. The members of the group Organization Management by default do not have this role but are able to delegate it.\\r\\n\\r\\nℹ️ Recommendations\\r\\n\\r\\nIf you temporarily need this delegation, consider the following:\\r\\n- create an empty group with this delegation\\r\\n- monitor the group content and alert when the group modified\\r\\n- add administrators in this group only for a short period of time\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"SearchRBACHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Role contains \\\"export\\\" and CmdletResultValue.Name !contains \\\"Deleg\\\"\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend RoleAssigneeType = iff(CmdletResultValue.RoleAssigneeType== \\\"User\\\" , \\\"User\\\", \\\"RoleGroup\\\")\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n| extend ManagementRoleAssignement = tostring(CmdletResultValue.Name)\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| extend RoleAssigneeName = iff( RoleAssigneeType == \\\"User\\\", strcat(\\\"🧑‍🦰 \\\",RoleAssigneeName), strcat(\\\"👪 \\\", RoleAssigneeName) )\\r\\n| project RoleAssigneeName, RoleAssigneeType, Status,CustomRecipientWriteScope,CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope,ManagementRoleAssignement,WhenChanged,WhenCreated\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"MESCompareDataMRA(SectionCompare=\\\"MRA\\\",DateCompare=\\\"{DateCompare:value}\\\",CurrentDate = \\\"{DateOfConfiguration:value}\\\",EnvList ={EnvironmentList},TypeEnv = \\\"Online\\\",ExclusionsAcct = \\\"N/A\\\",CurrentRole=\\\"export\\\")\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"ManagementRoleAssignement\"],\"expandTopLevel\":true},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 1 - Copy\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Mailbox Import Export Role\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Mailbox Search Role\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This delegation allows to search inside all or in a scope of mailboxes.\\r\\nExcluded from the result as it is a default configuration :\\r\\nDelegating delegation to Organization Management\\r\\nDiscovery Management has been excluded\\r\\n\"},\"name\":\"text - 0\"},{\"type\":1,\"content\":{\"json\":\"**Mailbox Search** is an RBAC role that allows an account to search in any mailbox.\\r\\n\\r\\n⚡ This role is very powerfull.\\r\\n\\r\\nBy default, this role is only delegated to the group Discovery Management. The members of the group Organization Management do not have this role but are able to delegate it.\\r\\n\\r\\nℹ️ Recommendations\\r\\n\\r\\nIf you temporarily need this delegation, consider the following:\\r\\n\\r\\n- add the administrators in the Discovery Management group\\r\\n- monitor the group content and alert when the group modified\\r\\n- add administrators in this group only for a short period of time\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"SearchRBACHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Role contains \\\"search\\\" and CmdletResultValue.Name !contains \\\"Deleg\\\"\\r\\n| where CmdletResultValue.RoleAssigneeName != \\\"Exchange Online-ApplicationAccount\\\" and CmdletResultValue.RoleAssigneeName != \\\"Discovery Management\\\"\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend RoleAssigneeType = iff(CmdletResultValue.RoleAssigneeType== \\\"User\\\" , \\\"User\\\", \\\"Group\\\")\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n| extend ManagementRoleAssignement = tostring(CmdletResultValue.Name)\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| extend RoleAssigneeName = iff( RoleAssigneeType == \\\"User\\\", strcat(\\\"🧑‍🦰 \\\",RoleAssigneeName), strcat(\\\"👪 \\\", RoleAssigneeName) )\\r\\n| project RoleAssigneeName, RoleAssigneeType, Status,CustomRecipientWriteScope,CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope,ManagementRoleAssignement,WhenChanged,WhenCreated\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"MESCompareDataMRA(SectionCompare=\\\"MRA\\\",DateCompare=\\\"{DateCompare:value}\\\",CurrentDate = \\\"{DateOfConfiguration:value}\\\",EnvList ={EnvironmentList},TypeEnv = \\\"Online\\\",ExclusionsAcct = \\\"N/A\\\",CurrentRole=\\\"Search\\\")\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"ManagementRoleAssignement\"],\"expandTopLevel\":true},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 1 - Copy\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Mailbox Search Role\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Delegation\"},\"name\":\"Importantsecurityconfiguration\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Exchange Group\",\"items\":[{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"ℹ️ Recommendations\\r\\n\\r\\n- Ensure that no service account are a member of the high privilege groups. Use RBAC to delegate the exact required permissions.\\r\\n- Limit the usage of nested group for administration.\\r\\n- Ensure that accounts are given only the required pernissions to execute their tasks.\\r\\n- Use just in time administration principle by adding users in a group only when they need the permissions, then remove them when their operation is over.\\r\\n- Limit the number of Organization management members. When you review the Admin Audit logs you might see that the administrators rarely needed Organization Management privileges.\\r\\n- Monitor the content of the following groups:\\r\\n - TenantAdmins_-xxx (Membership in this role group is synchronized across services and managed centrally)\\r\\n - Organization Management\\r\\n - ExchangeServiceAdmins_-xxx (Membership in this role group is synchronized across services and managed centrally)\\r\\n - Recipient Management (Member of this group have at least the following rights : set-mailbox, Add-MailboxPermission)\\r\\n - Discovery Management\\r\\n - Hygiene Management\\r\\n - Security Administrator (Membership in this role group is synchronized across services and managed centrally)\\r\\n - xxx High privilege group (not an exhaustive list)\\r\\n - Compliance Management\\r\\n - All RBAC groups that have high roles delegation\\r\\n - All nested groups in high privileges groups\\r\\n - Note that this is not a complete list. The content of all the groups that have high privileges should be monitored.\\r\\n- Each time a new RBAC group is created, decide if the content of this groups should be monitored\\r\\n- Periodically review the members of the groups\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"text - 0\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\" Number of direct members per group with RecipientType User\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"RoleGroupMember\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n//| where CmdletResultValue.RecipientType !contains \\\"group\\\"\\r\\n| extend Members= tostring(CmdletResultValue.Identity)\\r\\n| summarize dcount(tostring(Members)) by RoleGroup = tostring(CmdletResultValue.RoleGroup)\\r\\n| where RoleGroup has_any (\\\"TenantAdmins\\\",\\\"Organization Management\\\", \\\"Discovery Management\\\", \\\"Compliance Management\\\", \\\"Server Management\\\", \\\"ExchangeServiceAdmins\\\",\\\"Security Administrator\\\", \\\"SecurityAdmins\\\", \\\"Recipient Manangement\\\", \\\"Records Manangement\\\",\\\"Impersonation\\\",\\\"Export\\\")\\r\\n| sort by dcount_Members\\r\\n\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"RoleGroup\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"dcount_Members\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":true,\"sortCriteriaField\":\"dcount_Members\",\"sortOrderField\":2,\"size\":\"auto\"}},\"name\":\"query - 0\"}]},\"name\":\"ExchangeGroupsList\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Number of direct members per group with RecipientType User\",\"expandable\":true,\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"RoleGroupMember\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n| where CmdletResultValue.RecipientType !contains \\\"group\\\"\\r\\n| extend Members= tostring(CmdletResultValue.Identity)\\r\\n| summarize dcount(tostring(Members)) by RoleGroup = tostring(CmdletResultValue.RoleGroup)\\r\\n| sort by dcount_Members\\r\\n\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"RoleGroup\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"dcount_Members\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":true,\"sortCriteriaField\":\"dcount_Members\",\"sortOrderField\":2,\"size\":\"auto\"}},\"name\":\"query - 0\"}]},\"name\":\"ExchangeGroupsList - Copy\"},{\"type\":1,\"content\":{\"json\":\"Exchange Online groups content.\\r\\nSelect a group to display detailed information of its contents.\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"b4b7a6ad-381a-48d6-9938-bf7cb812b474\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Group\",\"type\":2,\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"RoleGroup\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n//| where CmdletResultValue.Parentgroup != \\\"Exchange Trusted Subsystem\\\"\\r\\n//| where CmdletResultValue.Parentgroup != \\\"Exchange Windows Permissions\\\"\\r\\n| project CmdletResultValue\\r\\n| extend GroupName = tostring(CmdletResultValue.Name)\\r\\n| distinct GroupName\\r\\n| sort by GroupName asc\\r\\n\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"//ExchangeConfiguration(SpecificSectionList=\\\"ExGroup\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\nExchangeConfiguration(SpecificSectionList=\\\"RoleGroupMember\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| search CmdletResultValue.RoleGroup == \\\"{Group}\\\"\\r\\n//| where CmdletResultValue.Level != 0\\r\\n| project CmdletResultValue\\r\\n| extend Members = tostring(CmdletResultValue.Identity)\\r\\n//| extend Parentgroup = tostring(CmdletResultValue.Parentgroup)\\r\\n//| extend MemberPath = tostring(CmdletResultValue.MemberPath)\\r\\n//| extend Level = tostring(CmdletResultValue.Level)\\r\\n//| extend ObjectClass = tostring(CmdletResultValue.ObjectClass)\\r\\n//| extend LastLogon = CmdletResultValue.LastLogonString\\r\\n//| extend LastLogon = iif ( todatetime (CmdletResultValue.LastLogonString) < ago(-366d), CmdletResultValue.LastLogonString,strcat(\\\"💥\\\",CmdletResultValue.LastLogonString))\\r\\n//| extend LastPwdSet = CmdletResultValue.LastPwdSetString\\r\\n//| extend Enabled = tostring(CmdletResultValue.Enabled)\\r\\n| extend Members = case( CmdletResultValue.RecipientType == \\\"Group\\\", strcat( \\\"👪 \\\", Members), strcat( \\\"🧑‍🦰 \\\", Members) )\\r\\n| extend RecipientType = tostring(CmdletResultValue.RecipientType)\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"CmdletResultValue\",\"formatter\":5}],\"rowLimit\":10000,\"filter\":true}},\"name\":\"ExchangeServersGroupsGrid\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Exchange group\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"ExchAD\"},\"name\":\"Exchange and AD GRoup\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Transport Security configuration\",\"items\":[{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Inbound Connector configuration\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section shows the configuration of the Inbound connnectors\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"TransportRulesHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"InBoundC\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n| extend Name = tostring(CmdletResultValue.Name)\\r\\n| extend State = tostring(CmdletResultValue.Enabled)\\r\\n| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n| extend SenderIPAddresses = tostring(CmdletResultValue.SenderIPAddresses)\\r\\n| extend SenderDomains = tostring(CmdletResultValue.SenderDomains)\\r\\n| extend TrustedOrganizations = tostring(CmdletResultValue.TrustedOrganizations)\\r\\n| extend AssociatedAcceptedDomainsRequireTls = tostring(CmdletResultValue.AssociatedAcceptedDomainsRequireTls)\\r\\n| extend RestrictDomainsToIPAddresses = tostring(CmdletResultValue.RestrictDomainsToIPAddresses)\\r\\n| extend RestrictDomainsToCertificate = tostring(CmdletResultValue.RestrictDomainsToCertificate)\\r\\n| extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n| extend TreatMessagesAsInternal = tostring(CmdletResultValue.TreatMessagesAsInternal)\\r\\n| extend TlsSenderCertificateName = tostring(CmdletResultValue.TlsSenderCertificateName)\\r\\n| extend ScanAndDropRecipients = tostring(CmdletResultValue.ScanAndDropRecipients)\\r\\n| extend Comment = tostring(CmdletResultValue.Comment)\\r\\n| extend WhenChanged = tostring(CmdletResultValue.WhenChanged)\\r\\n| extend WhenCreated = tostring(CmdletResultValue.WhenCreated)\\r\\n| project-away CmdletResultValue\\r\\n| sort by Name asc\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let _EnvList ={EnvironmentList};\\r\\nlet _TypeEnv = \\\"Online\\\";\\r\\nlet _DateCompare = \\\"{DateCompare:value}\\\";\\r\\nlet _CurrentDate = \\\"{DateOfConfiguration:value}\\\";\\r\\nlet _DateCompareB = todatetime(_DateCompare);\\r\\nlet _currD = (ExchangeConfiguration(SpecificSectionList=\\\"InBoundC\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n| summarize TimeMax = arg_max(TimeGenerated,*)\\r\\n| extend TimeMax = tostring(split(TimeMax,\\\"T\\\")[0])\\r\\n| project TimeMax);\\r\\nlet _CurrentDateB = todatetime(toscalar(_currD));\\r\\nlet BeforeData = ExchangeConfiguration(SpecificSectionList=\\\"InBoundC\\\",SpecificConfigurationDate=_DateCompareB,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend SenderIPAddresses = tostring(CmdletResultValue.SenderIPAddresses)\\r\\n\\t| extend SenderDomains = tostring(CmdletResultValue.SenderDomains)\\r\\n\\t| extend TrustedOrganizations = tostring(CmdletResultValue.TrustedOrganizations)\\r\\n\\t| extend AssociatedAcceptedDomainsRequireTls = tostring(CmdletResultValue.AssociatedAcceptedDomainsRequireTls)\\r\\n\\t| extend RestrictDomainsToIPAddresses = tostring(CmdletResultValue.RestrictDomainsToIPAddresses)\\r\\n\\t| extend RestrictDomainsToCertificate = tostring(CmdletResultValue.RestrictDomainsToCertificate)\\r\\n\\t| extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n\\t| extend TreatMessagesAsInternal = tostring(CmdletResultValue.TreatMessagesAsInternal)\\r\\n\\t| extend TlsSenderCertificateName = tostring(CmdletResultValue.TlsSenderCertificateName)\\r\\n\\t| extend ScanAndDropRecipients = tostring(CmdletResultValue.ScanAndDropRecipients)\\r\\n\\t| extend Comment = tostring(CmdletResultValue.Comment)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet AfterData = \\r\\n ExchangeConfiguration(SpecificSectionList=\\\"InBoundC\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n \\t| extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend SenderIPAddresses = tostring(CmdletResultValue.SenderIPAddresses)\\r\\n\\t| extend SenderDomains = tostring(CmdletResultValue.SenderDomains)\\r\\n\\t| extend TrustedOrganizations = tostring(CmdletResultValue.TrustedOrganizations)\\r\\n\\t| extend AssociatedAcceptedDomainsRequireTls = tostring(CmdletResultValue.AssociatedAcceptedDomainsRequireTls)\\r\\n\\t| extend RestrictDomainsToIPAddresses = tostring(CmdletResultValue.RestrictDomainsToIPAddresses)\\r\\n\\t| extend RestrictDomainsToCertificate = tostring(CmdletResultValue.RestrictDomainsToCertificate)\\r\\n\\t| extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n\\t| extend TreatMessagesAsInternal = tostring(CmdletResultValue.TreatMessagesAsInternal)\\r\\n\\t| extend TlsSenderCertificateName = tostring(CmdletResultValue.TlsSenderCertificateName)\\r\\n\\t| extend ScanAndDropRecipients = tostring(CmdletResultValue.ScanAndDropRecipients)\\r\\n\\t| extend Comment = tostring(CmdletResultValue.Comment)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet i=0;\\r\\nlet allDataRange = \\r\\n ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"InBoundC\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | project TimeGenerated,CmdletResultValue,WhenChanged = todatetime(WhenChanged_t), WhenCreated=todatetime(WhenCreated_t)\\r\\n \\t| extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend SenderIPAddresses = tostring(CmdletResultValue.SenderIPAddresses)\\r\\n\\t| extend SenderDomains = tostring(CmdletResultValue.SenderDomains)\\r\\n\\t| extend TrustedOrganizations = tostring(CmdletResultValue.TrustedOrganizations)\\r\\n\\t| extend AssociatedAcceptedDomainsRequireTls = tostring(CmdletResultValue.AssociatedAcceptedDomainsRequireTls)\\r\\n\\t| extend RestrictDomainsToIPAddresses = tostring(CmdletResultValue.RestrictDomainsToIPAddresses)\\r\\n\\t| extend RestrictDomainsToCertificate = tostring(CmdletResultValue.RestrictDomainsToCertificate)\\r\\n\\t| extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n\\t| extend TreatMessagesAsInternal = tostring(CmdletResultValue.TreatMessagesAsInternal)\\r\\n\\t| extend TlsSenderCertificateName = tostring(CmdletResultValue.TlsSenderCertificateName)\\r\\n\\t| extend ScanAndDropRecipients = tostring(CmdletResultValue.ScanAndDropRecipients)\\r\\n\\t| extend Comment = tostring(CmdletResultValue.Comment)\\r\\n ;\\r\\nlet DiffAddDataP1 = allDataRange\\r\\n | join kind = rightanti (AfterData | where WhenCreated >=_DateCompareB) on WhenCreated\\r\\n;\\r\\nlet DiffAddDataP2 = allDataRange\\r\\n | join kind = innerunique (allDataRange ) on WhenCreated\\r\\n | where WhenCreated >=_DateCompareB\\r\\n | where bin(WhenCreated,5m)==bin(WhenChanged,5m)\\r\\n | distinct Identity,State,ConnectorType,ConnectorSource,SenderIPAddresses,SenderDomains,TrustedOrganizations,AssociatedAcceptedDomainsRequireTls,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,CloudServicesMailEnabled,TreatMessagesAsInternal,TlsSenderCertificateName,ScanAndDropRecipients,Comment,WhenChanged,WhenCreated\\r\\n ;\\r\\nlet DiffAddData = union DiffAddDataP1,DiffAddDataP2\\r\\n| extend Actiontype =\\\"Add\\\";\\r\\nlet DiffRemoveData = allDataRange\\r\\n | join kind = leftanti AfterData on Identity\\r\\n | extend Actiontype =\\\"Remove\\\"\\r\\n | distinct Actiontype ,Identity,State,ConnectorType,ConnectorSource,SenderIPAddresses,SenderDomains,TrustedOrganizations,AssociatedAcceptedDomainsRequireTls,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,CloudServicesMailEnabled,TreatMessagesAsInternal,TlsSenderCertificateName,ScanAndDropRecipients,Comment,WhenCreated\\r\\n | project WhenChanged=_CurrentDateB,Actiontype,Identity,State,ConnectorType,ConnectorSource,SenderIPAddresses,SenderDomains,TrustedOrganizations,AssociatedAcceptedDomainsRequireTls,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,CloudServicesMailEnabled,TreatMessagesAsInternal,TlsSenderCertificateName,ScanAndDropRecipients,Comment,WhenCreated\\r\\n ;\\r\\nlet DiffModifData = union AfterData,allDataRange\\r\\n| sort by Identity,WhenChanged asc\\r\\n| project WhenChanged,Identity,State,ConnectorType,ConnectorSource,SenderIPAddresses,SenderDomains,TrustedOrganizations,AssociatedAcceptedDomainsRequireTls,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,CloudServicesMailEnabled,TreatMessagesAsInternal,TlsSenderCertificateName,ScanAndDropRecipients,Comment,WhenCreated\\r\\n| extend Identity = iff( Identity == prev(Identity) and Identity != prev(Identity) and prev(Identity) !=\\\"\\\" , strcat(\\\"📍 \\\", Identity, \\\" (\\\",prev(Identity),\\\"->\\\", Identity,\\\" )\\\"),Identity)\\r\\n| extend State = iff( Identity == prev(Identity) and State != prev(State) and prev(State) !=\\\"\\\" , strcat(\\\"📍 \\\", State, \\\" (\\\",prev(State),\\\"->\\\", State,\\\" )\\\"),State)\\r\\n| extend ConnectorType = iff( Identity == prev(Identity) and ConnectorType != prev(ConnectorType) and prev(ConnectorType) !=\\\"\\\" , strcat(\\\"📍 \\\", ConnectorType, \\\" (\\\",prev(ConnectorType),\\\"->\\\", ConnectorType,\\\" )\\\"),ConnectorType)\\r\\n| extend ConnectorSource = iff( Identity == prev(Identity) and ConnectorSource != prev(ConnectorSource) and prev(ConnectorSource) !=\\\"\\\" , strcat(\\\"📍 \\\", ConnectorSource, \\\" (\\\",prev(ConnectorSource),\\\"->\\\", ConnectorSource,\\\" )\\\"),ConnectorSource)\\r\\n| extend SenderIPAddresses = iff( Identity == prev(Identity) and SenderIPAddresses != prev(SenderIPAddresses) and prev(SenderIPAddresses) !=\\\"\\\" , strcat(\\\"📍 \\\", SenderIPAddresses, \\\" (\\\",prev(SenderIPAddresses),\\\"->\\\", SenderIPAddresses,\\\" )\\\"),SenderIPAddresses)\\r\\n| extend SenderDomains = iff( Identity == prev(Identity) and SenderDomains != prev(SenderDomains) and prev(SenderDomains) !=\\\"\\\" , strcat(\\\"📍 \\\", SenderDomains, \\\" (\\\",prev(SenderDomains),\\\"->\\\", SenderDomains,\\\" )\\\"),SenderDomains)\\r\\n| extend TrustedOrganizations = iff( Identity == prev(Identity) and TrustedOrganizations != prev(TrustedOrganizations) and prev(TrustedOrganizations) !=\\\"\\\" , strcat(\\\"📍 \\\", TrustedOrganizations, \\\" (\\\",prev(TrustedOrganizations),\\\"->\\\", TrustedOrganizations,\\\" )\\\"),TrustedOrganizations)\\r\\n| extend AssociatedAcceptedDomainsRequireTls = iff (Identity == prev(Identity) and AssociatedAcceptedDomainsRequireTls != prev(AssociatedAcceptedDomainsRequireTls) and prev(AssociatedAcceptedDomainsRequireTls) !=\\\"\\\" , strcat(\\\"📍 \\\", AssociatedAcceptedDomainsRequireTls, \\\" (\\\",prev(AssociatedAcceptedDomainsRequireTls),\\\"->\\\", AssociatedAcceptedDomainsRequireTls,\\\" )\\\"),AssociatedAcceptedDomainsRequireTls)\\r\\n| extend RestrictDomainsToIPAddresses = iff(Identity == prev(Identity) and RestrictDomainsToIPAddresses != prev(RestrictDomainsToIPAddresses) and prev(RestrictDomainsToIPAddresses) !=\\\"\\\" , strcat(\\\"📍 \\\", RestrictDomainsToIPAddresses, \\\" (\\\",prev(RestrictDomainsToIPAddresses),\\\"->\\\", RestrictDomainsToIPAddresses,\\\" )\\\"),RestrictDomainsToIPAddresses)\\r\\n| extend RestrictDomainsToCertificate = iff( Identity == prev(Identity) and RestrictDomainsToCertificate != prev(RestrictDomainsToCertificate) and prev(RestrictDomainsToCertificate) !=\\\"\\\" , strcat(\\\"📍 \\\", RestrictDomainsToCertificate, \\\" (\\\",prev(RestrictDomainsToCertificate),\\\"->\\\", RestrictDomainsToCertificate,\\\" )\\\"),RestrictDomainsToCertificate)\\r\\n| extend CloudServicesMailEnabled = iff( Identity == prev(Identity) and CloudServicesMailEnabled != prev(CloudServicesMailEnabled) and prev(CloudServicesMailEnabled) !=\\\"\\\" , strcat(\\\"📍 \\\", CloudServicesMailEnabled, \\\" (\\\",prev(CloudServicesMailEnabled),\\\"->\\\", CloudServicesMailEnabled,\\\" )\\\"),CloudServicesMailEnabled)\\r\\n| extend TreatMessagesAsInternal = iff( Identity == prev(Identity) and TreatMessagesAsInternal != prev(TreatMessagesAsInternal) and prev(TreatMessagesAsInternal) !=\\\"\\\" , strcat(\\\"📍 \\\", TreatMessagesAsInternal, \\\" (\\\",prev(TreatMessagesAsInternal),\\\"->\\\", TreatMessagesAsInternal,\\\" )\\\"),TreatMessagesAsInternal)\\r\\n| extend TlsSenderCertificateName = iff(Identity == prev(Identity) and TlsSenderCertificateName != prev(TlsSenderCertificateName) and prev(TlsSenderCertificateName) !=\\\"\\\" , strcat(\\\"📍 \\\", TlsSenderCertificateName, \\\" (\\\",prev(TlsSenderCertificateName),\\\"->\\\", TlsSenderCertificateName,\\\" )\\\"),TlsSenderCertificateName)\\r\\n| extend ScanAndDropRecipients = iff( Identity == prev(Identity) and ScanAndDropRecipients != prev(ScanAndDropRecipients) and prev(ScanAndDropRecipients) !=\\\"\\\" , strcat(\\\"📍 \\\", ScanAndDropRecipients, \\\" (\\\",prev(ScanAndDropRecipients),\\\"->\\\", ScanAndDropRecipients,\\\" )\\\"),ScanAndDropRecipients)\\r\\n| extend Comment = iff( Identity == prev(Identity) and Comment != prev(Comment) and prev(Comment) !=\\\"\\\" , strcat(\\\"📍 \\\", Comment, \\\" (\\\",prev(Comment),\\\"->\\\", Comment,\\\" )\\\"),Comment)\\r\\n| extend ActiontypeR =iff((Identity contains \\\"📍\\\" or State contains \\\"📍\\\" or ConnectorType contains \\\"📍\\\" or ConnectorSource contains \\\"📍\\\" or SenderIPAddresses contains \\\"📍\\\" or SenderDomains contains \\\"📍\\\" or TrustedOrganizations contains \\\"📍\\\" or AssociatedAcceptedDomainsRequireTls contains \\\"📍\\\" or RestrictDomainsToIPAddresses contains \\\"📍\\\" or RestrictDomainsToCertificate contains \\\"📍\\\" or CloudServicesMailEnabled contains \\\"📍\\\" or TreatMessagesAsInternal contains \\\"📍\\\" or TlsSenderCertificateName contains \\\"📍\\\" or ScanAndDropRecipients contains \\\"📍\\\" or Comment contains \\\"📍\\\" ), i=i + 1, i)\\r\\n| extend Actiontype =iff(ActiontypeR > 0, \\\"Modif\\\", \\\"NO\\\")\\r\\n| where ActiontypeR == 1\\r\\n| project WhenChanged,Actiontype,Identity,State,ConnectorType,ConnectorSource,SenderIPAddresses,SenderDomains,TrustedOrganizations,AssociatedAcceptedDomainsRequireTls,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,CloudServicesMailEnabled,TreatMessagesAsInternal,TlsSenderCertificateName,ScanAndDropRecipients,Comment,WhenCreated\\r\\n;\\r\\nunion DiffAddData, DiffRemoveData, DiffModifData\\r\\n| extend WhenChanged = iff (Actiontype == \\\"Modif\\\", WhenChanged, iff(Actiontype == \\\"Add\\\",WhenCreated, WhenChanged))\\r\\n| extend Actiontype = case(Actiontype == \\\"Add\\\", strcat(\\\"➕ \\\", Actiontype), Actiontype == \\\"Remove\\\", strcat(\\\"➖ \\\", Actiontype), Actiontype == \\\"Modif\\\", strcat(\\\"📍 \\\", Actiontype), \\\"N/A\\\")\\r\\n| sort by WhenChanged desc \\r\\n| project\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n State,\\r\\n ConnectorType,\\r\\n ConnectorSource,\\r\\n Comment,\\r\\n SenderIPAddresses,\\r\\n SenderDomains,\\r\\n TrustedOrganizations,\\r\\n AssociatedAcceptedDomainsRequireTls,\\r\\n RestrictDomainsToIPAddresses,\\r\\n RestrictDomainsToCertificate,\\r\\n CloudServicesMailEnabled,\\r\\n TreatMessagesAsInternal,\\r\\n TlsSenderCertificateName,\\r\\n ScanAndDropRecipients,\\r\\n WhenCreated\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"Identity\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 2\"}]},\"name\":\"Inbound Connector configuration\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Outbound Connector configuration\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section shows the configuration of the Outbound connnectors\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"TransportRulesHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"OutBoundC\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n| extend Name = tostring(CmdletResultValue.Name)\\r\\n| extend State = tostring(CmdletResultValue.Enabled)\\r\\n| extend UseMXRecord = tostring(CmdletResultValue.UseMXRecord)\\r\\n| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n| extend RecipientDomains = tostring(CmdletResultValue.RecipientDomains)\\r\\n| extend SmartHosts = tostring(CmdletResultValue.SmartHosts)\\r\\n| extend TlsDomain = tostring(CmdletResultValue.TlsDomain)\\r\\n| extend TlsSettings = tostring(CmdletResultValue.TlsSettings)\\r\\n| extend IsTransportRuleScoped = tostring(CmdletResultValue.IsTransportRuleScoped)\\r\\n| extend RouteAllMessagesViaOnPremises = tostring(CmdletResultValue.RouteAllMessagesViaOnPremises)\\r\\n| extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n| extend AllAcceptedDomains = tostring(CmdletResultValue.AllAcceptedDomains)\\r\\n| extend SenderRewritingEnabled = tostring(CmdletResultValue.SenderRewritingEnabled)\\r\\n| extend TestMode = tostring(CmdletResultValue.TestMode)\\r\\n| extend LinkForModifiedConnector = tostring(CmdletResultValue.LinkForModifiedConnector)\\r\\n| extend ValidationRecipients = tostring(CmdletResultValue.ValidationRecipients)\\r\\n| extend IsValidated = tostring(CmdletResultValue.IsValidated)\\r\\n| extend LastValidationTimestamp = tostring(CmdletResultValue.LastValidationTimestamp)\\r\\n| extend Comment = tostring(CmdletResultValue.Comment)\\r\\n| extend WhenChanged = tostring(CmdletResultValue.WhenChanged)\\r\\n| extend WhenCreated = tostring(CmdletResultValue.WhenCreated)\\r\\n| project-away CmdletResultValue\\r\\n| sort by Name asc\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Outbound Connector configuration - Copy\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let _EnvList ={EnvironmentList};\\r\\nlet _TypeEnv = \\\"Online\\\";\\r\\nlet _DateCompare = \\\"{DateCompare:value}\\\";\\r\\nlet _CurrentDate = \\\"{DateOfConfiguration:value}\\\";\\r\\nlet _DateCompareB = todatetime(_DateCompare);\\r\\nlet _currD = (ExchangeConfiguration(SpecificSectionList=\\\"OutBoundC\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n| summarize TimeMax = arg_max(TimeGenerated,*)\\r\\n| extend TimeMax = tostring(split(TimeMax,\\\"T\\\")[0])\\r\\n| project TimeMax);\\r\\nlet _CurrentDateB = todatetime(toscalar(_currD));\\r\\nlet BeforeData = ExchangeConfiguration(SpecificSectionList=\\\"OutBoundC\\\",SpecificConfigurationDate=_DateCompareB,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n | extend UseMXRecord = tostring(CmdletResultValue.UseMXRecord)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend RecipientDomains = tostring(CmdletResultValue.RecipientDomains)\\r\\n | extend SmartHosts = tostring(CmdletResultValue.SmartHosts)\\r\\n | extend TlsDomain = tostring(CmdletResultValue.TlsDomain)\\r\\n | extend TlsSettings = tostring(CmdletResultValue.TlsSettings)\\r\\n | extend IsTransportRuleScoped = tostring(CmdletResultValue.IsTransportRuleScoped)\\r\\n | extend RouteAllMessagesViaOnPremises = tostring(CmdletResultValue.RouteAllMessagesViaOnPremises)\\r\\n | extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n | extend AllAcceptedDomains = tostring(CmdletResultValue.AllAcceptedDomains)\\r\\n | extend SenderRewritingEnabled = tostring(CmdletResultValue.SenderRewritingEnabled)\\r\\n | extend TestMode = tostring(CmdletResultValue.TestMode)\\r\\n | extend LinkForModifiedConnector = tostring(CmdletResultValue.LinkForModifiedConnector)\\r\\n | extend ValidationRecipients = tostring(CmdletResultValue.ValidationRecipients)\\r\\n | extend IsValidated = tostring(CmdletResultValue.IsValidated)\\r\\n | extend LastValidationTimestamp = tostring(CmdletResultValue.LastValidationTimestamp)\\r\\n | extend Comment = tostring(CmdletResultValue.Comment)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet AfterData = \\r\\n ExchangeConfiguration(SpecificSectionList=\\\"OutBoundC\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n | extend UseMXRecord = tostring(CmdletResultValue.UseMXRecord)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend RecipientDomains = tostring(CmdletResultValue.RecipientDomains)\\r\\n | extend SmartHosts = tostring(CmdletResultValue.SmartHosts)\\r\\n | extend TlsDomain = tostring(CmdletResultValue.TlsDomain)\\r\\n | extend TlsSettings = tostring(CmdletResultValue.TlsSettings)\\r\\n | extend IsTransportRuleScoped = tostring(CmdletResultValue.IsTransportRuleScoped)\\r\\n | extend RouteAllMessagesViaOnPremises = tostring(CmdletResultValue.RouteAllMessagesViaOnPremises)\\r\\n | extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n | extend AllAcceptedDomains = tostring(CmdletResultValue.AllAcceptedDomains)\\r\\n | extend SenderRewritingEnabled = tostring(CmdletResultValue.SenderRewritingEnabled)\\r\\n | extend TestMode = tostring(CmdletResultValue.TestMode)\\r\\n | extend LinkForModifiedConnector = tostring(CmdletResultValue.LinkForModifiedConnector)\\r\\n | extend ValidationRecipients = tostring(CmdletResultValue.ValidationRecipients)\\r\\n | extend IsValidated = tostring(CmdletResultValue.IsValidated)\\r\\n | extend LastValidationTimestamp = tostring(CmdletResultValue.LastValidationTimestamp)\\r\\n | extend Comment = tostring(CmdletResultValue.Comment)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet i=0;\\r\\nlet allDataRange = \\r\\n ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"OutBoundC\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | project TimeGenerated,CmdletResultValue,WhenChanged = todatetime(WhenChanged_t), WhenCreated=todatetime(WhenCreated_t)\\r\\n \\t| extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n | extend UseMXRecord = tostring(CmdletResultValue.UseMXRecord)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend RecipientDomains = tostring(CmdletResultValue.RecipientDomains)\\r\\n | extend SmartHosts = tostring(CmdletResultValue.SmartHosts)\\r\\n | extend TlsDomain = tostring(CmdletResultValue.TlsDomain)\\r\\n | extend TlsSettings = tostring(CmdletResultValue.TlsSettings)\\r\\n | extend IsTransportRuleScoped = tostring(CmdletResultValue.IsTransportRuleScoped)\\r\\n | extend RouteAllMessagesViaOnPremises = tostring(CmdletResultValue.RouteAllMessagesViaOnPremises)\\r\\n | extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n | extend AllAcceptedDomains = tostring(CmdletResultValue.AllAcceptedDomains)\\r\\n | extend SenderRewritingEnabled = tostring(CmdletResultValue.SenderRewritingEnabled)\\r\\n | extend TestMode = tostring(CmdletResultValue.TestMode)\\r\\n | extend LinkForModifiedConnector = tostring(CmdletResultValue.LinkForModifiedConnector)\\r\\n | extend ValidationRecipients = tostring(CmdletResultValue.ValidationRecipients)\\r\\n | extend IsValidated = tostring(CmdletResultValue.IsValidated)\\r\\n | extend LastValidationTimestamp = tostring(CmdletResultValue.LastValidationTimestamp)\\r\\n | extend Comment = tostring(CmdletResultValue.Comment)\\r\\n ;\\r\\nlet DiffAddDataP1 = allDataRange\\r\\n | join kind = rightanti (AfterData | where WhenCreated >=_DateCompareB) on WhenCreated\\r\\n;\\r\\nlet DiffAddDataP2 = allDataRange\\r\\n | join kind = innerunique (allDataRange ) on WhenCreated\\r\\n | where WhenCreated >=_DateCompareB\\r\\n | where bin(WhenCreated,5m)==bin(WhenChanged,5m)\\r\\n | distinct Identity,State,ConnectorType,ConnectorSource,UseMXRecord,RecipientDomains,SmartHosts,TlsDomain,TlsSettings,IsTransportRuleScoped,RouteAllMessagesViaOnPremises,CloudServicesMailEnabled,AllAcceptedDomains,SenderRewritingEnabled,TestMode,LinkForModifiedConnector,ValidationRecipients,IsValidated,LastValidationTimestamp,Comment,WhenCreated\\r\\n ;\\r\\nlet DiffAddData = union DiffAddDataP1,DiffAddDataP2\\r\\n| extend Actiontype =\\\"Add\\\";\\r\\nlet DiffRemoveData = allDataRange\\r\\n | join kind = leftanti AfterData on Identity\\r\\n | extend Actiontype =\\\"Remove\\\"\\r\\n | distinct Actiontype ,Identity,State,ConnectorType,ConnectorSource,UseMXRecord,RecipientDomains,SmartHosts,TlsDomain,TlsSettings,IsTransportRuleScoped,RouteAllMessagesViaOnPremises,CloudServicesMailEnabled,AllAcceptedDomains,SenderRewritingEnabled,TestMode,LinkForModifiedConnector,ValidationRecipients,IsValidated,LastValidationTimestamp,Comment,WhenCreated\\r\\n | project WhenChanged=_CurrentDateB,Actiontype,Identity,State,ConnectorType,ConnectorSource,UseMXRecord,RecipientDomains,SmartHosts,TlsDomain,TlsSettings,IsTransportRuleScoped,RouteAllMessagesViaOnPremises,CloudServicesMailEnabled,AllAcceptedDomains,SenderRewritingEnabled,TestMode,LinkForModifiedConnector,ValidationRecipients,IsValidated,LastValidationTimestamp,Comment,WhenCreated\\r\\n ;\\r\\nlet DiffModifData = union AfterData,allDataRange\\r\\n| sort by Identity,WhenChanged asc\\r\\n| project WhenChanged,Identity,State,ConnectorType,ConnectorSource,UseMXRecord,RecipientDomains,SmartHosts,TlsDomain,TlsSettings,IsTransportRuleScoped,RouteAllMessagesViaOnPremises,CloudServicesMailEnabled,AllAcceptedDomains,SenderRewritingEnabled,TestMode,LinkForModifiedConnector,ValidationRecipients,IsValidated,LastValidationTimestamp,Comment,WhenCreated\\r\\n| extend Identity = iff( Identity == prev(Identity) and Identity != prev(Identity) and prev(Identity) !=\\\"\\\" , strcat(\\\"📍 \\\", Identity, \\\" (\\\",prev(Identity),\\\"->\\\", Identity,\\\" )\\\"),Identity)\\r\\n| extend State = iff( Identity == prev(Identity) and State != prev(State) and prev(State) !=\\\"\\\" , strcat(\\\"📍 \\\", State, \\\" (\\\",prev(State),\\\"->\\\", State,\\\" )\\\"),State)\\r\\n| extend ConnectorType = iff( Identity == prev(Identity) and ConnectorType != prev(ConnectorType) and prev(ConnectorType) !=\\\"\\\" , strcat(\\\"📍 \\\", ConnectorType, \\\" (\\\",prev(ConnectorType),\\\"->\\\", ConnectorType,\\\" )\\\"),ConnectorType)\\r\\n| extend ConnectorSource = iff( Identity == prev(Identity) and ConnectorSource != prev(ConnectorSource) and prev(ConnectorSource) !=\\\"\\\" , strcat(\\\"📍 \\\", ConnectorSource, \\\" (\\\",prev(ConnectorSource),\\\"->\\\", ConnectorSource,\\\" )\\\"),ConnectorSource)\\r\\n| extend CloudServicesMailEnabled = iff( Identity == prev(Identity) and CloudServicesMailEnabled != prev(CloudServicesMailEnabled) and prev(CloudServicesMailEnabled) !=\\\"\\\" , strcat(\\\"📍 \\\", CloudServicesMailEnabled, \\\" (\\\",prev(CloudServicesMailEnabled),\\\"->\\\", CloudServicesMailEnabled,\\\" )\\\"),CloudServicesMailEnabled)\\r\\n| extend Comment = iff( Comment == prev(Comment) and Comment != prev(Comment) and prev(Comment) !=\\\"\\\" , strcat(\\\"📍 \\\", Comment, \\\" (\\\",prev(Comment),\\\"->\\\", Comment,\\\" )\\\"),Comment)\\r\\n| extend RecipientDomains = iff( Identity == prev(Identity) and RecipientDomains != prev(RecipientDomains) and prev(RecipientDomains) !=\\\"\\\" , strcat(\\\"📍 \\\", RecipientDomains, \\\" (\\\",prev(RecipientDomains),\\\"->\\\", RecipientDomains,\\\" )\\\"),RecipientDomains)\\r\\n| extend SmartHosts = iff( Identity == prev(Identity) and SmartHosts != prev(SmartHosts) and prev(SmartHosts) !=\\\"\\\" , strcat(\\\"📍 \\\", SmartHosts, \\\" (\\\",prev(SmartHosts),\\\"->\\\", SmartHosts,\\\" )\\\"),SmartHosts)\\r\\n| extend TlsDomain = iff( Identity == prev(Identity) and TlsDomain != prev(TlsDomain) and prev(TlsDomain) !=\\\"\\\" , strcat(\\\"📍 \\\", TlsDomain, \\\" (\\\",prev(TlsDomain),\\\"->\\\", TlsDomain,\\\" )\\\"),TlsDomain)\\r\\n| extend IsTransportRuleScoped = iff( Identity == prev(Identity) and IsTransportRuleScoped != prev(IsTransportRuleScoped) and prev(IsTransportRuleScoped) !=\\\"\\\" , strcat(\\\"📍 \\\", IsTransportRuleScoped, \\\" (\\\",prev(IsTransportRuleScoped),\\\"->\\\", IsTransportRuleScoped,\\\" )\\\"),IsTransportRuleScoped)\\r\\n| extend RouteAllMessagesViaOnPremises = iff( Identity == prev(Identity) and RouteAllMessagesViaOnPremises != prev(RouteAllMessagesViaOnPremises) and prev(RouteAllMessagesViaOnPremises) !=\\\"\\\" , strcat(\\\"📍 \\\", RouteAllMessagesViaOnPremises, \\\" (\\\",prev(RouteAllMessagesViaOnPremises),\\\"->\\\", RouteAllMessagesViaOnPremises,\\\" )\\\"),RouteAllMessagesViaOnPremises)\\r\\n| extend AllAcceptedDomains = iff( Identity == prev(Identity) and AllAcceptedDomains != prev(AllAcceptedDomains) and prev(AllAcceptedDomains) !=\\\"\\\" , strcat(\\\"📍 \\\", AllAcceptedDomains, \\\" (\\\",prev(AllAcceptedDomains),\\\"->\\\", AllAcceptedDomains,\\\" )\\\"),AllAcceptedDomains)\\r\\n| extend SenderRewritingEnabled = iff( Identity == prev(Identity) and SenderRewritingEnabled != prev(SenderRewritingEnabled) and prev(SenderRewritingEnabled) !=\\\"\\\" , strcat(\\\"📍 \\\", SenderRewritingEnabled, \\\" (\\\",prev(SenderRewritingEnabled),\\\"->\\\", SenderRewritingEnabled,\\\" )\\\"),SenderRewritingEnabled)\\r\\n| extend TestMode = iff( Identity == prev(Identity)and TestMode != prev(TestMode) and prev(TestMode) !=\\\"\\\" , strcat(\\\"📍 \\\", TestMode, \\\" (\\\",prev(TestMode),\\\"->\\\", TestMode,\\\" )\\\"),TestMode)\\r\\n| extend LinkForModifiedConnector = iff( Identity == prev(Identity) and LinkForModifiedConnector != prev(LinkForModifiedConnector) and prev(LinkForModifiedConnector) !=\\\"\\\" , strcat(\\\"📍 \\\", LinkForModifiedConnector, \\\" (\\\",prev(LinkForModifiedConnector),\\\"->\\\", LinkForModifiedConnector,\\\" )\\\"),LinkForModifiedConnector)\\r\\n| extend ValidationRecipients = iff( Identity == prev(Identity) and ValidationRecipients != prev(ValidationRecipients) and prev(ValidationRecipients) !=\\\"\\\" , strcat(\\\"📍 \\\", ValidationRecipients, \\\" (\\\",prev(ValidationRecipients),\\\"->\\\", ValidationRecipients,\\\" )\\\"),ValidationRecipients)\\r\\n| extend IsValidated = iff( Identity == prev(Identity) and IsValidated != prev(IsValidated) and prev(IsValidated) !=\\\"\\\" , strcat(\\\"📍 \\\", IsValidated, \\\" (\\\",prev(IsValidated),\\\"->\\\", IsValidated,\\\" )\\\"),IsValidated)\\r\\n| extend LastValidationTimestamp = iff( Identity == prev(Identity) and LastValidationTimestamp != prev(LastValidationTimestamp) and prev(LastValidationTimestamp) !=\\\"\\\" , strcat(\\\"📍 \\\", LastValidationTimestamp, \\\" (\\\",prev(LastValidationTimestamp),\\\"->\\\", LastValidationTimestamp,\\\" )\\\"),LastValidationTimestamp)\\r\\n| extend Comment = iff( Identity == prev(Identity) and Comment != prev(Comment) and prev(Comment) !=\\\"\\\" , strcat(\\\"📍 \\\", Comment, \\\" (\\\",prev(Comment),\\\"->\\\", Comment,\\\" )\\\"),Comment)\\r\\n| extend ActiontypeR =iff((Identity contains \\\"📍\\\" or State contains \\\"📍\\\" or ConnectorType contains \\\"📍\\\" or ConnectorSource contains \\\"📍\\\"or CloudServicesMailEnabled contains \\\"📍\\\" or Comment contains \\\"📍\\\" or UseMXRecord contains \\\"📍\\\" or RecipientDomains contains \\\"📍\\\" or SmartHosts contains \\\"📍\\\" or TlsDomain contains \\\"📍\\\" or TlsSettings contains \\\"📍\\\" or IsTransportRuleScoped contains \\\"📍\\\" or RouteAllMessagesViaOnPremises contains \\\"📍\\\" or AllAcceptedDomains contains \\\"📍\\\" or SenderRewritingEnabled contains \\\"📍\\\" or TestMode contains \\\"📍\\\" or LinkForModifiedConnector contains \\\"📍\\\" or ValidationRecipients contains \\\"📍\\\" or IsValidated contains \\\"📍\\\" or LastValidationTimestamp contains \\\"📍\\\" ), i=i + 1, i)\\r\\n| extend Actiontype =iff(ActiontypeR > 0, \\\"Modif\\\", \\\"NO\\\")\\r\\n| where ActiontypeR == 1\\r\\n| project WhenChanged,Actiontype,Identity,State,ConnectorType,ConnectorSource,UseMXRecord,RecipientDomains,SmartHosts,TlsDomain,TlsSettings,IsTransportRuleScoped,RouteAllMessagesViaOnPremises,CloudServicesMailEnabled,AllAcceptedDomains,SenderRewritingEnabled,TestMode,LinkForModifiedConnector,ValidationRecipients,IsValidated,LastValidationTimestamp,Comment,WhenCreated\\r\\n;\\r\\nunion DiffAddData, DiffRemoveData, DiffModifData\\r\\n| extend WhenChanged = iff (Actiontype == \\\"Modif\\\", WhenChanged, iff(Actiontype == \\\"Add\\\",WhenCreated, WhenChanged))\\r\\n| extend Actiontype = case(Actiontype == \\\"Add\\\", strcat(\\\"➕ \\\", Actiontype), Actiontype == \\\"Remove\\\", strcat(\\\"➖ \\\", Actiontype), Actiontype == \\\"Modif\\\", strcat(\\\"📍 \\\", Actiontype), \\\"N/A\\\")\\r\\n| sort by WhenChanged desc \\r\\n| project\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n State,\\r\\n ConnectorType,\\r\\n ConnectorSource, \\r\\n CloudServicesMailEnabled,\\r\\n Comment,\\r\\n UseMXRecord,\\r\\n RecipientDomains,\\r\\n SmartHosts,\\r\\n TlsDomain,\\r\\n TlsSettings,\\r\\n IsTransportRuleScoped,\\r\\n RouteAllMessagesViaOnPremises,\\r\\n AllAcceptedDomains,\\r\\n SenderRewritingEnabled,\\r\\n TestMode,\\r\\n LinkForModifiedConnector,\\r\\n ValidationRecipients,\\r\\n IsValidated,\\r\\n LastValidationTimestamp,\\r\\n WhenCreated\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"Identity\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 4\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Transport Rules with specific actions to monitor\",\"items\":[{\"type\":1,\"content\":{\"json\":\"A common way used by attackers to exfiltrate data is to set Transport Rules that send all or sensitive messages outside the organization or to a mailbox where they already have full control.\\r\\n\\r\\nThis section shows your Transport rules with sentitive actions that can lead to data leaks:\\r\\n- BlindCopyTo\\r\\n- SentTo\\r\\n- CopyTo\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"TransportRulesHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"TransportRule\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n| extend Identity = iif( CmdletResultValue.Identity contains \\\"OrgHierarchyToIgnore\\\",tostring(CmdletResultValue.Identity.Name),tostring(CmdletResultValue.Identity))\\r\\n| extend State = tostring(CmdletResultValue.State)\\r\\n| extend SentTo = tostring(CmdletResultValue.SentToString)\\r\\n| extend BlindCopyTo = tostring(CmdletResultValue.BlindCopyToString)\\r\\n| extend CopyTo = tostring(CmdletResultValue.CopyToString)\\r\\n| extend RedirectMessageTo = tostring(CmdletResultValue.RedirectMessageToString)\\r\\n| extend SetSCL = tostring(CmdletResultValue.SetSCL)\\r\\n| extend SenderIpRangesString = tostring(CmdletResultValue.SenderIpRangesString)\\r\\n| extend MessageTypeMatchesString = tostring(CmdletResultValue.MessageTypeMatchesString)\\r\\n| extend Mode = tostring(CmdletResultValue.Mode)\\r\\n| project-away CmdletResultValue\\r\\n| sort by Identity asc\",\"size\":1,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Transport Rules actions to monitor\"},{\"type\":1,\"content\":{\"json\":\"** Due to lack of informaiton in Powershell, the Transport Rule compare section could display approximate information for Add and Modif. Especially, for the WhenCreated parameter.\"},\"name\":\"text - 7\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let _EnvList ={EnvironmentList};\\r\\nlet _TypeEnv = \\\"Online\\\";\\r\\nlet _DateCompare = \\\"{DateCompare:value}\\\";\\r\\nlet _CurrentDate = \\\"{DateOfConfiguration:value}\\\";\\r\\nlet _DateCompareB = todatetime(_DateCompare);\\r\\nlet _currD = (ExchangeConfiguration(SpecificSectionList=\\\"TransportRule\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n| summarize TimeMax = arg_max(TimeGenerated,*)\\r\\n| extend TimeMax = tostring(split(TimeMax,\\\"T\\\")[0])\\r\\n| project TimeMax);\\r\\nlet _CurrentDateB = todatetime(toscalar(_currD));\\r\\nlet BeforeData = ExchangeConfiguration(SpecificSectionList=\\\"TransportRule\\\",SpecificConfigurationDate=_DateCompareB,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend SentTo = tostring(CmdletResultValue.SentToString)\\r\\n\\t| extend BlindCopyTo = tostring(CmdletResultValue.BlindCopyToString)\\r\\n\\t| extend CopyTo = tostring(CmdletResultValue.CopyToString)\\r\\n\\t| extend RedirectMessageTo = tostring(CmdletResultValue.RedirectMessageToString)\\r\\n\\t| extend Mode = tostring(CmdletResultValue.Mode)\\r\\n | extend SetSCL = tostring(CmdletResultValue.SetSCL)\\r\\n | extend SenderIpRangesString = tostring(CmdletResultValue.SenderIpRangesString)\\r\\n | extend MessageTypeMatchesString = tostring(CmdletResultValue.MessageTypeMatchesString)\\r\\n | extend CmdletResultValue.RedirectMessageToString\\r\\n\\t| extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet AfterData = \\r\\n ExchangeConfiguration(SpecificSectionList=\\\"TransportRule\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend SentTo = tostring(CmdletResultValue.SentToString)\\r\\n\\t| extend BlindCopyTo = tostring(CmdletResultValue.BlindCopyToString)\\r\\n\\t| extend CopyTo = tostring(CmdletResultValue.CopyToString)\\r\\n\\t| extend RedirectMessageTo = tostring(CmdletResultValue.RedirectMessageToString)\\r\\n\\t| extend Mode = tostring(CmdletResultValue.Mode)\\r\\n | extend SetSCL = tostring(CmdletResultValue.SetSCL)\\r\\n | extend SenderIpRangesString = tostring(CmdletResultValue.SenderIpRangesString)\\r\\n | extend MessageTypeMatchesString = tostring(CmdletResultValue.MessageTypeMatchesString)\\r\\n\\t| extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet i=0;\\r\\nlet allDataRange =\\r\\n ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"TransportRule\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | project TimeGenerated,CmdletResultValue,WhenChanged = todatetime(WhenChanged_t), WhenCreated=todatetime(WhenCreated_t)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| sort by Identity,TimeGenerated asc\\r\\n | extend SentTo = tostring(CmdletResultValue.SentToString)\\r\\n\\t| extend BlindCopyTo = tostring(CmdletResultValue.BlindCopyToString)\\r\\n\\t| extend CopyTo = tostring(CmdletResultValue.CopyToString)\\r\\n\\t| extend RedirectMessageTo = tostring(CmdletResultValue.RedirectMessageToString)\\r\\n\\t| extend Mode = tostring(CmdletResultValue.Mode)\\r\\n | extend CmdletResultValue.RedirectMessageToString\\r\\n | extend SetSCL = tostring(CmdletResultValue.SetSCL)\\r\\n | extend SenderIpRangesString = tostring(CmdletResultValue.SenderIpRangesString)\\r\\n | extend MessageTypeMatchesString = tostring(CmdletResultValue.MessageTypeMatchesString)\\r\\n | extend WhenChanged = todatetime(bin(WhenChanged,1m))\\r\\n | extend aa=prev(WhenCreated)\\r\\n | extend WhenCreated = iff( Identity == prev(Identity) and WhenChanged != prev(WhenChanged),aa ,WhenChanged)\\r\\n | extend WhenCreated =bin(WhenCreated,1m)\\r\\n ;\\r\\nlet DiffAddDataP1 = allDataRange\\r\\n | join kind = rightanti (AfterData | where WhenCreated >=_DateCompareB) on WhenCreated\\r\\n;\\r\\nlet DiffAddDataP2 = allDataRange\\r\\n | join kind = inner (allDataRange ) on WhenCreated\\r\\n | where WhenCreated >=_DateCompareB\\r\\n | where bin(WhenCreated,5m)==bin(WhenChanged,5m)\\r\\n | distinct Identity,SentTo,BlindCopyTo,CopyTo,RedirectMessageTo,Mode,SetSCL,SenderIpRangesString,MessageTypeMatchesString,WhenChanged,WhenCreated\\r\\n ;\\r\\nlet DiffAddData1 = union DiffAddDataP1,DiffAddDataP2\\r\\n| extend Actiontype =\\\"Add\\\";\\r\\nlet DiffAddData2 = union DiffAddDataP1,DiffAddDataP2\\r\\n| extend Actiontype =\\\"Add\\\"\\r\\n| distinct Identity;\\r\\nlet DiffAddData = DiffAddData1\\r\\n| join DiffAddData2 on Identity\\r\\n;\\r\\nlet DiffRemoveData = allDataRange\\r\\n | join kind = leftanti AfterData on Identity\\r\\n | extend Actiontype =\\\"Remove\\\"\\r\\n | distinct Actiontype ,Identity,SentTo,BlindCopyTo,CopyTo,RedirectMessageTo,SetSCL,SenderIpRangesString,MessageTypeMatchesString,Mode,WhenChanged,WhenCreated\\r\\n | project WhenChanged=_CurrentDateB,Actiontype,Identity,SentTo,BlindCopyTo,CopyTo,RedirectMessageTo,SetSCL,SenderIpRangesString,MessageTypeMatchesString,Mode,WhenCreated\\r\\n ;\\r\\nlet DiffModifData = union AfterData,allDataRange\\r\\n| sort by Identity,WhenChanged asc\\r\\n| project WhenChanged,Identity,SentTo,BlindCopyTo,CopyTo,RedirectMessageTo, SetSCL, SenderIpRangesString,MessageTypeMatchesString,Mode,WhenCreated\\r\\n| extend Identity = iff( Identity == prev(Identity) and Identity != prev(Identity) and prev(Identity) !=\\\"\\\" , strcat(\\\"📍 \\\", Identity, \\\" (\\\",prev(Identity),\\\"->\\\", Identity,\\\" )\\\"),Identity)\\r\\n| extend SentTo = iff( Identity == prev(Identity) and SentTo != prev(SentTo) and prev(SentTo) !=\\\"\\\" , strcat(\\\"📍 \\\", SentTo, \\\" (\\\",prev(SentTo),\\\"->\\\", SentTo,\\\" )\\\"),SentTo)\\r\\n| extend BlindCopyTo = iff( Identity == prev(Identity) and BlindCopyTo != prev(BlindCopyTo) and prev(BlindCopyTo) !=\\\"\\\" , strcat(\\\"📍 \\\", BlindCopyTo, \\\" (\\\",prev(BlindCopyTo),\\\"->\\\", BlindCopyTo,\\\" )\\\"),BlindCopyTo)\\r\\n| extend CopyTo = iff( Identity == prev(Identity) and CopyTo != prev(CopyTo) and prev(CopyTo) !=\\\"\\\" , strcat(\\\"📍 \\\", CopyTo, \\\" (\\\",prev(CopyTo),\\\"->\\\", CopyTo,\\\" )\\\"),CopyTo)\\r\\n| extend SetSCL = iff( Identity == prev(Identity)and SetSCL != prev(SetSCL) and prev(SetSCL) !=\\\"\\\" , strcat(\\\"📍 \\\", SetSCL, \\\" (\\\",prev(SetSCL),\\\"->\\\", SetSCL,\\\" )\\\"),SetSCL)\\r\\n| extend SenderIpRangesString = iff( Identity == prev(Identity)and SenderIpRangesString != prev(SenderIpRangesString) and prev(SenderIpRangesString) !=\\\"\\\" , strcat(\\\"📍 \\\", SenderIpRangesString, \\\" (\\\",prev(SenderIpRangesString),\\\"->\\\", SenderIpRangesString,\\\" )\\\"),SenderIpRangesString)\\r\\n| extend MessageTypeMatchesString = iff( Identity == prev(Identity)and MessageTypeMatchesString != prev(MessageTypeMatchesString) and prev(MessageTypeMatchesString) !=\\\"\\\" , strcat(\\\"📍 \\\", MessageTypeMatchesString, \\\" (\\\",prev(MessageTypeMatchesString),\\\"->\\\", MessageTypeMatchesString,\\\" )\\\"),MessageTypeMatchesString)\\r\\n| extend Mode = iff( Identity == prev(Identity)and Mode != prev(Mode) and prev(Mode) !=\\\"\\\" , strcat(\\\"📍 \\\", Mode, \\\" (\\\",prev(Mode),\\\"->\\\", Mode,\\\" )\\\"),Mode)\\r\\n| extend ActiontypeR =iff((Identity contains \\\"📍\\\" or SentTo contains \\\"📍\\\" or BlindCopyTo contains \\\"📍\\\" or CopyTo contains \\\"📍\\\" or SetSCL contains \\\"📍\\\" or SenderIpRangesString contains \\\"📍\\\" or MessageTypeMatchesString contains \\\"📍\\\" or Mode contains \\\"📍\\\" ), i=i + 1, i)\\r\\n| extend Actiontype =iff(ActiontypeR > 0, \\\"Modif\\\", \\\"NO\\\")\\r\\n| where ActiontypeR == 1\\r\\n| project WhenChanged,Actiontype,Identity,SentTo,BlindCopyTo,CopyTo,RedirectMessageTo,SetSCL,SenderIpRangesString,MessageTypeMatchesString,Mode,WhenCreated\\r\\n;\\r\\nunion DiffAddData, DiffRemoveData, DiffModifData\\r\\n| extend WhenChanged = iff (Actiontype == \\\"Modif\\\", WhenChanged, iff(Actiontype == \\\"Add\\\",WhenCreated, WhenChanged))\\r\\n| extend Actiontype = case(Actiontype == \\\"Add\\\", strcat(\\\"➕ \\\", Actiontype), Actiontype == \\\"Remove\\\", strcat(\\\"➖ \\\", Actiontype), Actiontype == \\\"Modif\\\", strcat(\\\"📍 \\\", Actiontype), \\\"N/A\\\")\\r\\n| sort by WhenChanged desc \\r\\n| project\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n SentTo,\\r\\n BlindCopyTo,\\r\\n CopyTo,\\r\\n RedirectMessageTo,\\r\\n SetSCL,\\r\\n SenderIpRangesString,\\r\\n MessageTypeMatchesString,\\r\\n Mode,\\r\\n WhenCreated\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"Identity\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 5\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Outbound Policy : Autoforward configuration\",\"items\":[{\"type\":1,\"content\":{\"json\":\"If **AutoForwardEnabled** is enabled, then automatic transfer are allowed.\\r\\nFor example: users in Outlook will be able set automatic transfer of all their emails to external addresses.\\r\\nThere are several methods to authorized automatic forward. \\r\\nPlease review this article : https://learn.microsoft.com/microsoft-365/security/office-365-security/outbound-spam-policies-external-email-forwarding?view=o365-worldwide\\r\\n**In summary :**\\r\\n\\r\\n**Scenario 1 :**\\r\\n\\r\\nYou configure remote domain settings to allow automatic forwarding.\\r\\nAutomatic forwarding in the outbound spam filter policy is set to Off.\\r\\n*Result :* \\r\\nAutomatically forwarded messages to recipients in the affected domains are blocked.\\r\\n\\r\\n**Scenario 2 :**\\r\\n\\r\\nYou configure remote domain settings to allow automatic forwarding.\\r\\nAutomatic forwarding in the outbound spam filter policy is set to Automatic - System-controlled.\\r\\n\\r\\n*Result :* \\r\\n\\r\\nAutomatically forwarded messages to recipients in the affected domains are blocked.\\r\\nAs described earlier, Automatic - System-controlled used to mean On, but the setting has changed over time to mean Off in all organizations.\\r\\n\\r\\nFor absolute clarity, you should configure your outbound spam filter policy to On or Off.\\r\\n\\r\\n**Scenario 3 :**\\r\\n\\r\\nAutomatic forwarding in the outbound spam filter policy is set to On\\r\\nYou use mail flow rules or remote domains to block automatically forwarded email\\r\\n\\r\\n*Result : *\\r\\n\\r\\nAutomatically forwarded messages to affected recipients are blocked by mail flow rules or remote domains.\\r\\n****\\r\\nAlso, when setting AutoForwardEnabled to a specific domain, it is strongly recommended enable TLS encryption.\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"AutoForwardHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let HOSFR = ExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterRule\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend HostedOutboundSpamFilterPolicy = tostring(CmdletResultValue.HostedOutboundSpamFilterPolicy)\\r\\n| project Identity,HostedOutboundSpamFilterPolicy;\\r\\nExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterPolicy\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend Identity = tostring(CmdletResultValue.Identity)\\r\\n| join kind = fullouter HOSFR on $left.Identity == $right.HostedOutboundSpamFilterPolicy\\r\\n| extend OutboundSpamFilterRule = tostring(Identity1)\\r\\n| extend IsDefault= tostring(CmdletResultValue.IsDefault)\\r\\n| extend Enabled= tostring(CmdletResultValue.Enabled)\\r\\n| extend AutoForwardingMode= iff (CmdletResultValue.AutoForwardingMode == \\\"On\\\" , strcat (\\\"❌ \\\", tostring(CmdletResultValue.AutoForwardingMode)), tostring(CmdletResultValue.AutoForwardingMode))\\r\\n| extend RecommendedPolicyType= tostring(CmdletResultValue.RecommendedPolicyType)\\r\\n| extend RecipientLimitExternalPerHour = tostring(CmdletResultValue.RecipientLimitExternalPerHour)\\r\\n| extend RecipientLimitInternalPerHour = tostring(CmdletResultValue.RecipientLimitInternalPerHour)\\r\\n| extend RecipientLimitPerDay= tostring(CmdletResultValue.RecipientLimitPerDay)\\r\\n| extend ActionWhenThresholdReached = tostring(CmdletResultValue.ActionWhenThresholdReached)\\r\\n| extend BccSuspiciousOutboundAdditionalRecipients= tostring(CmdletResultValue.BccSuspiciousOutboundAdditionalRecipients)\\r\\n| extend BccSuspiciousOutboundMail = tostring(CmdletResultValue.BccSuspiciousOutboundMail)\\r\\n| extend NotifyOutboundSpam= tostring(CmdletResultValue.NotifyOutboundSpam)\\r\\n| extend NotifyOutboundSpamRecipient = tostring(CmdletResultValue.NotifyOutboundSpamRecipient)\\r\\n| project Identity,IsDefault,Enabled,AutoForwardingMode,OutboundSpamFilterRule,BccSuspiciousOutboundAdditionalRecipients,BccSuspiciousOutboundMail,NotifyOutboundSpam,NotifyOutboundSpamRecipient,WhenChanged,WhenCreated\\r\\n| sort by Identity asc \",\"size\":1,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true}},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"OutboundPol - Copy\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let _EnvList ={EnvironmentList};\\r\\nlet _TypeEnv = \\\"Online\\\";\\r\\nlet _DateCompare = \\\"{DateCompare:value}\\\";\\r\\nlet _CurrentDate = \\\"{DateOfConfiguration:value}\\\";\\r\\nlet _DateCompareB = todatetime(_DateCompare);\\r\\nlet _currD = (ExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterPolicy\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n| summarize TimeMax = arg_max(TimeGenerated,*)\\r\\n| extend TimeMax = tostring(split(TimeMax,\\\"T\\\")[0])\\r\\n| project TimeMax);\\r\\nlet _CurrentDateB = todatetime(toscalar(_currD));\\r\\nlet HOSFR = ExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterRule\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend HostedOutboundSpamFilterPolicy = tostring(CmdletResultValue.HostedOutboundSpamFilterPolicy)\\r\\n| project Identity,HostedOutboundSpamFilterPolicy;\\r\\nlet BeforeData = ExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterPolicy\\\", SpecificConfigurationDate=_DateCompareB, SpecificConfigurationEnv=_EnvList, Target = _TypeEnv)\\r\\n | extend Identity = tostring(Identity)\\r\\n | join kind = fullouter HOSFR on $left.Identity == $right.HostedOutboundSpamFilterPolicy\\r\\n | extend OutboundSpamFilterRule = tostring(Identity1)\\r\\n | extend IsDefault= tostring(CmdletResultValue.IsDefault)\\r\\n | extend Enabled= tostring(CmdletResultValue.Enabled)\\r\\n | extend AutoForwardingMode= tostring(CmdletResultValue.AutoForwardingMode)\\r\\n | extend RecommendedPolicyType= tostring(CmdletResultValue.RecommendedPolicyType)\\r\\n | extend RecipientLimitExternalPerHour = tostring(CmdletResultValue.RecipientLimitExternalPerHour)\\r\\n | extend RecipientLimitInternalPerHour = tostring(CmdletResultValue.RecipientLimitInternalPerHour)\\r\\n | extend RecipientLimitPerDay= tostring(CmdletResultValue.RecipientLimitPerDay)\\r\\n | extend ActionWhenThresholdReached = tostring(CmdletResultValue.ActionWhenThresholdReached)\\r\\n | extend BccSuspiciousOutboundAdditionalRecipients= tostring(CmdletResultValue.BccSuspiciousOutboundAdditionalRecipients)\\r\\n | extend BccSuspiciousOutboundMail = tostring(CmdletResultValue.BccSuspiciousOutboundMail)\\r\\n | extend NotifyOutboundSpam= tostring(CmdletResultValue.NotifyOutboundSpam)\\r\\n | extend NotifyOutboundSpamRecipient = tostring(CmdletResultValue.NotifyOutboundSpamRecipient)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet AfterData = \\r\\n ExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterPolicy\\\", SpecificConfigurationDate=_CurrentDate, SpecificConfigurationEnv=_EnvList, Target = _TypeEnv)\\r\\n | extend Identity = tostring(Identity)\\r\\n | join kind = fullouter HOSFR on $left.Identity == $right.HostedOutboundSpamFilterPolicy\\r\\n | extend OutboundSpamFilterRule = tostring(Identity1)\\r\\n | extend IsDefault= tostring(CmdletResultValue.IsDefault)\\r\\n | extend Enabled= tostring(CmdletResultValue.Enabled)\\r\\n | extend AutoForwardingMode= tostring(CmdletResultValue.AutoForwardingMode)\\r\\n | extend RecommendedPolicyType= tostring(CmdletResultValue.RecommendedPolicyType)\\r\\n | extend RecipientLimitExternalPerHour = tostring(CmdletResultValue.RecipientLimitExternalPerHour)\\r\\n | extend RecipientLimitInternalPerHour = tostring(CmdletResultValue.RecipientLimitInternalPerHour)\\r\\n | extend RecipientLimitPerDay= tostring(CmdletResultValue.RecipientLimitPerDay)\\r\\n | extend ActionWhenThresholdReached = tostring(CmdletResultValue.ActionWhenThresholdReached)\\r\\n | extend BccSuspiciousOutboundAdditionalRecipients= tostring(CmdletResultValue.BccSuspiciousOutboundAdditionalRecipients)\\r\\n | extend BccSuspiciousOutboundMail = tostring(CmdletResultValue.BccSuspiciousOutboundMail)\\r\\n | extend NotifyOutboundSpam= tostring(CmdletResultValue.NotifyOutboundSpam)\\r\\n | extend NotifyOutboundSpamRecipient = tostring(CmdletResultValue.NotifyOutboundSpamRecipient)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet i=0;\\r\\nlet allDataRangeOSFR = ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"HostedOutboundSpamFilterRule\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n | extend HostedOutboundSpamFilterPolicy = tostring(CmdletResultValue.HostedOutboundSpamFilterPolicy)\\r\\n | project Identity, HostedOutboundSpamFilterPolicy;\\r\\nlet allDataRange = \\r\\n ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"HostedOutboundSpamFilterPolicy\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n | project\\r\\n TimeGenerated,\\r\\n Identity,\\r\\n CmdletResultValue,\\r\\n WhenChanged = todatetime(bin(WhenChanged_t,1m)),\\r\\n WhenCreated=todatetime(bin(WhenCreated_t,1m))\\r\\n | join kind=fullouter allDataRangeOSFR on $left.Identity == $right.HostedOutboundSpamFilterPolicy\\r\\n | extend OutboundSpamFilterRule = tostring(Identity1)\\r\\n | extend IsDefault= tostring(CmdletResultValue.IsDefault)\\r\\n | extend Enabled= tostring(CmdletResultValue.Enabled)\\r\\n | extend AutoForwardingMode= tostring(CmdletResultValue.AutoForwardingMode)\\r\\n | extend RecommendedPolicyType= tostring(CmdletResultValue.RecommendedPolicyType)\\r\\n | extend RecipientLimitExternalPerHour = tostring(CmdletResultValue.RecipientLimitExternalPerHour)\\r\\n | extend RecipientLimitInternalPerHour = tostring(CmdletResultValue.RecipientLimitInternalPerHour)\\r\\n | extend RecipientLimitPerDay= tostring(CmdletResultValue.RecipientLimitPerDay)\\r\\n | extend ActionWhenThresholdReached = tostring(CmdletResultValue.ActionWhenThresholdReached)\\r\\n | extend BccSuspiciousOutboundAdditionalRecipients= tostring(CmdletResultValue.BccSuspiciousOutboundAdditionalRecipients)\\r\\n | extend BccSuspiciousOutboundMail = tostring(CmdletResultValue.BccSuspiciousOutboundMail)\\r\\n | extend NotifyOutboundSpam= tostring(CmdletResultValue.NotifyOutboundSpam)\\r\\n | extend NotifyOutboundSpamRecipient = tostring(CmdletResultValue.NotifyOutboundSpamRecipient)\\r\\n | distinct\\r\\n WhenChanged,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \\r\\n;\\r\\nlet DiffAddDataP1 = allDataRange\\r\\n | join kind = rightanti (AfterData\\r\\n | where WhenCreated >= _DateCompareB)\\r\\n on WhenCreated\\r\\n;\\r\\nlet DiffAddDataP2 = allDataRange\\r\\n | join kind = innerunique (allDataRange) on WhenCreated\\r\\n | where WhenCreated >= _DateCompareB\\r\\n | where bin(WhenCreated, 5m) == bin(WhenChanged, 5m)\\r\\n | distinct\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \\r\\n;\\r\\nlet DiffAddData = union DiffAddDataP1, DiffAddDataP2\\r\\n | extend Actiontype =\\\"Add\\\";\\r\\nlet DiffRemoveData = allDataRange\\r\\n | join kind = leftanti AfterData on Identity\\r\\n | extend Actiontype =\\\"Remove\\\"\\r\\n | distinct\\r\\n Actiontype,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \\r\\n | project\\r\\n WhenChanged=_CurrentDateB,\\r\\n Actiontype,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated\\r\\n;\\r\\nlet DiffModifData = union AfterData, allDataRange\\r\\n | sort by Identity, WhenChanged asc\\r\\n | project\\r\\n WhenChanged,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \\r\\n | extend Identity = iff(Identity == prev(Identity) and Identity != prev(Identity) and prev(Identity) != \\\"\\\", strcat(\\\"📍 \\\", Identity, \\\" (\\\", prev(Identity), \\\"->\\\", Identity, \\\" )\\\"), Identity)\\r\\n | extend IsDefault = iff(Identity == prev(Identity) and IsDefault != prev(IsDefault) and prev(IsDefault) != \\\"\\\", strcat(\\\"📍 \\\", IsDefault, \\\" (\\\", prev(IsDefault), \\\"->\\\", IsDefault, \\\" )\\\"), IsDefault)\\r\\n | extend Enabled = iff(Identity == prev(Identity) and Enabled != prev(Enabled) and prev(Enabled) != \\\"\\\", strcat(\\\"📍 \\\", Enabled, \\\" (\\\", prev(Enabled), \\\"->\\\", Enabled, \\\" )\\\"), Enabled)\\r\\n | extend AutoForwardingMode = iff(Identity == prev(Identity) and AutoForwardingMode != prev(AutoForwardingMode) and prev(AutoForwardingMode) != \\\"\\\", strcat(\\\"📍 \\\", AutoForwardingMode, \\\" (\\\", prev(AutoForwardingMode), \\\"->\\\", AutoForwardingMode, \\\" )\\\"), AutoForwardingMode)\\r\\n | extend OutboundSpamFilterRule = iff(Identity == prev(Identity) and OutboundSpamFilterRule != prev(OutboundSpamFilterRule) and prev(OutboundSpamFilterRule) != \\\"\\\", strcat(\\\"📍 \\\", OutboundSpamFilterRule, \\\" (\\\", prev(OutboundSpamFilterRule), \\\"->\\\", OutboundSpamFilterRule, \\\" )\\\"), OutboundSpamFilterRule)\\r\\n | extend RecommendedPolicyType = iff(Identity == prev(Identity) and RecommendedPolicyType != prev(RecommendedPolicyType) and prev(RecommendedPolicyType) != \\\"\\\", strcat(\\\"📍 \\\", RecommendedPolicyType, \\\" (\\\", prev(RecommendedPolicyType), \\\"->\\\", RecommendedPolicyType, \\\" )\\\"), RecommendedPolicyType)\\r\\n | extend RecipientLimitExternalPerHour = iff(Identity == prev(Identity) and RecipientLimitExternalPerHour != prev(RecipientLimitExternalPerHour) and prev(RecipientLimitExternalPerHour) != \\\"\\\", strcat(\\\"📍 \\\", RecipientLimitExternalPerHour, \\\" (\\\", prev(RecipientLimitExternalPerHour), \\\"->\\\", RecipientLimitExternalPerHour, \\\" )\\\"), RecipientLimitExternalPerHour)\\r\\n | extend RecipientLimitInternalPerHour = iff(Identity == prev(Identity) and RecipientLimitInternalPerHour != prev(RecipientLimitInternalPerHour) and prev(RecipientLimitInternalPerHour) != \\\"\\\", strcat(\\\"📍 \\\", RecipientLimitInternalPerHour, \\\" (\\\", prev(RecipientLimitInternalPerHour), \\\"->\\\", RecipientLimitInternalPerHour, \\\" )\\\"), RecipientLimitInternalPerHour)\\r\\n | extend ActionWhenThresholdReached = iff(Identity == prev(Identity) and ActionWhenThresholdReached != prev(ActionWhenThresholdReached) and prev(ActionWhenThresholdReached) != \\\"\\\", strcat(\\\"📍 \\\", ActionWhenThresholdReached, \\\" (\\\", prev(ActionWhenThresholdReached), \\\"->\\\", ActionWhenThresholdReached, \\\" )\\\"), ActionWhenThresholdReached)\\r\\n | extend RecipientLimitPerDay = iff(Identity == prev(Identity) and RecipientLimitPerDay != prev(RecipientLimitPerDay) and prev(RecipientLimitPerDay) != \\\"\\\", strcat(\\\"📍 \\\", RecipientLimitPerDay, \\\" (\\\", prev(RecipientLimitPerDay), \\\"->\\\", RecipientLimitPerDay, \\\" )\\\"), RecipientLimitPerDay)\\r\\n | extend BccSuspiciousOutboundAdditionalRecipients = iff(Identity == prev(Identity) and BccSuspiciousOutboundAdditionalRecipients != prev(BccSuspiciousOutboundAdditionalRecipients) and prev(BccSuspiciousOutboundAdditionalRecipients) != \\\"\\\", strcat(\\\"📍 \\\", BccSuspiciousOutboundAdditionalRecipients, \\\" (\\\", prev(BccSuspiciousOutboundAdditionalRecipients), \\\"->\\\", BccSuspiciousOutboundAdditionalRecipients, \\\" )\\\"), BccSuspiciousOutboundAdditionalRecipients)\\r\\n | extend BccSuspiciousOutboundMail = iff(Identity == prev(Identity) and BccSuspiciousOutboundMail != prev(BccSuspiciousOutboundMail) and prev(BccSuspiciousOutboundMail) != \\\"\\\", strcat(\\\"📍 \\\", BccSuspiciousOutboundMail, \\\" (\\\", prev(BccSuspiciousOutboundMail), \\\"->\\\", BccSuspiciousOutboundMail, \\\" )\\\"), BccSuspiciousOutboundMail)\\r\\n | extend NotifyOutboundSpam = iff(Identity == prev(Identity) and NotifyOutboundSpam != prev(NotifyOutboundSpam) and prev(NotifyOutboundSpam) != \\\"\\\", strcat(\\\"📍 \\\", NotifyOutboundSpam, \\\" (\\\", prev(NotifyOutboundSpam), \\\"->\\\", NotifyOutboundSpam, \\\" )\\\"), NotifyOutboundSpam)\\r\\n | extend NotifyOutboundSpamRecipient = iff(Identity == prev(Identity) and NotifyOutboundSpamRecipient != prev(NotifyOutboundSpamRecipient) and prev(NotifyOutboundSpamRecipient) != \\\"\\\", strcat(\\\"📍 \\\", NotifyOutboundSpamRecipient, \\\" (\\\", prev(NotifyOutboundSpamRecipient), \\\"->\\\", NotifyOutboundSpamRecipient, \\\" )\\\"), NotifyOutboundSpamRecipient)\\r\\n | extend ActiontypeR =iff((Identity contains \\\"📍\\\" or IsDefault contains \\\"📍\\\" or Enabled contains \\\"📍\\\" or OutboundSpamFilterRule contains \\\"📍\\\" or AutoForwardingMode contains \\\"📍\\\" or BccSuspiciousOutboundAdditionalRecipients contains \\\"📍\\\" or BccSuspiciousOutboundMail contains \\\"📍\\\" or NotifyOutboundSpam contains \\\"📍\\\" or NotifyOutboundSpamRecipient contains \\\"📍\\\"), i=i + 1, i)\\r\\n | extend Actiontype =iff(ActiontypeR > 0, \\\"Modif\\\", \\\"NO\\\")\\r\\n | where ActiontypeR == 1\\r\\n | distinct\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \\r\\n;\\r\\nunion DiffAddData, DiffRemoveData, DiffModifData\\r\\n| extend WhenChanged = iff (Actiontype == \\\"Modif\\\", WhenChanged, iff(Actiontype == \\\"Add\\\", WhenCreated, WhenChanged))\\r\\n| extend Actiontype = case(Actiontype == \\\"Add\\\", strcat(\\\"➕ \\\", Actiontype), Actiontype == \\\"Remove\\\", strcat(\\\"➖ \\\", Actiontype), Actiontype == \\\"Modif\\\", strcat(\\\"📍 \\\", Actiontype), \\\"N/A\\\")\\r\\n| sort by WhenChanged desc \\r\\n| project\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"Identity\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 7 - Copy\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Remote Domain Autofoward Configuration - * should not allow AutoForwardEnabled\",\"items\":[{\"type\":1,\"content\":{\"json\":\"If **AutoForwardEnabled** is set to True for an SMTP domain and the Outbound Policy is set to On then users in Outlook are allowed to set automatic transfer of all their emails to addresses in this domain.\\r\\n\\r\\nWhen the Default Remote domain is set to * and has the AutoForwardEnabled set True, any user can configure an Outlook rule to automatically forward all emails to any SMTP domain domains outside the organization. This is a high risk configuration as it might allow accounts to leak information. \\r\\n\\r\\nAlso, when setting AutoForwardEnabled to a specific domain, it is strongly recommended enable TLS encryption.\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"AutoForwardHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"RemoteDomain\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n| extend Name = tostring(CmdletResultValue.Name)\\r\\n| extend Address = tostring(CmdletResultValue.DomainName)\\r\\n| extend AutoForwardEnabled = iff (CmdletResultValue.AutoForwardEnabled== \\\"true\\\" and CmdletResultValue.DomainName == \\\"*\\\", strcat (\\\"❌ \\\",tostring(CmdletResultValue.AutoForwardEnabled)),iff(CmdletResultValue.AutoForwardEnabled== \\\"true\\\" and CmdletResultValue.DomainName != \\\"*\\\", strcat (\\\"⚠️ \\\",tostring(CmdletResultValue.AutoForwardEnabled)),strcat (\\\"✅ \\\",tostring(CmdletResultValue.AutoForwardEnabled))))\\r\\n| project-away CmdletResultValue\\r\\n| sort by Address asc \",\"size\":1,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true}},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"ForwardGroup\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let _EnvList ={EnvironmentList};\\r\\nlet _TypeEnv = \\\"Online\\\";\\r\\nlet _DateCompare = \\\"{DateCompare:value}\\\";\\r\\nlet _CurrentDate = \\\"{DateOfConfiguration:value}\\\";\\r\\nlet _DateCompareB = todatetime(_DateCompare);\\r\\nlet _currD = (ExchangeConfiguration(SpecificSectionList=\\\"RemoteDomain\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n| summarize TimeMax = arg_max(TimeGenerated,*)\\r\\n| extend TimeMax = tostring(split(TimeMax,\\\"T\\\")[0])\\r\\n| project TimeMax);\\r\\nlet _CurrentDateB = todatetime(toscalar(_currD));\\r\\nlet BeforeData = ExchangeConfiguration(SpecificSectionList=\\\"RemoteDomain\\\",SpecificConfigurationDate=_DateCompareB,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Name)\\r\\n\\t| extend DomainName = tostring(CmdletResultValue.DomainName)\\r\\n\\t| extend AutoForwardEnabled = tostring(CmdletResultValue.AutoForwardEnabled)\\r\\n\\t| extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet AfterData = \\r\\n ExchangeConfiguration(SpecificSectionList=\\\"RemoteDomain\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n \\t | extend Identity = tostring(CmdletResultValue.Name)\\r\\n\\t| extend DomainName = tostring(CmdletResultValue.DomainName)\\r\\n\\t| extend AutoForwardEnabled = tostring(CmdletResultValue.AutoForwardEnabled)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet i=0;\\r\\nlet allDataRange = \\r\\n ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"RemoteDomain\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | project TimeGenerated,CmdletResultValue,WhenChanged = todatetime(WhenChanged_t), WhenCreated=todatetime(WhenCreated_t)\\r\\n | extend Identity = tostring(CmdletResultValue.Name)\\r\\n\\t| extend DomainName = tostring(CmdletResultValue.DomainName)\\r\\n\\t| extend AutoForwardEnabled = tostring(CmdletResultValue.AutoForwardEnabled)\\r\\n ;\\r\\nlet DiffAddDataP1 = allDataRange\\r\\n | join kind = rightanti (AfterData | where WhenCreated >=_DateCompareB) on WhenCreated\\r\\n;\\r\\nlet DiffAddDataP2 = allDataRange\\r\\n | join kind = innerunique (allDataRange ) on WhenCreated\\r\\n | where WhenCreated >=_DateCompareB\\r\\n | where bin(WhenCreated,5m)==bin(WhenChanged,5m)\\r\\n | distinct Identity,DomainName,AutoForwardEnabled,WhenChanged,WhenCreated\\r\\n ;\\r\\nlet DiffAddData = union DiffAddDataP1,DiffAddDataP2\\r\\n| extend Actiontype =\\\"Add\\\";\\r\\nlet DiffRemoveData = allDataRange\\r\\n | join kind = leftanti AfterData on Identity\\r\\n | extend Actiontype =\\\"Remove\\\"\\r\\n | distinct Actiontype ,Identity,DomainName,AutoForwardEnabled,WhenCreated\\r\\n | project WhenChanged=_CurrentDateB,Actiontype,Identity,DomainName,AutoForwardEnabled,WhenCreated\\r\\n ;\\r\\nlet DiffModifData = union AfterData,allDataRange\\r\\n| sort by Identity,WhenChanged asc\\r\\n| project WhenChanged,Identity,DomainName,AutoForwardEnabled,WhenCreated\\r\\n| extend Identity = iff( Identity == prev(Identity) and Identity != prev(Identity) and prev(Identity) !=\\\"\\\" , strcat(\\\"📍 \\\", Identity, \\\" (\\\",prev(Identity),\\\"->\\\", Identity,\\\" )\\\"),Identity)\\r\\n| extend DomainName = iff( Identity == prev(Identity) and DomainName != prev(DomainName) and prev(DomainName) !=\\\"\\\" , strcat(\\\"📍 \\\", DomainName, \\\" (\\\",prev(DomainName),\\\"->\\\", DomainName,\\\" )\\\"),DomainName)\\r\\n| extend AutoForwardEnabled = iff( Identity == prev(Identity) and AutoForwardEnabled != prev(AutoForwardEnabled) and prev(AutoForwardEnabled) !=\\\"\\\" , strcat(\\\"📍 \\\", AutoForwardEnabled, \\\" (\\\",prev(AutoForwardEnabled),\\\"->\\\", AutoForwardEnabled,\\\" )\\\"),AutoForwardEnabled)\\r\\n| extend ActiontypeR =iff((Identity contains \\\"📍\\\" or DomainName contains \\\"📍\\\" or AutoForwardEnabled contains \\\"📍\\\" ), i=i + 1, i)\\r\\n| extend Actiontype =iff(ActiontypeR > 0, \\\"Modif\\\", \\\"NO\\\")\\r\\n| where ActiontypeR == 1\\r\\n| project WhenChanged,Actiontype,Identity,DomainName,AutoForwardEnabled,WhenCreated\\r\\n;\\r\\nunion DiffAddData, DiffRemoveData, DiffModifData\\r\\n| extend WhenChanged = iff (Actiontype == \\\"Modif\\\", WhenChanged, iff(Actiontype == \\\"Add\\\",WhenCreated, WhenChanged))\\r\\n| extend Actiontype = case(Actiontype == \\\"Add\\\", strcat(\\\"➕ \\\", Actiontype), Actiontype == \\\"Remove\\\", strcat(\\\"➖ \\\", Actiontype), Actiontype == \\\"Modif\\\", strcat(\\\"📍 \\\", Actiontype), \\\"N/A\\\")\\r\\n| sort by WhenChanged desc \\r\\n| project\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n DomainName,\\r\\n AutoForwardEnabled,\\r\\n WhenCreated\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"Identity\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 7\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Transport\"},\"name\":\"Transport Security configuration\"}],\"fromTemplateId\":\"sentinel-MicrosoftExchangeSecurityReview-Online\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Microsoft Exchange Security Review Online\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"9ae328d6-99c8-4c44-8d59-42ca4d999098\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"EnvironmentList\",\"label\":\"Environment\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"ExchangeEnvironmentList(Target=\\\"Online\\\") | where ESIEnvironment != \\\"\\\"\",\"typeSettings\":{\"limitSelectTo\":1,\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"a88b4e41-eb2f-41bf-92d8-27c83650a4b8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"DateOfConfiguration\",\"label\":\"Collection time\",\"type\":2,\"isRequired\":true,\"query\":\"let _configurationEnv = split(iff(isnull({EnvironmentList}) or isempty({EnvironmentList}) or tolower({EnvironmentList}) == \\\"all\\\",\\\"All\\\",tostring({EnvironmentList})),',');\\r\\nESIExchangeOnlineConfig_CL\\r\\n| extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n| where ScopedEnvironment in (_configurationEnv)\\r\\n| extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n| summarize Collection = max(Collection)\\r\\n| project Collection = \\\"lastdate\\\", Selected = true\\r\\n| join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | summarize by Collection \\r\\n | join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm ')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | join kind=leftouter (\\r\\n ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | summarize count() by Collection\\r\\n ) on Collection\\r\\n ) on Collection\\r\\n) on Collection\\r\\n| project Value = iif(Selected,Collection,iif(count_ > 1,PreciseCollection,Collection1)), Label = iif(Selected,\\\"Last Known date\\\",iif(count_ > 1,PreciseCollection,Collection1)), Selected\\r\\n| sort by Selected, Value desc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"181fa282-a002-42f1-ad57-dfb86df3194e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Compare_Collect\",\"type\":10,\"description\":\"If this button is checked, two collections will be compared\",\"isRequired\":true,\"jsonData\":\"[\\r\\n { \\\"value\\\": \\\"True\\\", \\\"label\\\": \\\"Yes\\\" },\\r\\n { \\\"value\\\": \\\"True,False\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true }\\r\\n]\"},{\"id\":\"8ac96eb3-918b-4a36-bcc4-df50d8f46175\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Help\",\"label\":\"Show Help\",\"type\":10,\"isRequired\":true,\"query\":\"{\\\"version\\\":\\\"1.0.0\\\",\\\"content\\\":\\\"[\\\\r\\\\n { \\\\\\\"value\\\\\\\": \\\\\\\"Yes\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"Yes\\\\\\\"},\\\\r\\\\n {\\\\\\\"value\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"selected\\\\\\\":true }\\\\r\\\\n]\\\\r\\\\n\\\"}\\r\\n\",\"timeContext\":{\"durationMs\":2592000000},\"queryType\":8}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"TimeRange\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"a9e0099e-5eb1-43b8-915c-587aa05bccf0\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"DateCompare\",\"type\":2,\"description\":\"Date to Comapre\",\"isRequired\":true,\"query\":\"let _configurationEnv = split(iff(isnull({EnvironmentList}) or isempty({EnvironmentList}) or tolower({EnvironmentList}) == \\\"all\\\",\\\"All\\\",tostring({EnvironmentList})),',');\\r\\nESIExchangeOnlineConfig_CL\\r\\n| extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n| where ScopedEnvironment in (_configurationEnv)\\r\\n| extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n| summarize Collection = max(Collection)\\r\\n| project Collection = \\\"lastdate\\\", Selected = true\\r\\n| join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | summarize by Collection \\r\\n | join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm ')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | join kind=leftouter (\\r\\n ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \\\"All\\\", \\\"All\\\",ESIEnvironment_s) \\r\\n | where ScopedEnvironment in (_configurationEnv)\\r\\n | where TimeGenerated > ago(90d)\\r\\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\\r\\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm')\\r\\n | summarize by PreciseCollection, Collection \\r\\n | summarize count() by Collection\\r\\n ) on Collection\\r\\n ) on Collection\\r\\n) on Collection\\r\\n| project Value = iif(Selected,Collection,iif(count_ > 1,PreciseCollection,Collection1)), Label = iif(Selected,\\\"Last Known date\\\",iif(count_ > 1,PreciseCollection,Collection1)), Selected\\r\\n| sort by Selected, Value desc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"parameters - 0\"},{\"type\":1,\"content\":{\"json\":\"This workbook helps review your Exchange Security configuration.\\r\\nAdjust the time range, and when needed select an item in the dropdownlist\",\"style\":\"info\"},\"name\":\"text - 9\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"34188faf-7a02-4697-9b36-2afa986afc0f\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Mailbox Access\",\"subTarget\":\"Delegation\",\"postText\":\"t\",\"style\":\"link\",\"icon\":\"3\",\"linkIsContextBlade\":true},{\"id\":\"be02c735-6150-4b6e-a386-b2b023e754e5\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"EXO & Azure AD Groups\",\"subTarget\":\"ExchAD\",\"style\":\"link\"},{\"id\":\"26c68d90-925b-4c3c-a837-e3cecd489b2d\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Transport Configuration\",\"subTarget\":\"Transport\",\"style\":\"link\"},{\"id\":\"eb2888ca-7fa6-4e82-88db-1bb3663a801e\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Workbook Help\",\"subTarget\":\"Start\",\"style\":\"link\"}]},\"name\":\"TopMenuTabs\"},{\"type\":1,\"content\":{\"json\":\"To compare collects, select **Yes** and choose the initial date.\\r\\nFor each role, a new table will be displayed with **all** the modifications (Add, Remove, Modifications) beetween the two dates.\\r\\n\\r\\n**Important notes** : Some information are limited are may be not 100% accurate :\\r\\n - Date\\r\\n - GUID of user instead of the name\\r\\n - Fusion of modifications when a role assisgnment is changed within the same collect \\r\\n - ... \\r\\n\\r\\nThis is due to some restrictions in the collect. For more details information, please check the workbook **\\\"Microsoft Exchange Search AdminAuditLog - Online\\\"**\\r\\n.\\r\\n\\r\\nThe compare functionnality is not available for all sections in this workbook.\\r\\n\"},\"name\":\"text - 9\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Workbook goals\\r\\n\\r\\nThe goal of this workbook is to outline key security configurations of your Exchange on-premises environment.\\r\\n\\r\\nMost of Exchange organizations have were installed years ago (sometimes more than 10 years). Many configurations have been done and might not have been documented. For most environments, the core commitment was maintaining a high availability of the users’ mailboxes putting aside other consideration (even security considerations). Recommended security practices have also evolved since the first released and a regular review is necessary.\\r\\n\\r\\nThis workbook is designed to show your Exchange organization is configured with a security point of view. Indeed, some configurations easy to display as there are no UI available.\\r\\n\\r\\nFor each configuration, you will find explanations and recommendations when applicable.\\r\\n\\r\\n- This workbook does not pretend to show you every weak Security configurations, but the most common issues and known to be used by attackers. \\r\\n- It will not show you if you have been comprised, but will help you identify unexpected configuration.\\r\\n\\r\\n----\\r\\n\\r\\n## Quick reminder of how Exchange works\\r\\n\\r\\nDuring Exchange installation two very important groups are created :\\r\\n- Exchange Trusted Subsystem : Contain all the computer accounts for Exchange Server\\r\\n- Exchange Windows Permissions : Contain the group Exchange trusted Subsystem\\r\\n\\r\\nThese groups have :\\r\\n- Very high privileges in ALL AD domains including the root domain\\r\\n- Right on any Exchange including mailboxes\\r\\n\\r\\nAs each Exchange server computer account is member of Exchange Trusted Subsystem, it means by taking control of the computer account or being System on an Exchange server you will gain access to all the permissions granted to Exchange Trusted Subsystem and Exchange Windows Permissions.\\r\\n\\r\\nTo protect AD and Exchange, it is very important to ensure the following:\\r\\n- There is a very limited number of persons that are local Administrator on Exchange server\\r\\n- To protect user right like : Act part of the operating System, Debug\\r\\n\\r\\nEvery service account or application that have high privileges on Exchange need to be considered as sensitive\\r\\n\\r\\n** 💡 Exchange servers need to be considered as very sensitive servers**\\r\\n\\r\\n-----\\r\\n\\r\\n\\r\\n## Tabs\\r\\n\\r\\n### Mailbox Access\\r\\n\\r\\nThis tab will show you several top sensitive delegations that allow an account to access, modify, act as another user, search, export the content of a mailbox.\\r\\n\\r\\n### Exchange & AD Groups\\r\\n\\r\\nThis tab will show you the members of Exchange groups and Sensitive AD groups.\\r\\n\\r\\n### Local Administrators\\r\\n\\r\\nThis tab will show you the non standard content of the local Administrators group. Remember that a member of the local Administrators group can take control of the computer account of the server and then it will have all the permissions associated with Exchange Trusted Subsytem and Exchange Windows Permissions\\r\\n\\r\\nThe information is displayed with different views : \\r\\n- List of nonstandard users\\r\\n- Number of servers with a nonstandard a user\\r\\n- Nonstandard groups content\\r\\n- For each user important information are displayed like last logon, last password set, enabled\\r\\n\\r\\n### Exchange Security configuration\\r\\n\\r\\nThis tab will show you some important configuration for your Exchange Organization\\r\\n- Status of Admin Audit Log configuration\\r\\n- Status of POP and IMAP configuration : especially, is Plaintext Authentication configured ?\\r\\n- Nonstandard permissions on the Exchange container in the Configuration Partition\\r\\n\\r\\n### Transport Configuration\\r\\n\\r\\nThis tab will show you the configuration of the main Transport components\\r\\n- Receive Connectors configured with Anonymous and/or Open Relay\\r\\n- Remote Domain Autoforward configuration\\r\\n- Transport Rules configured with BlindCopyTo, SendTo, RedirectTo\\r\\n- Journal Rule and Journal Recipient configurations\\r\\n- Accepted Domains with *\\r\\n\\r\\n\"},\"name\":\"WorkbookInfo\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Start\"},\"name\":\"InformationTab\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Display important security configurations that allow to access mailboxes' content. Direct delegations on mailboxes are not listed (Full Access permission mailboxes or direct delegations on mailboxes folders)\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Name !contains \\\"Deleg\\\" and CmdletResultValue.RoleAssigneeName != \\\"Hygiene Management\\\" and CmdletResultValue.RoleAssigneeName != \\\"Exchange Online-ApplicationAccount\\\" and CmdletResultValue.RoleAssigneeName != \\\"Discovery Management\\\" and CmdletResultValue.RoleAssigneeName !contains \\\"RIM-MailboxAdmins\\\"\\r\\n| where CmdletResultValue.Role contains \\\"Export\\\" or CmdletResultValue.Role contains \\\"Impersonation\\\" or CmdletResultValue.Role contains \\\"Search\\\"\\r\\n| summarize dcount(tostring(CmdletResultValue.RoleAssigneeName)) by role=tostring(CmdletResultValue.Role)\",\"size\":3,\"title\":\"Number of accounts with sensitive RBAC roles\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"role\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"dcount_CmdletResultValue_RoleAssigneeName\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2,\"maximumSignificantDigits\":3}}},\"showBorder\":true,\"sortCriteriaField\":\"role\",\"sortOrderField\":1}},\"name\":\"MRAQuery\"},{\"type\":1,\"content\":{\"json\":\"**ApplicationImpersonation** is a RBAC role that allows access (read and modify) to the content of all mailboxes. This role is very powerfull and should be carefully delegated. When a delegation is necessary, RBAC scopes should be configured to limit the list of impacted mailboxes.\\r\\n\\r\\nIt is common to see service accounts for backup solution, antivirus software, MDM...\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"SensitiveRBACHelp\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Application Impersonation Role\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This delegation allows the delegated account to access and modify the content of every mailboxes using EWS.\\r\\nExcluded from the result as it is a default configuration :\\r\\nDelegating delegation to Organization Management\"},\"name\":\"text - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Role contains \\\"Impersonation\\\" and CmdletResultValue.RoleAssigneeName != \\\"Hygiene Management\\\" and CmdletResultValue.RoleAssigneeName !contains \\\"RIM-MailboxAdmins\\\" and CmdletResultValue.Name !contains \\\"Deleg\\\"\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend RoleAssigneeType = iff(CmdletResultValue.RoleAssigneeType== \\\"User\\\" , \\\"User\\\", \\\"RoleGroup\\\")\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n| extend ManagementRoleAssignement = tostring(CmdletResultValue.Name)\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| extend RoleAssigneeName = iff( RoleAssigneeType == \\\"User\\\", strcat(\\\"🧑‍🦰 \\\",RoleAssigneeName), strcat(\\\"👪 \\\", RoleAssigneeName) )\\r\\n| project RoleAssigneeName, RoleAssigneeType, Status,CustomRecipientWriteScope,CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope,ManagementRoleAssignement,WhenChanged,WhenCreated\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true}},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExclusionsAcctValue = dynamic([\\\"Hygiene Management\\\", \\\"RIM-MailboxAdmins\\\"]);\\r\\nMESCompareDataMRA(SectionCompare=\\\"MRA\\\",DateCompare=\\\"{DateCompare:value}\\\",CurrentDate = \\\"{DateOfConfiguration:value}\\\",EnvList ={EnvironmentList},TypeEnv = \\\"Online\\\",ExclusionsAcct = ExclusionsAcctValue ,CurrentRole=\\\"Impersonation\\\")\",\"size\":3,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"ManagementRoleAssignement\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 2\"}]},\"name\":\"Application Impersonation Role\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Mailbox Import Export Role\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This delegation allows to import contents in all mailboxes.\\r\\nExcluded from the result as it is a default configuration :\\r\\nDelegating delegation to Organization Management\\r\\n\"},\"name\":\"text - 0\"},{\"type\":1,\"content\":{\"json\":\"**Mailbox Import Export** is an RBAC role that allows an account to import (export is not available online) contant in a user mailbox. It also allows searches in all mailboxes.\\r\\n\\r\\n⚡ This role is very powerfull.\\r\\n\\r\\nBy default, this role is not delegated to any user or group. The members of the group Organization Management by default do not have this role but are able to delegate it.\\r\\n\\r\\nℹ️ Recommendations\\r\\n\\r\\nIf you temporarily need this delegation, consider the following:\\r\\n- create an empty group with this delegation\\r\\n- monitor the group content and alert when the group modified\\r\\n- add administrators in this group only for a short period of time\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"SearchRBACHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Role contains \\\"export\\\" and CmdletResultValue.Name !contains \\\"Deleg\\\"\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend RoleAssigneeType = iff(CmdletResultValue.RoleAssigneeType== \\\"User\\\" , \\\"User\\\", \\\"RoleGroup\\\")\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n| extend ManagementRoleAssignement = tostring(CmdletResultValue.Name)\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| extend RoleAssigneeName = iff( RoleAssigneeType == \\\"User\\\", strcat(\\\"🧑‍🦰 \\\",RoleAssigneeName), strcat(\\\"👪 \\\", RoleAssigneeName) )\\r\\n| project RoleAssigneeName, RoleAssigneeType, Status,CustomRecipientWriteScope,CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope,ManagementRoleAssignement,WhenChanged,WhenCreated\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"MESCompareDataMRA(SectionCompare=\\\"MRA\\\",DateCompare=\\\"{DateCompare:value}\\\",CurrentDate = \\\"{DateOfConfiguration:value}\\\",EnvList ={EnvironmentList},TypeEnv = \\\"Online\\\",ExclusionsAcct = \\\"N/A\\\",CurrentRole=\\\"export\\\")\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"ManagementRoleAssignement\"],\"expandTopLevel\":true},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 1 - Copy\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Mailbox Import Export Role\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Mailbox Search Role\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This delegation allows to search inside all or in a scope of mailboxes.\\r\\nExcluded from the result as it is a default configuration :\\r\\nDelegating delegation to Organization Management\\r\\nDiscovery Management has been excluded\\r\\n\"},\"name\":\"text - 0\"},{\"type\":1,\"content\":{\"json\":\"**Mailbox Search** is an RBAC role that allows an account to search in any mailbox.\\r\\n\\r\\n⚡ This role is very powerfull.\\r\\n\\r\\nBy default, this role is only delegated to the group Discovery Management. The members of the group Organization Management do not have this role but are able to delegate it.\\r\\n\\r\\nℹ️ Recommendations\\r\\n\\r\\nIf you temporarily need this delegation, consider the following:\\r\\n\\r\\n- add the administrators in the Discovery Management group\\r\\n- monitor the group content and alert when the group modified\\r\\n- add administrators in this group only for a short period of time\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"SearchRBACHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"MRA\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| where CmdletResultValue.Role contains \\\"search\\\" and CmdletResultValue.Name !contains \\\"Deleg\\\"\\r\\n| where CmdletResultValue.RoleAssigneeName != \\\"Exchange Online-ApplicationAccount\\\" and CmdletResultValue.RoleAssigneeName != \\\"Discovery Management\\\"\\r\\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\\r\\n| extend RoleAssigneeType = iff(CmdletResultValue.RoleAssigneeType== \\\"User\\\" , \\\"User\\\", \\\"Group\\\")\\r\\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\\r\\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\\r\\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\\r\\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\\r\\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\\r\\n| extend ManagementRoleAssignement = tostring(CmdletResultValue.Name)\\r\\n| extend Status= tostring(CmdletResultValue.Enabled)\\r\\n| extend RoleAssigneeName = iff( RoleAssigneeType == \\\"User\\\", strcat(\\\"🧑‍🦰 \\\",RoleAssigneeName), strcat(\\\"👪 \\\", RoleAssigneeName) )\\r\\n| project RoleAssigneeName, RoleAssigneeType, Status,CustomRecipientWriteScope,CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope,ManagementRoleAssignement,WhenChanged,WhenCreated\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"MESCompareDataMRA(SectionCompare=\\\"MRA\\\",DateCompare=\\\"{DateCompare:value}\\\",CurrentDate = \\\"{DateOfConfiguration:value}\\\",EnvList ={EnvironmentList},TypeEnv = \\\"Online\\\",ExclusionsAcct = \\\"N/A\\\",CurrentRole=\\\"Search\\\")\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"ManagementRoleAssignement\"],\"expandTopLevel\":true},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"sortBy\":[{\"itemKey\":\"ConfigWriteScope\",\"sortOrder\":1}]},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 1 - Copy\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Mailbox Search Role\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Delegation\"},\"name\":\"Importantsecurityconfiguration\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Exchange Group\",\"items\":[{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"ℹ️ Recommendations\\r\\n\\r\\n- Ensure that no service account are a member of the high privilege groups. Use RBAC to delegate the exact required permissions.\\r\\n- Limit the usage of nested group for administration.\\r\\n- Ensure that accounts are given only the required pernissions to execute their tasks.\\r\\n- Use just in time administration principle by adding users in a group only when they need the permissions, then remove them when their operation is over.\\r\\n- Limit the number of Organization management members. When you review the Admin Audit logs you might see that the administrators rarely needed Organization Management privileges.\\r\\n- Monitor the content of the following groups:\\r\\n - TenantAdmins_-xxx (Membership in this role group is synchronized across services and managed centrally)\\r\\n - Organization Management\\r\\n - ExchangeServiceAdmins_-xxx (Membership in this role group is synchronized across services and managed centrally)\\r\\n - Recipient Management (Member of this group have at least the following rights : set-mailbox, Add-MailboxPermission)\\r\\n - Discovery Management\\r\\n - Hygiene Management\\r\\n - Security Administrator (Membership in this role group is synchronized across services and managed centrally)\\r\\n - xxx High privilege group (not an exhaustive list)\\r\\n - Compliance Management\\r\\n - All RBAC groups that have high roles delegation\\r\\n - All nested groups in high privileges groups\\r\\n - Note that this is not a complete list. The content of all the groups that have high privileges should be monitored.\\r\\n- Each time a new RBAC group is created, decide if the content of this groups should be monitored\\r\\n- Periodically review the members of the groups\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"text - 0\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\" Number of direct members per group with RecipientType User\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"RoleGroupMember\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n//| where CmdletResultValue.RecipientType !contains \\\"group\\\"\\r\\n| extend Members= tostring(CmdletResultValue.Identity)\\r\\n| summarize dcount(tostring(Members)) by RoleGroup = tostring(CmdletResultValue.RoleGroup)\\r\\n| where RoleGroup has_any (\\\"TenantAdmins\\\",\\\"Organization Management\\\", \\\"Discovery Management\\\", \\\"Compliance Management\\\", \\\"Server Management\\\", \\\"ExchangeServiceAdmins\\\",\\\"Security Administrator\\\", \\\"SecurityAdmins\\\", \\\"Recipient Manangement\\\", \\\"Records Manangement\\\",\\\"Impersonation\\\",\\\"Export\\\")\\r\\n| sort by dcount_Members\\r\\n\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"RoleGroup\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"dcount_Members\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":true,\"sortCriteriaField\":\"dcount_Members\",\"sortOrderField\":2,\"size\":\"auto\"}},\"name\":\"query - 0\"}]},\"name\":\"ExchangeGroupsList\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Number of direct members per group with RecipientType User\",\"expandable\":true,\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"RoleGroupMember\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n| where CmdletResultValue.RecipientType !contains \\\"group\\\"\\r\\n| extend Members= tostring(CmdletResultValue.Identity)\\r\\n| summarize dcount(tostring(Members)) by RoleGroup = tostring(CmdletResultValue.RoleGroup)\\r\\n| sort by dcount_Members\\r\\n\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"RoleGroup\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"dcount_Members\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":true,\"sortCriteriaField\":\"dcount_Members\",\"sortOrderField\":2,\"size\":\"auto\"}},\"name\":\"query - 0\"}]},\"name\":\"ExchangeGroupsList - Copy\"},{\"type\":1,\"content\":{\"json\":\"Exchange Online groups content.\\r\\nSelect a group to display detailed information of its contents.\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"b4b7a6ad-381a-48d6-9938-bf7cb812b474\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Group\",\"type\":2,\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"RoleGroup\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n//| where CmdletResultValue.Parentgroup != \\\"Exchange Trusted Subsystem\\\"\\r\\n//| where CmdletResultValue.Parentgroup != \\\"Exchange Windows Permissions\\\"\\r\\n| project CmdletResultValue\\r\\n| extend GroupName = tostring(CmdletResultValue.Name)\\r\\n| distinct GroupName\\r\\n| sort by GroupName asc\\r\\n\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"//ExchangeConfiguration(SpecificSectionList=\\\"ExGroup\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\nExchangeConfiguration(SpecificSectionList=\\\"RoleGroupMember\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| search CmdletResultValue.RoleGroup == \\\"{Group}\\\"\\r\\n//| where CmdletResultValue.Level != 0\\r\\n| project CmdletResultValue\\r\\n| extend Members = tostring(CmdletResultValue.Identity)\\r\\n//| extend Parentgroup = tostring(CmdletResultValue.Parentgroup)\\r\\n//| extend MemberPath = tostring(CmdletResultValue.MemberPath)\\r\\n//| extend Level = tostring(CmdletResultValue.Level)\\r\\n//| extend ObjectClass = tostring(CmdletResultValue.ObjectClass)\\r\\n//| extend LastLogon = CmdletResultValue.LastLogonString\\r\\n//| extend LastLogon = iif ( todatetime (CmdletResultValue.LastLogonString) < ago(-366d), CmdletResultValue.LastLogonString,strcat(\\\"💥\\\",CmdletResultValue.LastLogonString))\\r\\n//| extend LastPwdSet = CmdletResultValue.LastPwdSetString\\r\\n//| extend Enabled = tostring(CmdletResultValue.Enabled)\\r\\n| extend Members = case( CmdletResultValue.RecipientType == \\\"Group\\\", strcat( \\\"👪 \\\", Members), strcat( \\\"🧑‍🦰 \\\", Members) )\\r\\n| extend RecipientType = tostring(CmdletResultValue.RecipientType)\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"CmdletResultValue\",\"formatter\":5}],\"rowLimit\":10000,\"filter\":true}},\"name\":\"ExchangeServersGroupsGrid\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Exchange group\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"ExchAD\"},\"name\":\"Exchange and AD GRoup\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Transport Security configuration\",\"items\":[{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Inbound Connector configuration\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section shows the configuration of the Inbound connnectors\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"TransportRulesHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"InBoundC\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n| extend Name = tostring(CmdletResultValue.Name)\\r\\n| extend State = tostring(CmdletResultValue.Enabled)\\r\\n| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n| extend SenderIPAddresses = tostring(CmdletResultValue.SenderIPAddresses)\\r\\n| extend SenderDomains = tostring(CmdletResultValue.SenderDomains)\\r\\n| extend TrustedOrganizations = tostring(CmdletResultValue.TrustedOrganizations)\\r\\n| extend AssociatedAcceptedDomainsRequireTls = tostring(CmdletResultValue.AssociatedAcceptedDomainsRequireTls)\\r\\n| extend RestrictDomainsToIPAddresses = tostring(CmdletResultValue.RestrictDomainsToIPAddresses)\\r\\n| extend RestrictDomainsToCertificate = tostring(CmdletResultValue.RestrictDomainsToCertificate)\\r\\n| extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n| extend TreatMessagesAsInternal = tostring(CmdletResultValue.TreatMessagesAsInternal)\\r\\n| extend TlsSenderCertificateName = tostring(CmdletResultValue.TlsSenderCertificateName)\\r\\n| extend ScanAndDropRecipients = tostring(CmdletResultValue.ScanAndDropRecipients)\\r\\n| extend Comment = tostring(CmdletResultValue.Comment)\\r\\n| extend WhenChanged = tostring(CmdletResultValue.WhenChanged)\\r\\n| extend WhenCreated = tostring(CmdletResultValue.WhenCreated)\\r\\n| project-away CmdletResultValue\\r\\n| sort by Name asc\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let _EnvList ={EnvironmentList};\\r\\nlet _TypeEnv = \\\"Online\\\";\\r\\nlet _DateCompare = \\\"{DateCompare:value}\\\";\\r\\nlet _CurrentDate = \\\"{DateOfConfiguration:value}\\\";\\r\\nlet _DateCompareB = todatetime(_DateCompare);\\r\\nlet _currD = (ExchangeConfiguration(SpecificSectionList=\\\"InBoundC\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n| summarize TimeMax = arg_max(TimeGenerated,*)\\r\\n| extend TimeMax = tostring(split(TimeMax,\\\"T\\\")[0])\\r\\n| project TimeMax);\\r\\nlet _CurrentDateB = todatetime(toscalar(_currD));\\r\\nlet BeforeData = ExchangeConfiguration(SpecificSectionList=\\\"InBoundC\\\",SpecificConfigurationDate=_DateCompareB,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend SenderIPAddresses = tostring(CmdletResultValue.SenderIPAddresses)\\r\\n\\t| extend SenderDomains = tostring(CmdletResultValue.SenderDomains)\\r\\n\\t| extend TrustedOrganizations = tostring(CmdletResultValue.TrustedOrganizations)\\r\\n\\t| extend AssociatedAcceptedDomainsRequireTls = tostring(CmdletResultValue.AssociatedAcceptedDomainsRequireTls)\\r\\n\\t| extend RestrictDomainsToIPAddresses = tostring(CmdletResultValue.RestrictDomainsToIPAddresses)\\r\\n\\t| extend RestrictDomainsToCertificate = tostring(CmdletResultValue.RestrictDomainsToCertificate)\\r\\n\\t| extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n\\t| extend TreatMessagesAsInternal = tostring(CmdletResultValue.TreatMessagesAsInternal)\\r\\n\\t| extend TlsSenderCertificateName = tostring(CmdletResultValue.TlsSenderCertificateName)\\r\\n\\t| extend ScanAndDropRecipients = tostring(CmdletResultValue.ScanAndDropRecipients)\\r\\n\\t| extend Comment = tostring(CmdletResultValue.Comment)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet AfterData = \\r\\n ExchangeConfiguration(SpecificSectionList=\\\"InBoundC\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n \\t| extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend SenderIPAddresses = tostring(CmdletResultValue.SenderIPAddresses)\\r\\n\\t| extend SenderDomains = tostring(CmdletResultValue.SenderDomains)\\r\\n\\t| extend TrustedOrganizations = tostring(CmdletResultValue.TrustedOrganizations)\\r\\n\\t| extend AssociatedAcceptedDomainsRequireTls = tostring(CmdletResultValue.AssociatedAcceptedDomainsRequireTls)\\r\\n\\t| extend RestrictDomainsToIPAddresses = tostring(CmdletResultValue.RestrictDomainsToIPAddresses)\\r\\n\\t| extend RestrictDomainsToCertificate = tostring(CmdletResultValue.RestrictDomainsToCertificate)\\r\\n\\t| extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n\\t| extend TreatMessagesAsInternal = tostring(CmdletResultValue.TreatMessagesAsInternal)\\r\\n\\t| extend TlsSenderCertificateName = tostring(CmdletResultValue.TlsSenderCertificateName)\\r\\n\\t| extend ScanAndDropRecipients = tostring(CmdletResultValue.ScanAndDropRecipients)\\r\\n\\t| extend Comment = tostring(CmdletResultValue.Comment)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet i=0;\\r\\nlet allDataRange = \\r\\n ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"InBoundC\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | project TimeGenerated,CmdletResultValue,WhenChanged = todatetime(WhenChanged_t), WhenCreated=todatetime(WhenCreated_t)\\r\\n \\t| extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend SenderIPAddresses = tostring(CmdletResultValue.SenderIPAddresses)\\r\\n\\t| extend SenderDomains = tostring(CmdletResultValue.SenderDomains)\\r\\n\\t| extend TrustedOrganizations = tostring(CmdletResultValue.TrustedOrganizations)\\r\\n\\t| extend AssociatedAcceptedDomainsRequireTls = tostring(CmdletResultValue.AssociatedAcceptedDomainsRequireTls)\\r\\n\\t| extend RestrictDomainsToIPAddresses = tostring(CmdletResultValue.RestrictDomainsToIPAddresses)\\r\\n\\t| extend RestrictDomainsToCertificate = tostring(CmdletResultValue.RestrictDomainsToCertificate)\\r\\n\\t| extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n\\t| extend TreatMessagesAsInternal = tostring(CmdletResultValue.TreatMessagesAsInternal)\\r\\n\\t| extend TlsSenderCertificateName = tostring(CmdletResultValue.TlsSenderCertificateName)\\r\\n\\t| extend ScanAndDropRecipients = tostring(CmdletResultValue.ScanAndDropRecipients)\\r\\n\\t| extend Comment = tostring(CmdletResultValue.Comment)\\r\\n ;\\r\\nlet DiffAddDataP1 = allDataRange\\r\\n | join kind = rightanti (AfterData | where WhenCreated >=_DateCompareB) on WhenCreated\\r\\n;\\r\\nlet DiffAddDataP2 = allDataRange\\r\\n | join kind = innerunique (allDataRange ) on WhenCreated\\r\\n | where WhenCreated >=_DateCompareB\\r\\n | where bin(WhenCreated,5m)==bin(WhenChanged,5m)\\r\\n | distinct Identity,State,ConnectorType,ConnectorSource,SenderIPAddresses,SenderDomains,TrustedOrganizations,AssociatedAcceptedDomainsRequireTls,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,CloudServicesMailEnabled,TreatMessagesAsInternal,TlsSenderCertificateName,ScanAndDropRecipients,Comment,WhenChanged,WhenCreated\\r\\n ;\\r\\nlet DiffAddData = union DiffAddDataP1,DiffAddDataP2\\r\\n| extend Actiontype =\\\"Add\\\";\\r\\nlet DiffRemoveData = allDataRange\\r\\n | join kind = leftanti AfterData on Identity\\r\\n | extend Actiontype =\\\"Remove\\\"\\r\\n | distinct Actiontype ,Identity,State,ConnectorType,ConnectorSource,SenderIPAddresses,SenderDomains,TrustedOrganizations,AssociatedAcceptedDomainsRequireTls,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,CloudServicesMailEnabled,TreatMessagesAsInternal,TlsSenderCertificateName,ScanAndDropRecipients,Comment,WhenCreated\\r\\n | project WhenChanged=_CurrentDateB,Actiontype,Identity,State,ConnectorType,ConnectorSource,SenderIPAddresses,SenderDomains,TrustedOrganizations,AssociatedAcceptedDomainsRequireTls,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,CloudServicesMailEnabled,TreatMessagesAsInternal,TlsSenderCertificateName,ScanAndDropRecipients,Comment,WhenCreated\\r\\n ;\\r\\nlet DiffModifData = union AfterData,allDataRange\\r\\n| sort by Identity,WhenChanged asc\\r\\n| project WhenChanged,Identity,State,ConnectorType,ConnectorSource,SenderIPAddresses,SenderDomains,TrustedOrganizations,AssociatedAcceptedDomainsRequireTls,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,CloudServicesMailEnabled,TreatMessagesAsInternal,TlsSenderCertificateName,ScanAndDropRecipients,Comment,WhenCreated\\r\\n| extend Identity = iff( Identity == prev(Identity) and Identity != prev(Identity) and prev(Identity) !=\\\"\\\" , strcat(\\\"📍 \\\", Identity, \\\" (\\\",prev(Identity),\\\"->\\\", Identity,\\\" )\\\"),Identity)\\r\\n| extend State = iff( Identity == prev(Identity) and State != prev(State) and prev(State) !=\\\"\\\" , strcat(\\\"📍 \\\", State, \\\" (\\\",prev(State),\\\"->\\\", State,\\\" )\\\"),State)\\r\\n| extend ConnectorType = iff( Identity == prev(Identity) and ConnectorType != prev(ConnectorType) and prev(ConnectorType) !=\\\"\\\" , strcat(\\\"📍 \\\", ConnectorType, \\\" (\\\",prev(ConnectorType),\\\"->\\\", ConnectorType,\\\" )\\\"),ConnectorType)\\r\\n| extend ConnectorSource = iff( Identity == prev(Identity) and ConnectorSource != prev(ConnectorSource) and prev(ConnectorSource) !=\\\"\\\" , strcat(\\\"📍 \\\", ConnectorSource, \\\" (\\\",prev(ConnectorSource),\\\"->\\\", ConnectorSource,\\\" )\\\"),ConnectorSource)\\r\\n| extend SenderIPAddresses = iff( Identity == prev(Identity) and SenderIPAddresses != prev(SenderIPAddresses) and prev(SenderIPAddresses) !=\\\"\\\" , strcat(\\\"📍 \\\", SenderIPAddresses, \\\" (\\\",prev(SenderIPAddresses),\\\"->\\\", SenderIPAddresses,\\\" )\\\"),SenderIPAddresses)\\r\\n| extend SenderDomains = iff( Identity == prev(Identity) and SenderDomains != prev(SenderDomains) and prev(SenderDomains) !=\\\"\\\" , strcat(\\\"📍 \\\", SenderDomains, \\\" (\\\",prev(SenderDomains),\\\"->\\\", SenderDomains,\\\" )\\\"),SenderDomains)\\r\\n| extend TrustedOrganizations = iff( Identity == prev(Identity) and TrustedOrganizations != prev(TrustedOrganizations) and prev(TrustedOrganizations) !=\\\"\\\" , strcat(\\\"📍 \\\", TrustedOrganizations, \\\" (\\\",prev(TrustedOrganizations),\\\"->\\\", TrustedOrganizations,\\\" )\\\"),TrustedOrganizations)\\r\\n| extend AssociatedAcceptedDomainsRequireTls = iff (Identity == prev(Identity) and AssociatedAcceptedDomainsRequireTls != prev(AssociatedAcceptedDomainsRequireTls) and prev(AssociatedAcceptedDomainsRequireTls) !=\\\"\\\" , strcat(\\\"📍 \\\", AssociatedAcceptedDomainsRequireTls, \\\" (\\\",prev(AssociatedAcceptedDomainsRequireTls),\\\"->\\\", AssociatedAcceptedDomainsRequireTls,\\\" )\\\"),AssociatedAcceptedDomainsRequireTls)\\r\\n| extend RestrictDomainsToIPAddresses = iff(Identity == prev(Identity) and RestrictDomainsToIPAddresses != prev(RestrictDomainsToIPAddresses) and prev(RestrictDomainsToIPAddresses) !=\\\"\\\" , strcat(\\\"📍 \\\", RestrictDomainsToIPAddresses, \\\" (\\\",prev(RestrictDomainsToIPAddresses),\\\"->\\\", RestrictDomainsToIPAddresses,\\\" )\\\"),RestrictDomainsToIPAddresses)\\r\\n| extend RestrictDomainsToCertificate = iff( Identity == prev(Identity) and RestrictDomainsToCertificate != prev(RestrictDomainsToCertificate) and prev(RestrictDomainsToCertificate) !=\\\"\\\" , strcat(\\\"📍 \\\", RestrictDomainsToCertificate, \\\" (\\\",prev(RestrictDomainsToCertificate),\\\"->\\\", RestrictDomainsToCertificate,\\\" )\\\"),RestrictDomainsToCertificate)\\r\\n| extend CloudServicesMailEnabled = iff( Identity == prev(Identity) and CloudServicesMailEnabled != prev(CloudServicesMailEnabled) and prev(CloudServicesMailEnabled) !=\\\"\\\" , strcat(\\\"📍 \\\", CloudServicesMailEnabled, \\\" (\\\",prev(CloudServicesMailEnabled),\\\"->\\\", CloudServicesMailEnabled,\\\" )\\\"),CloudServicesMailEnabled)\\r\\n| extend TreatMessagesAsInternal = iff( Identity == prev(Identity) and TreatMessagesAsInternal != prev(TreatMessagesAsInternal) and prev(TreatMessagesAsInternal) !=\\\"\\\" , strcat(\\\"📍 \\\", TreatMessagesAsInternal, \\\" (\\\",prev(TreatMessagesAsInternal),\\\"->\\\", TreatMessagesAsInternal,\\\" )\\\"),TreatMessagesAsInternal)\\r\\n| extend TlsSenderCertificateName = iff(Identity == prev(Identity) and TlsSenderCertificateName != prev(TlsSenderCertificateName) and prev(TlsSenderCertificateName) !=\\\"\\\" , strcat(\\\"📍 \\\", TlsSenderCertificateName, \\\" (\\\",prev(TlsSenderCertificateName),\\\"->\\\", TlsSenderCertificateName,\\\" )\\\"),TlsSenderCertificateName)\\r\\n| extend ScanAndDropRecipients = iff( Identity == prev(Identity) and ScanAndDropRecipients != prev(ScanAndDropRecipients) and prev(ScanAndDropRecipients) !=\\\"\\\" , strcat(\\\"📍 \\\", ScanAndDropRecipients, \\\" (\\\",prev(ScanAndDropRecipients),\\\"->\\\", ScanAndDropRecipients,\\\" )\\\"),ScanAndDropRecipients)\\r\\n| extend Comment = iff( Identity == prev(Identity) and Comment != prev(Comment) and prev(Comment) !=\\\"\\\" , strcat(\\\"📍 \\\", Comment, \\\" (\\\",prev(Comment),\\\"->\\\", Comment,\\\" )\\\"),Comment)\\r\\n| extend ActiontypeR =iff((Identity contains \\\"📍\\\" or State contains \\\"📍\\\" or ConnectorType contains \\\"📍\\\" or ConnectorSource contains \\\"📍\\\" or SenderIPAddresses contains \\\"📍\\\" or SenderDomains contains \\\"📍\\\" or TrustedOrganizations contains \\\"📍\\\" or AssociatedAcceptedDomainsRequireTls contains \\\"📍\\\" or RestrictDomainsToIPAddresses contains \\\"📍\\\" or RestrictDomainsToCertificate contains \\\"📍\\\" or CloudServicesMailEnabled contains \\\"📍\\\" or TreatMessagesAsInternal contains \\\"📍\\\" or TlsSenderCertificateName contains \\\"📍\\\" or ScanAndDropRecipients contains \\\"📍\\\" or Comment contains \\\"📍\\\" ), i=i + 1, i)\\r\\n| extend Actiontype =iff(ActiontypeR > 0, \\\"Modif\\\", \\\"NO\\\")\\r\\n| where ActiontypeR == 1\\r\\n| project WhenChanged,Actiontype,Identity,State,ConnectorType,ConnectorSource,SenderIPAddresses,SenderDomains,TrustedOrganizations,AssociatedAcceptedDomainsRequireTls,RestrictDomainsToIPAddresses,RestrictDomainsToCertificate,CloudServicesMailEnabled,TreatMessagesAsInternal,TlsSenderCertificateName,ScanAndDropRecipients,Comment,WhenCreated\\r\\n;\\r\\nunion DiffAddData, DiffRemoveData, DiffModifData\\r\\n| extend WhenChanged = iff (Actiontype == \\\"Modif\\\", WhenChanged, iff(Actiontype == \\\"Add\\\",WhenCreated, WhenChanged))\\r\\n| extend Actiontype = case(Actiontype == \\\"Add\\\", strcat(\\\"➕ \\\", Actiontype), Actiontype == \\\"Remove\\\", strcat(\\\"➖ \\\", Actiontype), Actiontype == \\\"Modif\\\", strcat(\\\"📍 \\\", Actiontype), \\\"N/A\\\")\\r\\n| sort by WhenChanged desc \\r\\n| project\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n State,\\r\\n ConnectorType,\\r\\n ConnectorSource,\\r\\n Comment,\\r\\n SenderIPAddresses,\\r\\n SenderDomains,\\r\\n TrustedOrganizations,\\r\\n AssociatedAcceptedDomainsRequireTls,\\r\\n RestrictDomainsToIPAddresses,\\r\\n RestrictDomainsToCertificate,\\r\\n CloudServicesMailEnabled,\\r\\n TreatMessagesAsInternal,\\r\\n TlsSenderCertificateName,\\r\\n ScanAndDropRecipients,\\r\\n WhenCreated\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"Identity\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 2\"}]},\"name\":\"Inbound Connector configuration\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Outbound Connector configuration\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This section shows the configuration of the Outbound connnectors\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"TransportRulesHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"OutBoundC\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n| extend Name = tostring(CmdletResultValue.Name)\\r\\n| extend State = tostring(CmdletResultValue.Enabled)\\r\\n| extend UseMXRecord = tostring(CmdletResultValue.UseMXRecord)\\r\\n| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n| extend RecipientDomains = tostring(CmdletResultValue.RecipientDomains)\\r\\n| extend SmartHosts = tostring(CmdletResultValue.SmartHosts)\\r\\n| extend TlsDomain = tostring(CmdletResultValue.TlsDomain)\\r\\n| extend TlsSettings = tostring(CmdletResultValue.TlsSettings)\\r\\n| extend IsTransportRuleScoped = tostring(CmdletResultValue.IsTransportRuleScoped)\\r\\n| extend RouteAllMessagesViaOnPremises = tostring(CmdletResultValue.RouteAllMessagesViaOnPremises)\\r\\n| extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n| extend AllAcceptedDomains = tostring(CmdletResultValue.AllAcceptedDomains)\\r\\n| extend SenderRewritingEnabled = tostring(CmdletResultValue.SenderRewritingEnabled)\\r\\n| extend TestMode = tostring(CmdletResultValue.TestMode)\\r\\n| extend LinkForModifiedConnector = tostring(CmdletResultValue.LinkForModifiedConnector)\\r\\n| extend ValidationRecipients = tostring(CmdletResultValue.ValidationRecipients)\\r\\n| extend IsValidated = tostring(CmdletResultValue.IsValidated)\\r\\n| extend LastValidationTimestamp = tostring(CmdletResultValue.LastValidationTimestamp)\\r\\n| extend Comment = tostring(CmdletResultValue.Comment)\\r\\n| extend WhenChanged = tostring(CmdletResultValue.WhenChanged)\\r\\n| extend WhenCreated = tostring(CmdletResultValue.WhenCreated)\\r\\n| project-away CmdletResultValue\\r\\n| sort by Name asc\",\"size\":3,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Outbound Connector configuration - Copy\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let _EnvList ={EnvironmentList};\\r\\nlet _TypeEnv = \\\"Online\\\";\\r\\nlet _DateCompare = \\\"{DateCompare:value}\\\";\\r\\nlet _CurrentDate = \\\"{DateOfConfiguration:value}\\\";\\r\\nlet _DateCompareB = todatetime(_DateCompare);\\r\\nlet _currD = (ExchangeConfiguration(SpecificSectionList=\\\"OutBoundC\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n| summarize TimeMax = arg_max(TimeGenerated,*)\\r\\n| extend TimeMax = tostring(split(TimeMax,\\\"T\\\")[0])\\r\\n| project TimeMax);\\r\\nlet _CurrentDateB = todatetime(toscalar(_currD));\\r\\nlet BeforeData = ExchangeConfiguration(SpecificSectionList=\\\"OutBoundC\\\",SpecificConfigurationDate=_DateCompareB,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n | extend UseMXRecord = tostring(CmdletResultValue.UseMXRecord)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend RecipientDomains = tostring(CmdletResultValue.RecipientDomains)\\r\\n | extend SmartHosts = tostring(CmdletResultValue.SmartHosts)\\r\\n | extend TlsDomain = tostring(CmdletResultValue.TlsDomain)\\r\\n | extend TlsSettings = tostring(CmdletResultValue.TlsSettings)\\r\\n | extend IsTransportRuleScoped = tostring(CmdletResultValue.IsTransportRuleScoped)\\r\\n | extend RouteAllMessagesViaOnPremises = tostring(CmdletResultValue.RouteAllMessagesViaOnPremises)\\r\\n | extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n | extend AllAcceptedDomains = tostring(CmdletResultValue.AllAcceptedDomains)\\r\\n | extend SenderRewritingEnabled = tostring(CmdletResultValue.SenderRewritingEnabled)\\r\\n | extend TestMode = tostring(CmdletResultValue.TestMode)\\r\\n | extend LinkForModifiedConnector = tostring(CmdletResultValue.LinkForModifiedConnector)\\r\\n | extend ValidationRecipients = tostring(CmdletResultValue.ValidationRecipients)\\r\\n | extend IsValidated = tostring(CmdletResultValue.IsValidated)\\r\\n | extend LastValidationTimestamp = tostring(CmdletResultValue.LastValidationTimestamp)\\r\\n | extend Comment = tostring(CmdletResultValue.Comment)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet AfterData = \\r\\n ExchangeConfiguration(SpecificSectionList=\\\"OutBoundC\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n | extend UseMXRecord = tostring(CmdletResultValue.UseMXRecord)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend RecipientDomains = tostring(CmdletResultValue.RecipientDomains)\\r\\n | extend SmartHosts = tostring(CmdletResultValue.SmartHosts)\\r\\n | extend TlsDomain = tostring(CmdletResultValue.TlsDomain)\\r\\n | extend TlsSettings = tostring(CmdletResultValue.TlsSettings)\\r\\n | extend IsTransportRuleScoped = tostring(CmdletResultValue.IsTransportRuleScoped)\\r\\n | extend RouteAllMessagesViaOnPremises = tostring(CmdletResultValue.RouteAllMessagesViaOnPremises)\\r\\n | extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n | extend AllAcceptedDomains = tostring(CmdletResultValue.AllAcceptedDomains)\\r\\n | extend SenderRewritingEnabled = tostring(CmdletResultValue.SenderRewritingEnabled)\\r\\n | extend TestMode = tostring(CmdletResultValue.TestMode)\\r\\n | extend LinkForModifiedConnector = tostring(CmdletResultValue.LinkForModifiedConnector)\\r\\n | extend ValidationRecipients = tostring(CmdletResultValue.ValidationRecipients)\\r\\n | extend IsValidated = tostring(CmdletResultValue.IsValidated)\\r\\n | extend LastValidationTimestamp = tostring(CmdletResultValue.LastValidationTimestamp)\\r\\n | extend Comment = tostring(CmdletResultValue.Comment)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet i=0;\\r\\nlet allDataRange = \\r\\n ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"OutBoundC\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | project TimeGenerated,CmdletResultValue,WhenChanged = todatetime(WhenChanged_t), WhenCreated=todatetime(WhenCreated_t)\\r\\n \\t| extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend State = tostring(CmdletResultValue.Enabled)\\r\\n | extend UseMXRecord = tostring(CmdletResultValue.UseMXRecord)\\r\\n\\t| extend ConnectorType = tostring(CmdletResultValue.ConnectorType)\\r\\n\\t| extend ConnectorSource = tostring(CmdletResultValue.ConnectorSource)\\r\\n\\t| extend RecipientDomains = tostring(CmdletResultValue.RecipientDomains)\\r\\n | extend SmartHosts = tostring(CmdletResultValue.SmartHosts)\\r\\n | extend TlsDomain = tostring(CmdletResultValue.TlsDomain)\\r\\n | extend TlsSettings = tostring(CmdletResultValue.TlsSettings)\\r\\n | extend IsTransportRuleScoped = tostring(CmdletResultValue.IsTransportRuleScoped)\\r\\n | extend RouteAllMessagesViaOnPremises = tostring(CmdletResultValue.RouteAllMessagesViaOnPremises)\\r\\n | extend CloudServicesMailEnabled = tostring(CmdletResultValue.CloudServicesMailEnabled)\\r\\n | extend AllAcceptedDomains = tostring(CmdletResultValue.AllAcceptedDomains)\\r\\n | extend SenderRewritingEnabled = tostring(CmdletResultValue.SenderRewritingEnabled)\\r\\n | extend TestMode = tostring(CmdletResultValue.TestMode)\\r\\n | extend LinkForModifiedConnector = tostring(CmdletResultValue.LinkForModifiedConnector)\\r\\n | extend ValidationRecipients = tostring(CmdletResultValue.ValidationRecipients)\\r\\n | extend IsValidated = tostring(CmdletResultValue.IsValidated)\\r\\n | extend LastValidationTimestamp = tostring(CmdletResultValue.LastValidationTimestamp)\\r\\n | extend Comment = tostring(CmdletResultValue.Comment)\\r\\n ;\\r\\nlet DiffAddDataP1 = allDataRange\\r\\n | join kind = rightanti (AfterData | where WhenCreated >=_DateCompareB) on WhenCreated\\r\\n;\\r\\nlet DiffAddDataP2 = allDataRange\\r\\n | join kind = innerunique (allDataRange ) on WhenCreated\\r\\n | where WhenCreated >=_DateCompareB\\r\\n | where bin(WhenCreated,5m)==bin(WhenChanged,5m)\\r\\n | distinct Identity,State,ConnectorType,ConnectorSource,UseMXRecord,RecipientDomains,SmartHosts,TlsDomain,TlsSettings,IsTransportRuleScoped,RouteAllMessagesViaOnPremises,CloudServicesMailEnabled,AllAcceptedDomains,SenderRewritingEnabled,TestMode,LinkForModifiedConnector,ValidationRecipients,IsValidated,LastValidationTimestamp,Comment,WhenCreated\\r\\n ;\\r\\nlet DiffAddData = union DiffAddDataP1,DiffAddDataP2\\r\\n| extend Actiontype =\\\"Add\\\";\\r\\nlet DiffRemoveData = allDataRange\\r\\n | join kind = leftanti AfterData on Identity\\r\\n | extend Actiontype =\\\"Remove\\\"\\r\\n | distinct Actiontype ,Identity,State,ConnectorType,ConnectorSource,UseMXRecord,RecipientDomains,SmartHosts,TlsDomain,TlsSettings,IsTransportRuleScoped,RouteAllMessagesViaOnPremises,CloudServicesMailEnabled,AllAcceptedDomains,SenderRewritingEnabled,TestMode,LinkForModifiedConnector,ValidationRecipients,IsValidated,LastValidationTimestamp,Comment,WhenCreated\\r\\n | project WhenChanged=_CurrentDateB,Actiontype,Identity,State,ConnectorType,ConnectorSource,UseMXRecord,RecipientDomains,SmartHosts,TlsDomain,TlsSettings,IsTransportRuleScoped,RouteAllMessagesViaOnPremises,CloudServicesMailEnabled,AllAcceptedDomains,SenderRewritingEnabled,TestMode,LinkForModifiedConnector,ValidationRecipients,IsValidated,LastValidationTimestamp,Comment,WhenCreated\\r\\n ;\\r\\nlet DiffModifData = union AfterData,allDataRange\\r\\n| sort by Identity,WhenChanged asc\\r\\n| project WhenChanged,Identity,State,ConnectorType,ConnectorSource,UseMXRecord,RecipientDomains,SmartHosts,TlsDomain,TlsSettings,IsTransportRuleScoped,RouteAllMessagesViaOnPremises,CloudServicesMailEnabled,AllAcceptedDomains,SenderRewritingEnabled,TestMode,LinkForModifiedConnector,ValidationRecipients,IsValidated,LastValidationTimestamp,Comment,WhenCreated\\r\\n| extend Identity = iff( Identity == prev(Identity) and Identity != prev(Identity) and prev(Identity) !=\\\"\\\" , strcat(\\\"📍 \\\", Identity, \\\" (\\\",prev(Identity),\\\"->\\\", Identity,\\\" )\\\"),Identity)\\r\\n| extend State = iff( Identity == prev(Identity) and State != prev(State) and prev(State) !=\\\"\\\" , strcat(\\\"📍 \\\", State, \\\" (\\\",prev(State),\\\"->\\\", State,\\\" )\\\"),State)\\r\\n| extend ConnectorType = iff( Identity == prev(Identity) and ConnectorType != prev(ConnectorType) and prev(ConnectorType) !=\\\"\\\" , strcat(\\\"📍 \\\", ConnectorType, \\\" (\\\",prev(ConnectorType),\\\"->\\\", ConnectorType,\\\" )\\\"),ConnectorType)\\r\\n| extend ConnectorSource = iff( Identity == prev(Identity) and ConnectorSource != prev(ConnectorSource) and prev(ConnectorSource) !=\\\"\\\" , strcat(\\\"📍 \\\", ConnectorSource, \\\" (\\\",prev(ConnectorSource),\\\"->\\\", ConnectorSource,\\\" )\\\"),ConnectorSource)\\r\\n| extend CloudServicesMailEnabled = iff( Identity == prev(Identity) and CloudServicesMailEnabled != prev(CloudServicesMailEnabled) and prev(CloudServicesMailEnabled) !=\\\"\\\" , strcat(\\\"📍 \\\", CloudServicesMailEnabled, \\\" (\\\",prev(CloudServicesMailEnabled),\\\"->\\\", CloudServicesMailEnabled,\\\" )\\\"),CloudServicesMailEnabled)\\r\\n| extend Comment = iff( Comment == prev(Comment) and Comment != prev(Comment) and prev(Comment) !=\\\"\\\" , strcat(\\\"📍 \\\", Comment, \\\" (\\\",prev(Comment),\\\"->\\\", Comment,\\\" )\\\"),Comment)\\r\\n| extend RecipientDomains = iff( Identity == prev(Identity) and RecipientDomains != prev(RecipientDomains) and prev(RecipientDomains) !=\\\"\\\" , strcat(\\\"📍 \\\", RecipientDomains, \\\" (\\\",prev(RecipientDomains),\\\"->\\\", RecipientDomains,\\\" )\\\"),RecipientDomains)\\r\\n| extend SmartHosts = iff( Identity == prev(Identity) and SmartHosts != prev(SmartHosts) and prev(SmartHosts) !=\\\"\\\" , strcat(\\\"📍 \\\", SmartHosts, \\\" (\\\",prev(SmartHosts),\\\"->\\\", SmartHosts,\\\" )\\\"),SmartHosts)\\r\\n| extend TlsDomain = iff( Identity == prev(Identity) and TlsDomain != prev(TlsDomain) and prev(TlsDomain) !=\\\"\\\" , strcat(\\\"📍 \\\", TlsDomain, \\\" (\\\",prev(TlsDomain),\\\"->\\\", TlsDomain,\\\" )\\\"),TlsDomain)\\r\\n| extend IsTransportRuleScoped = iff( Identity == prev(Identity) and IsTransportRuleScoped != prev(IsTransportRuleScoped) and prev(IsTransportRuleScoped) !=\\\"\\\" , strcat(\\\"📍 \\\", IsTransportRuleScoped, \\\" (\\\",prev(IsTransportRuleScoped),\\\"->\\\", IsTransportRuleScoped,\\\" )\\\"),IsTransportRuleScoped)\\r\\n| extend RouteAllMessagesViaOnPremises = iff( Identity == prev(Identity) and RouteAllMessagesViaOnPremises != prev(RouteAllMessagesViaOnPremises) and prev(RouteAllMessagesViaOnPremises) !=\\\"\\\" , strcat(\\\"📍 \\\", RouteAllMessagesViaOnPremises, \\\" (\\\",prev(RouteAllMessagesViaOnPremises),\\\"->\\\", RouteAllMessagesViaOnPremises,\\\" )\\\"),RouteAllMessagesViaOnPremises)\\r\\n| extend AllAcceptedDomains = iff( Identity == prev(Identity) and AllAcceptedDomains != prev(AllAcceptedDomains) and prev(AllAcceptedDomains) !=\\\"\\\" , strcat(\\\"📍 \\\", AllAcceptedDomains, \\\" (\\\",prev(AllAcceptedDomains),\\\"->\\\", AllAcceptedDomains,\\\" )\\\"),AllAcceptedDomains)\\r\\n| extend SenderRewritingEnabled = iff( Identity == prev(Identity) and SenderRewritingEnabled != prev(SenderRewritingEnabled) and prev(SenderRewritingEnabled) !=\\\"\\\" , strcat(\\\"📍 \\\", SenderRewritingEnabled, \\\" (\\\",prev(SenderRewritingEnabled),\\\"->\\\", SenderRewritingEnabled,\\\" )\\\"),SenderRewritingEnabled)\\r\\n| extend TestMode = iff( Identity == prev(Identity)and TestMode != prev(TestMode) and prev(TestMode) !=\\\"\\\" , strcat(\\\"📍 \\\", TestMode, \\\" (\\\",prev(TestMode),\\\"->\\\", TestMode,\\\" )\\\"),TestMode)\\r\\n| extend LinkForModifiedConnector = iff( Identity == prev(Identity) and LinkForModifiedConnector != prev(LinkForModifiedConnector) and prev(LinkForModifiedConnector) !=\\\"\\\" , strcat(\\\"📍 \\\", LinkForModifiedConnector, \\\" (\\\",prev(LinkForModifiedConnector),\\\"->\\\", LinkForModifiedConnector,\\\" )\\\"),LinkForModifiedConnector)\\r\\n| extend ValidationRecipients = iff( Identity == prev(Identity) and ValidationRecipients != prev(ValidationRecipients) and prev(ValidationRecipients) !=\\\"\\\" , strcat(\\\"📍 \\\", ValidationRecipients, \\\" (\\\",prev(ValidationRecipients),\\\"->\\\", ValidationRecipients,\\\" )\\\"),ValidationRecipients)\\r\\n| extend IsValidated = iff( Identity == prev(Identity) and IsValidated != prev(IsValidated) and prev(IsValidated) !=\\\"\\\" , strcat(\\\"📍 \\\", IsValidated, \\\" (\\\",prev(IsValidated),\\\"->\\\", IsValidated,\\\" )\\\"),IsValidated)\\r\\n| extend LastValidationTimestamp = iff( Identity == prev(Identity) and LastValidationTimestamp != prev(LastValidationTimestamp) and prev(LastValidationTimestamp) !=\\\"\\\" , strcat(\\\"📍 \\\", LastValidationTimestamp, \\\" (\\\",prev(LastValidationTimestamp),\\\"->\\\", LastValidationTimestamp,\\\" )\\\"),LastValidationTimestamp)\\r\\n| extend Comment = iff( Identity == prev(Identity) and Comment != prev(Comment) and prev(Comment) !=\\\"\\\" , strcat(\\\"📍 \\\", Comment, \\\" (\\\",prev(Comment),\\\"->\\\", Comment,\\\" )\\\"),Comment)\\r\\n| extend ActiontypeR =iff((Identity contains \\\"📍\\\" or State contains \\\"📍\\\" or ConnectorType contains \\\"📍\\\" or ConnectorSource contains \\\"📍\\\"or CloudServicesMailEnabled contains \\\"📍\\\" or Comment contains \\\"📍\\\" or UseMXRecord contains \\\"📍\\\" or RecipientDomains contains \\\"📍\\\" or SmartHosts contains \\\"📍\\\" or TlsDomain contains \\\"📍\\\" or TlsSettings contains \\\"📍\\\" or IsTransportRuleScoped contains \\\"📍\\\" or RouteAllMessagesViaOnPremises contains \\\"📍\\\" or AllAcceptedDomains contains \\\"📍\\\" or SenderRewritingEnabled contains \\\"📍\\\" or TestMode contains \\\"📍\\\" or LinkForModifiedConnector contains \\\"📍\\\" or ValidationRecipients contains \\\"📍\\\" or IsValidated contains \\\"📍\\\" or LastValidationTimestamp contains \\\"📍\\\" ), i=i + 1, i)\\r\\n| extend Actiontype =iff(ActiontypeR > 0, \\\"Modif\\\", \\\"NO\\\")\\r\\n| where ActiontypeR == 1\\r\\n| project WhenChanged,Actiontype,Identity,State,ConnectorType,ConnectorSource,UseMXRecord,RecipientDomains,SmartHosts,TlsDomain,TlsSettings,IsTransportRuleScoped,RouteAllMessagesViaOnPremises,CloudServicesMailEnabled,AllAcceptedDomains,SenderRewritingEnabled,TestMode,LinkForModifiedConnector,ValidationRecipients,IsValidated,LastValidationTimestamp,Comment,WhenCreated\\r\\n;\\r\\nunion DiffAddData, DiffRemoveData, DiffModifData\\r\\n| extend WhenChanged = iff (Actiontype == \\\"Modif\\\", WhenChanged, iff(Actiontype == \\\"Add\\\",WhenCreated, WhenChanged))\\r\\n| extend Actiontype = case(Actiontype == \\\"Add\\\", strcat(\\\"➕ \\\", Actiontype), Actiontype == \\\"Remove\\\", strcat(\\\"➖ \\\", Actiontype), Actiontype == \\\"Modif\\\", strcat(\\\"📍 \\\", Actiontype), \\\"N/A\\\")\\r\\n| sort by WhenChanged desc \\r\\n| project\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n State,\\r\\n ConnectorType,\\r\\n ConnectorSource, \\r\\n CloudServicesMailEnabled,\\r\\n Comment,\\r\\n UseMXRecord,\\r\\n RecipientDomains,\\r\\n SmartHosts,\\r\\n TlsDomain,\\r\\n TlsSettings,\\r\\n IsTransportRuleScoped,\\r\\n RouteAllMessagesViaOnPremises,\\r\\n AllAcceptedDomains,\\r\\n SenderRewritingEnabled,\\r\\n TestMode,\\r\\n LinkForModifiedConnector,\\r\\n ValidationRecipients,\\r\\n IsValidated,\\r\\n LastValidationTimestamp,\\r\\n WhenCreated\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"Identity\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 4\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Transport Rules with specific actions to monitor\",\"items\":[{\"type\":1,\"content\":{\"json\":\"A common way used by attackers to exfiltrate data is to set Transport Rules that send all or sensitive messages outside the organization or to a mailbox where they already have full control.\\r\\n\\r\\nThis section shows your Transport rules with sentitive actions that can lead to data leaks:\\r\\n- BlindCopyTo\\r\\n- SentTo\\r\\n- CopyTo\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"TransportRulesHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"TransportRule\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n| extend Identity = iif( CmdletResultValue.Identity contains \\\"OrgHierarchyToIgnore\\\",tostring(CmdletResultValue.Identity.Name),tostring(CmdletResultValue.Identity))\\r\\n| extend State = tostring(CmdletResultValue.State)\\r\\n| extend SentTo = tostring(CmdletResultValue.SentToString)\\r\\n| extend BlindCopyTo = tostring(CmdletResultValue.BlindCopyToString)\\r\\n| extend CopyTo = tostring(CmdletResultValue.CopyToString)\\r\\n| extend RedirectMessageTo = tostring(CmdletResultValue.RedirectMessageToString)\\r\\n| extend SetSCL = tostring(CmdletResultValue.SetSCL)\\r\\n| extend SenderIpRangesString = tostring(CmdletResultValue.SenderIpRangesString)\\r\\n| extend MessageTypeMatchesString = tostring(CmdletResultValue.MessageTypeMatchesString)\\r\\n| extend Mode = tostring(CmdletResultValue.Mode)\\r\\n| project-away CmdletResultValue\\r\\n| sort by Identity asc\",\"size\":1,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"Transport Rules actions to monitor\"},{\"type\":1,\"content\":{\"json\":\"** Due to lack of informaiton in Powershell, the Transport Rule compare section could display approximate information for Add and Modif. Especially, for the WhenCreated parameter.\"},\"name\":\"text - 7\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let _EnvList ={EnvironmentList};\\r\\nlet _TypeEnv = \\\"Online\\\";\\r\\nlet _DateCompare = \\\"{DateCompare:value}\\\";\\r\\nlet _CurrentDate = \\\"{DateOfConfiguration:value}\\\";\\r\\nlet _DateCompareB = todatetime(_DateCompare);\\r\\nlet _currD = (ExchangeConfiguration(SpecificSectionList=\\\"TransportRule\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n| summarize TimeMax = arg_max(TimeGenerated,*)\\r\\n| extend TimeMax = tostring(split(TimeMax,\\\"T\\\")[0])\\r\\n| project TimeMax);\\r\\nlet _CurrentDateB = todatetime(toscalar(_currD));\\r\\nlet BeforeData = ExchangeConfiguration(SpecificSectionList=\\\"TransportRule\\\",SpecificConfigurationDate=_DateCompareB,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend SentTo = tostring(CmdletResultValue.SentToString)\\r\\n\\t| extend BlindCopyTo = tostring(CmdletResultValue.BlindCopyToString)\\r\\n\\t| extend CopyTo = tostring(CmdletResultValue.CopyToString)\\r\\n\\t| extend RedirectMessageTo = tostring(CmdletResultValue.RedirectMessageToString)\\r\\n\\t| extend Mode = tostring(CmdletResultValue.Mode)\\r\\n | extend SetSCL = tostring(CmdletResultValue.SetSCL)\\r\\n | extend SenderIpRangesString = tostring(CmdletResultValue.SenderIpRangesString)\\r\\n | extend MessageTypeMatchesString = tostring(CmdletResultValue.MessageTypeMatchesString)\\r\\n | extend CmdletResultValue.RedirectMessageToString\\r\\n\\t| extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet AfterData = \\r\\n ExchangeConfiguration(SpecificSectionList=\\\"TransportRule\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| extend SentTo = tostring(CmdletResultValue.SentToString)\\r\\n\\t| extend BlindCopyTo = tostring(CmdletResultValue.BlindCopyToString)\\r\\n\\t| extend CopyTo = tostring(CmdletResultValue.CopyToString)\\r\\n\\t| extend RedirectMessageTo = tostring(CmdletResultValue.RedirectMessageToString)\\r\\n\\t| extend Mode = tostring(CmdletResultValue.Mode)\\r\\n | extend SetSCL = tostring(CmdletResultValue.SetSCL)\\r\\n | extend SenderIpRangesString = tostring(CmdletResultValue.SenderIpRangesString)\\r\\n | extend MessageTypeMatchesString = tostring(CmdletResultValue.MessageTypeMatchesString)\\r\\n\\t| extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet i=0;\\r\\nlet allDataRange =\\r\\n ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"TransportRule\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | project TimeGenerated,CmdletResultValue,WhenChanged = todatetime(WhenChanged_t), WhenCreated=todatetime(WhenCreated_t)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n\\t| sort by Identity,TimeGenerated asc\\r\\n | extend SentTo = tostring(CmdletResultValue.SentToString)\\r\\n\\t| extend BlindCopyTo = tostring(CmdletResultValue.BlindCopyToString)\\r\\n\\t| extend CopyTo = tostring(CmdletResultValue.CopyToString)\\r\\n\\t| extend RedirectMessageTo = tostring(CmdletResultValue.RedirectMessageToString)\\r\\n\\t| extend Mode = tostring(CmdletResultValue.Mode)\\r\\n | extend CmdletResultValue.RedirectMessageToString\\r\\n | extend SetSCL = tostring(CmdletResultValue.SetSCL)\\r\\n | extend SenderIpRangesString = tostring(CmdletResultValue.SenderIpRangesString)\\r\\n | extend MessageTypeMatchesString = tostring(CmdletResultValue.MessageTypeMatchesString)\\r\\n | extend WhenChanged = todatetime(bin(WhenChanged,1m))\\r\\n | extend aa=prev(WhenCreated)\\r\\n | extend WhenCreated = iff( Identity == prev(Identity) and WhenChanged != prev(WhenChanged),aa ,WhenChanged)\\r\\n | extend WhenCreated =bin(WhenCreated,1m)\\r\\n ;\\r\\nlet DiffAddDataP1 = allDataRange\\r\\n | join kind = rightanti (AfterData | where WhenCreated >=_DateCompareB) on WhenCreated\\r\\n;\\r\\nlet DiffAddDataP2 = allDataRange\\r\\n | join kind = inner (allDataRange ) on WhenCreated\\r\\n | where WhenCreated >=_DateCompareB\\r\\n | where bin(WhenCreated,5m)==bin(WhenChanged,5m)\\r\\n | distinct Identity,SentTo,BlindCopyTo,CopyTo,RedirectMessageTo,Mode,SetSCL,SenderIpRangesString,MessageTypeMatchesString,WhenChanged,WhenCreated\\r\\n ;\\r\\nlet DiffAddData1 = union DiffAddDataP1,DiffAddDataP2\\r\\n| extend Actiontype =\\\"Add\\\";\\r\\nlet DiffAddData2 = union DiffAddDataP1,DiffAddDataP2\\r\\n| extend Actiontype =\\\"Add\\\"\\r\\n| distinct Identity;\\r\\nlet DiffAddData = DiffAddData1\\r\\n| join DiffAddData2 on Identity\\r\\n;\\r\\nlet DiffRemoveData = allDataRange\\r\\n | join kind = leftanti AfterData on Identity\\r\\n | extend Actiontype =\\\"Remove\\\"\\r\\n | distinct Actiontype ,Identity,SentTo,BlindCopyTo,CopyTo,RedirectMessageTo,SetSCL,SenderIpRangesString,MessageTypeMatchesString,Mode,WhenChanged,WhenCreated\\r\\n | project WhenChanged=_CurrentDateB,Actiontype,Identity,SentTo,BlindCopyTo,CopyTo,RedirectMessageTo,SetSCL,SenderIpRangesString,MessageTypeMatchesString,Mode,WhenCreated\\r\\n ;\\r\\nlet DiffModifData = union AfterData,allDataRange\\r\\n| sort by Identity,WhenChanged asc\\r\\n| project WhenChanged,Identity,SentTo,BlindCopyTo,CopyTo,RedirectMessageTo, SetSCL, SenderIpRangesString,MessageTypeMatchesString,Mode,WhenCreated\\r\\n| extend Identity = iff( Identity == prev(Identity) and Identity != prev(Identity) and prev(Identity) !=\\\"\\\" , strcat(\\\"📍 \\\", Identity, \\\" (\\\",prev(Identity),\\\"->\\\", Identity,\\\" )\\\"),Identity)\\r\\n| extend SentTo = iff( Identity == prev(Identity) and SentTo != prev(SentTo) and prev(SentTo) !=\\\"\\\" , strcat(\\\"📍 \\\", SentTo, \\\" (\\\",prev(SentTo),\\\"->\\\", SentTo,\\\" )\\\"),SentTo)\\r\\n| extend BlindCopyTo = iff( Identity == prev(Identity) and BlindCopyTo != prev(BlindCopyTo) and prev(BlindCopyTo) !=\\\"\\\" , strcat(\\\"📍 \\\", BlindCopyTo, \\\" (\\\",prev(BlindCopyTo),\\\"->\\\", BlindCopyTo,\\\" )\\\"),BlindCopyTo)\\r\\n| extend CopyTo = iff( Identity == prev(Identity) and CopyTo != prev(CopyTo) and prev(CopyTo) !=\\\"\\\" , strcat(\\\"📍 \\\", CopyTo, \\\" (\\\",prev(CopyTo),\\\"->\\\", CopyTo,\\\" )\\\"),CopyTo)\\r\\n| extend SetSCL = iff( Identity == prev(Identity)and SetSCL != prev(SetSCL) and prev(SetSCL) !=\\\"\\\" , strcat(\\\"📍 \\\", SetSCL, \\\" (\\\",prev(SetSCL),\\\"->\\\", SetSCL,\\\" )\\\"),SetSCL)\\r\\n| extend SenderIpRangesString = iff( Identity == prev(Identity)and SenderIpRangesString != prev(SenderIpRangesString) and prev(SenderIpRangesString) !=\\\"\\\" , strcat(\\\"📍 \\\", SenderIpRangesString, \\\" (\\\",prev(SenderIpRangesString),\\\"->\\\", SenderIpRangesString,\\\" )\\\"),SenderIpRangesString)\\r\\n| extend MessageTypeMatchesString = iff( Identity == prev(Identity)and MessageTypeMatchesString != prev(MessageTypeMatchesString) and prev(MessageTypeMatchesString) !=\\\"\\\" , strcat(\\\"📍 \\\", MessageTypeMatchesString, \\\" (\\\",prev(MessageTypeMatchesString),\\\"->\\\", MessageTypeMatchesString,\\\" )\\\"),MessageTypeMatchesString)\\r\\n| extend Mode = iff( Identity == prev(Identity)and Mode != prev(Mode) and prev(Mode) !=\\\"\\\" , strcat(\\\"📍 \\\", Mode, \\\" (\\\",prev(Mode),\\\"->\\\", Mode,\\\" )\\\"),Mode)\\r\\n| extend ActiontypeR =iff((Identity contains \\\"📍\\\" or SentTo contains \\\"📍\\\" or BlindCopyTo contains \\\"📍\\\" or CopyTo contains \\\"📍\\\" or SetSCL contains \\\"📍\\\" or SenderIpRangesString contains \\\"📍\\\" or MessageTypeMatchesString contains \\\"📍\\\" or Mode contains \\\"📍\\\" ), i=i + 1, i)\\r\\n| extend Actiontype =iff(ActiontypeR > 0, \\\"Modif\\\", \\\"NO\\\")\\r\\n| where ActiontypeR == 1\\r\\n| project WhenChanged,Actiontype,Identity,SentTo,BlindCopyTo,CopyTo,RedirectMessageTo,SetSCL,SenderIpRangesString,MessageTypeMatchesString,Mode,WhenCreated\\r\\n;\\r\\nunion DiffAddData, DiffRemoveData, DiffModifData\\r\\n| extend WhenChanged = iff (Actiontype == \\\"Modif\\\", WhenChanged, iff(Actiontype == \\\"Add\\\",WhenCreated, WhenChanged))\\r\\n| extend Actiontype = case(Actiontype == \\\"Add\\\", strcat(\\\"➕ \\\", Actiontype), Actiontype == \\\"Remove\\\", strcat(\\\"➖ \\\", Actiontype), Actiontype == \\\"Modif\\\", strcat(\\\"📍 \\\", Actiontype), \\\"N/A\\\")\\r\\n| sort by WhenChanged desc \\r\\n| project\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n SentTo,\\r\\n BlindCopyTo,\\r\\n CopyTo,\\r\\n RedirectMessageTo,\\r\\n SetSCL,\\r\\n SenderIpRangesString,\\r\\n MessageTypeMatchesString,\\r\\n Mode,\\r\\n WhenCreated\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"Identity\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 5\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Outbound Policy : Autoforward configuration\",\"items\":[{\"type\":1,\"content\":{\"json\":\"If **AutoForwardEnabled** is enabled, then automatic transfer are allowed.\\r\\nFor example: users in Outlook will be able set automatic transfer of all their emails to external addresses.\\r\\nThere are several methods to authorized automatic forward. \\r\\nPlease review this article : https://learn.microsoft.com/microsoft-365/security/office-365-security/outbound-spam-policies-external-email-forwarding?view=o365-worldwide\\r\\n**In summary :**\\r\\n\\r\\n**Scenario 1 :**\\r\\n\\r\\nYou configure remote domain settings to allow automatic forwarding.\\r\\nAutomatic forwarding in the outbound spam filter policy is set to Off.\\r\\n*Result :* \\r\\nAutomatically forwarded messages to recipients in the affected domains are blocked.\\r\\n\\r\\n**Scenario 2 :**\\r\\n\\r\\nYou configure remote domain settings to allow automatic forwarding.\\r\\nAutomatic forwarding in the outbound spam filter policy is set to Automatic - System-controlled.\\r\\n\\r\\n*Result :* \\r\\n\\r\\nAutomatically forwarded messages to recipients in the affected domains are blocked.\\r\\nAs described earlier, Automatic - System-controlled used to mean On, but the setting has changed over time to mean Off in all organizations.\\r\\n\\r\\nFor absolute clarity, you should configure your outbound spam filter policy to On or Off.\\r\\n\\r\\n**Scenario 3 :**\\r\\n\\r\\nAutomatic forwarding in the outbound spam filter policy is set to On\\r\\nYou use mail flow rules or remote domains to block automatically forwarded email\\r\\n\\r\\n*Result : *\\r\\n\\r\\nAutomatically forwarded messages to affected recipients are blocked by mail flow rules or remote domains.\\r\\n****\\r\\nAlso, when setting AutoForwardEnabled to a specific domain, it is strongly recommended enable TLS encryption.\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"AutoForwardHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let HOSFR = ExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterRule\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend HostedOutboundSpamFilterPolicy = tostring(CmdletResultValue.HostedOutboundSpamFilterPolicy)\\r\\n| project Identity,HostedOutboundSpamFilterPolicy;\\r\\nExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterPolicy\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend Identity = tostring(CmdletResultValue.Identity)\\r\\n| join kind = fullouter HOSFR on $left.Identity == $right.HostedOutboundSpamFilterPolicy\\r\\n| extend OutboundSpamFilterRule = tostring(Identity1)\\r\\n| extend IsDefault= tostring(CmdletResultValue.IsDefault)\\r\\n| extend Enabled= tostring(CmdletResultValue.Enabled)\\r\\n| extend AutoForwardingMode= iff (CmdletResultValue.AutoForwardingMode == \\\"On\\\" , strcat (\\\"❌ \\\", tostring(CmdletResultValue.AutoForwardingMode)), tostring(CmdletResultValue.AutoForwardingMode))\\r\\n| extend RecommendedPolicyType= tostring(CmdletResultValue.RecommendedPolicyType)\\r\\n| extend RecipientLimitExternalPerHour = tostring(CmdletResultValue.RecipientLimitExternalPerHour)\\r\\n| extend RecipientLimitInternalPerHour = tostring(CmdletResultValue.RecipientLimitInternalPerHour)\\r\\n| extend RecipientLimitPerDay= tostring(CmdletResultValue.RecipientLimitPerDay)\\r\\n| extend ActionWhenThresholdReached = tostring(CmdletResultValue.ActionWhenThresholdReached)\\r\\n| extend BccSuspiciousOutboundAdditionalRecipients= tostring(CmdletResultValue.BccSuspiciousOutboundAdditionalRecipients)\\r\\n| extend BccSuspiciousOutboundMail = tostring(CmdletResultValue.BccSuspiciousOutboundMail)\\r\\n| extend NotifyOutboundSpam= tostring(CmdletResultValue.NotifyOutboundSpam)\\r\\n| extend NotifyOutboundSpamRecipient = tostring(CmdletResultValue.NotifyOutboundSpamRecipient)\\r\\n| project Identity,IsDefault,Enabled,AutoForwardingMode,OutboundSpamFilterRule,BccSuspiciousOutboundAdditionalRecipients,BccSuspiciousOutboundMail,NotifyOutboundSpam,NotifyOutboundSpamRecipient,WhenChanged,WhenCreated\\r\\n| sort by Identity asc \",\"size\":1,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true}},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"OutboundPol - Copy\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let _EnvList ={EnvironmentList};\\r\\nlet _TypeEnv = \\\"Online\\\";\\r\\nlet _DateCompare = \\\"{DateCompare:value}\\\";\\r\\nlet _CurrentDate = \\\"{DateOfConfiguration:value}\\\";\\r\\nlet _DateCompareB = todatetime(_DateCompare);\\r\\nlet _currD = (ExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterPolicy\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n| summarize TimeMax = arg_max(TimeGenerated,*)\\r\\n| extend TimeMax = tostring(split(TimeMax,\\\"T\\\")[0])\\r\\n| project TimeMax);\\r\\nlet _CurrentDateB = todatetime(toscalar(_currD));\\r\\nlet HOSFR = ExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterRule\\\", SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| extend HostedOutboundSpamFilterPolicy = tostring(CmdletResultValue.HostedOutboundSpamFilterPolicy)\\r\\n| project Identity,HostedOutboundSpamFilterPolicy;\\r\\nlet BeforeData = ExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterPolicy\\\", SpecificConfigurationDate=_DateCompareB, SpecificConfigurationEnv=_EnvList, Target = _TypeEnv)\\r\\n | extend Identity = tostring(Identity)\\r\\n | join kind = fullouter HOSFR on $left.Identity == $right.HostedOutboundSpamFilterPolicy\\r\\n | extend OutboundSpamFilterRule = tostring(Identity1)\\r\\n | extend IsDefault= tostring(CmdletResultValue.IsDefault)\\r\\n | extend Enabled= tostring(CmdletResultValue.Enabled)\\r\\n | extend AutoForwardingMode= tostring(CmdletResultValue.AutoForwardingMode)\\r\\n | extend RecommendedPolicyType= tostring(CmdletResultValue.RecommendedPolicyType)\\r\\n | extend RecipientLimitExternalPerHour = tostring(CmdletResultValue.RecipientLimitExternalPerHour)\\r\\n | extend RecipientLimitInternalPerHour = tostring(CmdletResultValue.RecipientLimitInternalPerHour)\\r\\n | extend RecipientLimitPerDay= tostring(CmdletResultValue.RecipientLimitPerDay)\\r\\n | extend ActionWhenThresholdReached = tostring(CmdletResultValue.ActionWhenThresholdReached)\\r\\n | extend BccSuspiciousOutboundAdditionalRecipients= tostring(CmdletResultValue.BccSuspiciousOutboundAdditionalRecipients)\\r\\n | extend BccSuspiciousOutboundMail = tostring(CmdletResultValue.BccSuspiciousOutboundMail)\\r\\n | extend NotifyOutboundSpam= tostring(CmdletResultValue.NotifyOutboundSpam)\\r\\n | extend NotifyOutboundSpamRecipient = tostring(CmdletResultValue.NotifyOutboundSpamRecipient)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet AfterData = \\r\\n ExchangeConfiguration(SpecificSectionList=\\\"HostedOutboundSpamFilterPolicy\\\", SpecificConfigurationDate=_CurrentDate, SpecificConfigurationEnv=_EnvList, Target = _TypeEnv)\\r\\n | extend Identity = tostring(Identity)\\r\\n | join kind = fullouter HOSFR on $left.Identity == $right.HostedOutboundSpamFilterPolicy\\r\\n | extend OutboundSpamFilterRule = tostring(Identity1)\\r\\n | extend IsDefault= tostring(CmdletResultValue.IsDefault)\\r\\n | extend Enabled= tostring(CmdletResultValue.Enabled)\\r\\n | extend AutoForwardingMode= tostring(CmdletResultValue.AutoForwardingMode)\\r\\n | extend RecommendedPolicyType= tostring(CmdletResultValue.RecommendedPolicyType)\\r\\n | extend RecipientLimitExternalPerHour = tostring(CmdletResultValue.RecipientLimitExternalPerHour)\\r\\n | extend RecipientLimitInternalPerHour = tostring(CmdletResultValue.RecipientLimitInternalPerHour)\\r\\n | extend RecipientLimitPerDay= tostring(CmdletResultValue.RecipientLimitPerDay)\\r\\n | extend ActionWhenThresholdReached = tostring(CmdletResultValue.ActionWhenThresholdReached)\\r\\n | extend BccSuspiciousOutboundAdditionalRecipients= tostring(CmdletResultValue.BccSuspiciousOutboundAdditionalRecipients)\\r\\n | extend BccSuspiciousOutboundMail = tostring(CmdletResultValue.BccSuspiciousOutboundMail)\\r\\n | extend NotifyOutboundSpam= tostring(CmdletResultValue.NotifyOutboundSpam)\\r\\n | extend NotifyOutboundSpamRecipient = tostring(CmdletResultValue.NotifyOutboundSpamRecipient)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet i=0;\\r\\nlet allDataRangeOSFR = ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"HostedOutboundSpamFilterRule\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n | extend HostedOutboundSpamFilterPolicy = tostring(CmdletResultValue.HostedOutboundSpamFilterPolicy)\\r\\n | project Identity, HostedOutboundSpamFilterPolicy;\\r\\nlet allDataRange = \\r\\n ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"HostedOutboundSpamFilterPolicy\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | extend Identity = tostring(CmdletResultValue.Identity)\\r\\n | project\\r\\n TimeGenerated,\\r\\n Identity,\\r\\n CmdletResultValue,\\r\\n WhenChanged = todatetime(bin(WhenChanged_t,1m)),\\r\\n WhenCreated=todatetime(bin(WhenCreated_t,1m))\\r\\n | join kind=fullouter allDataRangeOSFR on $left.Identity == $right.HostedOutboundSpamFilterPolicy\\r\\n | extend OutboundSpamFilterRule = tostring(Identity1)\\r\\n | extend IsDefault= tostring(CmdletResultValue.IsDefault)\\r\\n | extend Enabled= tostring(CmdletResultValue.Enabled)\\r\\n | extend AutoForwardingMode= tostring(CmdletResultValue.AutoForwardingMode)\\r\\n | extend RecommendedPolicyType= tostring(CmdletResultValue.RecommendedPolicyType)\\r\\n | extend RecipientLimitExternalPerHour = tostring(CmdletResultValue.RecipientLimitExternalPerHour)\\r\\n | extend RecipientLimitInternalPerHour = tostring(CmdletResultValue.RecipientLimitInternalPerHour)\\r\\n | extend RecipientLimitPerDay= tostring(CmdletResultValue.RecipientLimitPerDay)\\r\\n | extend ActionWhenThresholdReached = tostring(CmdletResultValue.ActionWhenThresholdReached)\\r\\n | extend BccSuspiciousOutboundAdditionalRecipients= tostring(CmdletResultValue.BccSuspiciousOutboundAdditionalRecipients)\\r\\n | extend BccSuspiciousOutboundMail = tostring(CmdletResultValue.BccSuspiciousOutboundMail)\\r\\n | extend NotifyOutboundSpam= tostring(CmdletResultValue.NotifyOutboundSpam)\\r\\n | extend NotifyOutboundSpamRecipient = tostring(CmdletResultValue.NotifyOutboundSpamRecipient)\\r\\n | distinct\\r\\n WhenChanged,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \\r\\n;\\r\\nlet DiffAddDataP1 = allDataRange\\r\\n | join kind = rightanti (AfterData\\r\\n | where WhenCreated >= _DateCompareB)\\r\\n on WhenCreated\\r\\n;\\r\\nlet DiffAddDataP2 = allDataRange\\r\\n | join kind = innerunique (allDataRange) on WhenCreated\\r\\n | where WhenCreated >= _DateCompareB\\r\\n | where bin(WhenCreated, 5m) == bin(WhenChanged, 5m)\\r\\n | distinct\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \\r\\n;\\r\\nlet DiffAddData = union DiffAddDataP1, DiffAddDataP2\\r\\n | extend Actiontype =\\\"Add\\\";\\r\\nlet DiffRemoveData = allDataRange\\r\\n | join kind = leftanti AfterData on Identity\\r\\n | extend Actiontype =\\\"Remove\\\"\\r\\n | distinct\\r\\n Actiontype,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \\r\\n | project\\r\\n WhenChanged=_CurrentDateB,\\r\\n Actiontype,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated\\r\\n;\\r\\nlet DiffModifData = union AfterData, allDataRange\\r\\n | sort by Identity, WhenChanged asc\\r\\n | project\\r\\n WhenChanged,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \\r\\n | extend Identity = iff(Identity == prev(Identity) and Identity != prev(Identity) and prev(Identity) != \\\"\\\", strcat(\\\"📍 \\\", Identity, \\\" (\\\", prev(Identity), \\\"->\\\", Identity, \\\" )\\\"), Identity)\\r\\n | extend IsDefault = iff(Identity == prev(Identity) and IsDefault != prev(IsDefault) and prev(IsDefault) != \\\"\\\", strcat(\\\"📍 \\\", IsDefault, \\\" (\\\", prev(IsDefault), \\\"->\\\", IsDefault, \\\" )\\\"), IsDefault)\\r\\n | extend Enabled = iff(Identity == prev(Identity) and Enabled != prev(Enabled) and prev(Enabled) != \\\"\\\", strcat(\\\"📍 \\\", Enabled, \\\" (\\\", prev(Enabled), \\\"->\\\", Enabled, \\\" )\\\"), Enabled)\\r\\n | extend AutoForwardingMode = iff(Identity == prev(Identity) and AutoForwardingMode != prev(AutoForwardingMode) and prev(AutoForwardingMode) != \\\"\\\", strcat(\\\"📍 \\\", AutoForwardingMode, \\\" (\\\", prev(AutoForwardingMode), \\\"->\\\", AutoForwardingMode, \\\" )\\\"), AutoForwardingMode)\\r\\n | extend OutboundSpamFilterRule = iff(Identity == prev(Identity) and OutboundSpamFilterRule != prev(OutboundSpamFilterRule) and prev(OutboundSpamFilterRule) != \\\"\\\", strcat(\\\"📍 \\\", OutboundSpamFilterRule, \\\" (\\\", prev(OutboundSpamFilterRule), \\\"->\\\", OutboundSpamFilterRule, \\\" )\\\"), OutboundSpamFilterRule)\\r\\n | extend RecommendedPolicyType = iff(Identity == prev(Identity) and RecommendedPolicyType != prev(RecommendedPolicyType) and prev(RecommendedPolicyType) != \\\"\\\", strcat(\\\"📍 \\\", RecommendedPolicyType, \\\" (\\\", prev(RecommendedPolicyType), \\\"->\\\", RecommendedPolicyType, \\\" )\\\"), RecommendedPolicyType)\\r\\n | extend RecipientLimitExternalPerHour = iff(Identity == prev(Identity) and RecipientLimitExternalPerHour != prev(RecipientLimitExternalPerHour) and prev(RecipientLimitExternalPerHour) != \\\"\\\", strcat(\\\"📍 \\\", RecipientLimitExternalPerHour, \\\" (\\\", prev(RecipientLimitExternalPerHour), \\\"->\\\", RecipientLimitExternalPerHour, \\\" )\\\"), RecipientLimitExternalPerHour)\\r\\n | extend RecipientLimitInternalPerHour = iff(Identity == prev(Identity) and RecipientLimitInternalPerHour != prev(RecipientLimitInternalPerHour) and prev(RecipientLimitInternalPerHour) != \\\"\\\", strcat(\\\"📍 \\\", RecipientLimitInternalPerHour, \\\" (\\\", prev(RecipientLimitInternalPerHour), \\\"->\\\", RecipientLimitInternalPerHour, \\\" )\\\"), RecipientLimitInternalPerHour)\\r\\n | extend ActionWhenThresholdReached = iff(Identity == prev(Identity) and ActionWhenThresholdReached != prev(ActionWhenThresholdReached) and prev(ActionWhenThresholdReached) != \\\"\\\", strcat(\\\"📍 \\\", ActionWhenThresholdReached, \\\" (\\\", prev(ActionWhenThresholdReached), \\\"->\\\", ActionWhenThresholdReached, \\\" )\\\"), ActionWhenThresholdReached)\\r\\n | extend RecipientLimitPerDay = iff(Identity == prev(Identity) and RecipientLimitPerDay != prev(RecipientLimitPerDay) and prev(RecipientLimitPerDay) != \\\"\\\", strcat(\\\"📍 \\\", RecipientLimitPerDay, \\\" (\\\", prev(RecipientLimitPerDay), \\\"->\\\", RecipientLimitPerDay, \\\" )\\\"), RecipientLimitPerDay)\\r\\n | extend BccSuspiciousOutboundAdditionalRecipients = iff(Identity == prev(Identity) and BccSuspiciousOutboundAdditionalRecipients != prev(BccSuspiciousOutboundAdditionalRecipients) and prev(BccSuspiciousOutboundAdditionalRecipients) != \\\"\\\", strcat(\\\"📍 \\\", BccSuspiciousOutboundAdditionalRecipients, \\\" (\\\", prev(BccSuspiciousOutboundAdditionalRecipients), \\\"->\\\", BccSuspiciousOutboundAdditionalRecipients, \\\" )\\\"), BccSuspiciousOutboundAdditionalRecipients)\\r\\n | extend BccSuspiciousOutboundMail = iff(Identity == prev(Identity) and BccSuspiciousOutboundMail != prev(BccSuspiciousOutboundMail) and prev(BccSuspiciousOutboundMail) != \\\"\\\", strcat(\\\"📍 \\\", BccSuspiciousOutboundMail, \\\" (\\\", prev(BccSuspiciousOutboundMail), \\\"->\\\", BccSuspiciousOutboundMail, \\\" )\\\"), BccSuspiciousOutboundMail)\\r\\n | extend NotifyOutboundSpam = iff(Identity == prev(Identity) and NotifyOutboundSpam != prev(NotifyOutboundSpam) and prev(NotifyOutboundSpam) != \\\"\\\", strcat(\\\"📍 \\\", NotifyOutboundSpam, \\\" (\\\", prev(NotifyOutboundSpam), \\\"->\\\", NotifyOutboundSpam, \\\" )\\\"), NotifyOutboundSpam)\\r\\n | extend NotifyOutboundSpamRecipient = iff(Identity == prev(Identity) and NotifyOutboundSpamRecipient != prev(NotifyOutboundSpamRecipient) and prev(NotifyOutboundSpamRecipient) != \\\"\\\", strcat(\\\"📍 \\\", NotifyOutboundSpamRecipient, \\\" (\\\", prev(NotifyOutboundSpamRecipient), \\\"->\\\", NotifyOutboundSpamRecipient, \\\" )\\\"), NotifyOutboundSpamRecipient)\\r\\n | extend ActiontypeR =iff((Identity contains \\\"📍\\\" or IsDefault contains \\\"📍\\\" or Enabled contains \\\"📍\\\" or OutboundSpamFilterRule contains \\\"📍\\\" or AutoForwardingMode contains \\\"📍\\\" or BccSuspiciousOutboundAdditionalRecipients contains \\\"📍\\\" or BccSuspiciousOutboundMail contains \\\"📍\\\" or NotifyOutboundSpam contains \\\"📍\\\" or NotifyOutboundSpamRecipient contains \\\"📍\\\"), i=i + 1, i)\\r\\n | extend Actiontype =iff(ActiontypeR > 0, \\\"Modif\\\", \\\"NO\\\")\\r\\n | where ActiontypeR == 1\\r\\n | distinct\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \\r\\n;\\r\\nunion DiffAddData, DiffRemoveData, DiffModifData\\r\\n| extend WhenChanged = iff (Actiontype == \\\"Modif\\\", WhenChanged, iff(Actiontype == \\\"Add\\\", WhenCreated, WhenChanged))\\r\\n| extend Actiontype = case(Actiontype == \\\"Add\\\", strcat(\\\"➕ \\\", Actiontype), Actiontype == \\\"Remove\\\", strcat(\\\"➖ \\\", Actiontype), Actiontype == \\\"Modif\\\", strcat(\\\"📍 \\\", Actiontype), \\\"N/A\\\")\\r\\n| sort by WhenChanged desc \\r\\n| project\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n IsDefault,\\r\\n Enabled,\\r\\n AutoForwardingMode,\\r\\n OutboundSpamFilterRule,\\r\\n RecommendedPolicyType,\\r\\n RecipientLimitExternalPerHour,\\r\\n RecipientLimitInternalPerHour,\\r\\n ActionWhenThresholdReached,\\r\\n RecipientLimitPerDay,\\r\\n BccSuspiciousOutboundAdditionalRecipients,\\r\\n BccSuspiciousOutboundMail,\\r\\n NotifyOutboundSpam,\\r\\n NotifyOutboundSpamRecipient,\\r\\n WhenCreated \",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"Identity\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 7 - Copy\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Remote Domain Autofoward Configuration - * should not allow AutoForwardEnabled\",\"items\":[{\"type\":1,\"content\":{\"json\":\"If **AutoForwardEnabled** is set to True for an SMTP domain and the Outbound Policy is set to On then users in Outlook are allowed to set automatic transfer of all their emails to addresses in this domain.\\r\\n\\r\\nWhen the Default Remote domain is set to * and has the AutoForwardEnabled set True, any user can configure an Outlook rule to automatically forward all emails to any SMTP domain domains outside the organization. This is a high risk configuration as it might allow accounts to leak information. \\r\\n\\r\\nAlso, when setting AutoForwardEnabled to a specific domain, it is strongly recommended enable TLS encryption.\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"AutoForwardHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"ExchangeConfiguration(SpecificSectionList=\\\"RemoteDomain\\\",SpecificConfigurationDate=\\\"{DateOfConfiguration:value}\\\",SpecificConfigurationEnv={EnvironmentList},Target = \\\"Online\\\")\\r\\n| project CmdletResultValue\\r\\n| extend Name = tostring(CmdletResultValue.Name)\\r\\n| extend Address = tostring(CmdletResultValue.DomainName)\\r\\n| extend AutoForwardEnabled = iff (CmdletResultValue.AutoForwardEnabled== \\\"true\\\" and CmdletResultValue.DomainName == \\\"*\\\", strcat (\\\"❌ \\\",tostring(CmdletResultValue.AutoForwardEnabled)),iff(CmdletResultValue.AutoForwardEnabled== \\\"true\\\" and CmdletResultValue.DomainName != \\\"*\\\", strcat (\\\"⚠️ \\\",tostring(CmdletResultValue.AutoForwardEnabled)),strcat (\\\"✅ \\\",tostring(CmdletResultValue.AutoForwardEnabled))))\\r\\n| project-away CmdletResultValue\\r\\n| sort by Address asc \",\"size\":1,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true}},\"name\":\"query - 1\",\"styleSettings\":{\"showBorder\":true}}]},\"name\":\"ForwardGroup\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let _EnvList ={EnvironmentList};\\r\\nlet _TypeEnv = \\\"Online\\\";\\r\\nlet _DateCompare = \\\"{DateCompare:value}\\\";\\r\\nlet _CurrentDate = \\\"{DateOfConfiguration:value}\\\";\\r\\nlet _DateCompareB = todatetime(_DateCompare);\\r\\nlet _currD = (ExchangeConfiguration(SpecificSectionList=\\\"RemoteDomain\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n| summarize TimeMax = arg_max(TimeGenerated,*)\\r\\n| extend TimeMax = tostring(split(TimeMax,\\\"T\\\")[0])\\r\\n| project TimeMax);\\r\\nlet _CurrentDateB = todatetime(toscalar(_currD));\\r\\nlet BeforeData = ExchangeConfiguration(SpecificSectionList=\\\"RemoteDomain\\\",SpecificConfigurationDate=_DateCompareB,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n | extend Identity = tostring(CmdletResultValue.Name)\\r\\n\\t| extend DomainName = tostring(CmdletResultValue.DomainName)\\r\\n\\t| extend AutoForwardEnabled = tostring(CmdletResultValue.AutoForwardEnabled)\\r\\n\\t| extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet AfterData = \\r\\n ExchangeConfiguration(SpecificSectionList=\\\"RemoteDomain\\\",SpecificConfigurationDate=_CurrentDate,SpecificConfigurationEnv=_EnvList,Target = _TypeEnv)\\r\\n \\t | extend Identity = tostring(CmdletResultValue.Name)\\r\\n\\t| extend DomainName = tostring(CmdletResultValue.DomainName)\\r\\n\\t| extend AutoForwardEnabled = tostring(CmdletResultValue.AutoForwardEnabled)\\r\\n | extend WhenChanged = todatetime(WhenChanged)\\r\\n | extend WhenCreated = todatetime(WhenCreated)\\r\\n;\\r\\nlet i=0;\\r\\nlet allDataRange = \\r\\n ESIExchangeOnlineConfig_CL\\r\\n | where TimeGenerated between (_DateCompareB .. _CurrentDateB)\\r\\n | where ESIEnvironment_s == _EnvList\\r\\n | where ExecutionResult_s <> \\\"EmptyResult\\\"\\r\\n | where Section_s == \\\"RemoteDomain\\\"\\r\\n | extend CmdletResultValue = parse_json(rawData_s)\\r\\n | project TimeGenerated,CmdletResultValue,WhenChanged = todatetime(WhenChanged_t), WhenCreated=todatetime(WhenCreated_t)\\r\\n | extend Identity = tostring(CmdletResultValue.Name)\\r\\n\\t| extend DomainName = tostring(CmdletResultValue.DomainName)\\r\\n\\t| extend AutoForwardEnabled = tostring(CmdletResultValue.AutoForwardEnabled)\\r\\n ;\\r\\nlet DiffAddDataP1 = allDataRange\\r\\n | join kind = rightanti (AfterData | where WhenCreated >=_DateCompareB) on WhenCreated\\r\\n;\\r\\nlet DiffAddDataP2 = allDataRange\\r\\n | join kind = innerunique (allDataRange ) on WhenCreated\\r\\n | where WhenCreated >=_DateCompareB\\r\\n | where bin(WhenCreated,5m)==bin(WhenChanged,5m)\\r\\n | distinct Identity,DomainName,AutoForwardEnabled,WhenChanged,WhenCreated\\r\\n ;\\r\\nlet DiffAddData = union DiffAddDataP1,DiffAddDataP2\\r\\n| extend Actiontype =\\\"Add\\\";\\r\\nlet DiffRemoveData = allDataRange\\r\\n | join kind = leftanti AfterData on Identity\\r\\n | extend Actiontype =\\\"Remove\\\"\\r\\n | distinct Actiontype ,Identity,DomainName,AutoForwardEnabled,WhenCreated\\r\\n | project WhenChanged=_CurrentDateB,Actiontype,Identity,DomainName,AutoForwardEnabled,WhenCreated\\r\\n ;\\r\\nlet DiffModifData = union AfterData,allDataRange\\r\\n| sort by Identity,WhenChanged asc\\r\\n| project WhenChanged,Identity,DomainName,AutoForwardEnabled,WhenCreated\\r\\n| extend Identity = iff( Identity == prev(Identity) and Identity != prev(Identity) and prev(Identity) !=\\\"\\\" , strcat(\\\"📍 \\\", Identity, \\\" (\\\",prev(Identity),\\\"->\\\", Identity,\\\" )\\\"),Identity)\\r\\n| extend DomainName = iff( Identity == prev(Identity) and DomainName != prev(DomainName) and prev(DomainName) !=\\\"\\\" , strcat(\\\"📍 \\\", DomainName, \\\" (\\\",prev(DomainName),\\\"->\\\", DomainName,\\\" )\\\"),DomainName)\\r\\n| extend AutoForwardEnabled = iff( Identity == prev(Identity) and AutoForwardEnabled != prev(AutoForwardEnabled) and prev(AutoForwardEnabled) !=\\\"\\\" , strcat(\\\"📍 \\\", AutoForwardEnabled, \\\" (\\\",prev(AutoForwardEnabled),\\\"->\\\", AutoForwardEnabled,\\\" )\\\"),AutoForwardEnabled)\\r\\n| extend ActiontypeR =iff((Identity contains \\\"📍\\\" or DomainName contains \\\"📍\\\" or AutoForwardEnabled contains \\\"📍\\\" ), i=i + 1, i)\\r\\n| extend Actiontype =iff(ActiontypeR > 0, \\\"Modif\\\", \\\"NO\\\")\\r\\n| where ActiontypeR == 1\\r\\n| project WhenChanged,Actiontype,Identity,DomainName,AutoForwardEnabled,WhenCreated\\r\\n;\\r\\nunion DiffAddData, DiffRemoveData, DiffModifData\\r\\n| extend WhenChanged = iff (Actiontype == \\\"Modif\\\", WhenChanged, iff(Actiontype == \\\"Add\\\",WhenCreated, WhenChanged))\\r\\n| extend Actiontype = case(Actiontype == \\\"Add\\\", strcat(\\\"➕ \\\", Actiontype), Actiontype == \\\"Remove\\\", strcat(\\\"➖ \\\", Actiontype), Actiontype == \\\"Modif\\\", strcat(\\\"📍 \\\", Actiontype), \\\"N/A\\\")\\r\\n| sort by WhenChanged desc \\r\\n| project\\r\\n WhenChanged,\\r\\n Actiontype,\\r\\n Identity,\\r\\n DomainName,\\r\\n AutoForwardEnabled,\\r\\n WhenCreated\",\"size\":3,\"showAnalytics\":true,\"title\":\"Display changes ( Add, Remove, modifications of parameters )\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"Identity\"],\"expandTopLevel\":true}}},\"conditionalVisibility\":{\"parameterName\":\"Compare_Collect\",\"comparison\":\"isEqualTo\",\"value\":\"True\"},\"name\":\"query - 7\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Transport\"},\"name\":\"Transport Security configuration\"}],\"fallbackResourceIds\":[\"/subscriptions/6e1d3e11-a87d-4467-bb2a-c6b2d791fc03/resourcegroups/rg_sent-soc/providers/microsoft.operationalinsights/workspaces/sent-socdev\"],\"fromTemplateId\":\"sentinel-MicrosoftExchangeSecurityReview-Online\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -1561,7 +1575,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Admin Activity - Online Workbook with template version 3.1.7", + "description": "Microsoft Exchange Admin Activity - Online Workbook with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion3')]", @@ -1579,7 +1593,7 @@ }, "properties": { "displayName": "[parameters('workbook3-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Microsoft Exchange Admin Activity\\r\\n\\r\\nThis workbook helps you visualize what is happening in your Exchange environment.\\r\\nResults removed :\\r\\n\\t- All Test-* and Set-AdServerSetting Cmdlets\\r\\n\\r\\n**Selection of an environment is unavailable. As this workbook is based on the OfficeActivity Logs (Microsoft 365 Solution) directly linked to the Microsoft Sentinel Environment, we cannot provide a view of another one.**\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"3792117c-d924-4ec7-a327-1e8d5e9f291a\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}],\"allowCustom\":true},\"value\":{\"durationMs\":14400000}},{\"id\":\"743317e2-ebcf-4958-861d-4ff97fc7cce1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"EnvironmentList\",\"label\":\"Environment\",\"type\":2,\"query\":\"OfficeActivity | where TimeGenerated {TimeRange}\\r\\n| summarize by OrganizationName\",\"isHiddenWhenLocked\":true,\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"8ac96eb3-918b-4a36-bcc4-df50d8f46175\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Help\",\"label\":\"Show Help\",\"type\":10,\"isRequired\":true,\"query\":\"{\\\"version\\\":\\\"1.0.0\\\",\\\"content\\\":\\\"[\\\\r\\\\n { \\\\\\\"value\\\\\\\": \\\\\\\"Yes\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"Yes\\\\\\\"},\\\\r\\\\n {\\\\\\\"value\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"selected\\\\\\\":true }\\\\r\\\\n]\\\\r\\\\n\\\"}\\r\\n\",\"timeContext\":{\"durationMs\":2592000000},\"queryType\":8}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"TimeRange\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"34188faf-7a02-4697-9b36-2afa986afc0f\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Cmdlet Analysis\",\"subTarget\":\"Cmdlet\",\"postText\":\"t\",\"style\":\"link\",\"icon\":\"3\",\"linkIsContextBlade\":true}]},\"name\":\"links - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Cmdlet summary\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This tab parses the events from OfficeActivity logs :\\r\\n\\r\\n- list of cmdlets\\r\\n- filter on a VIP and/or Sensitive objects (based on Watchlist \\\"Exchange VIP\\\" and \\\" Monitored Exchange Cmdlets\\\")\\r\\n- anomalies detections are based on the KQL function series_decompose_anomalies\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"CmdletGroupHelp\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"5a942eba-c991-4b84-9a94-c153bca86e12\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"VIPOnly\",\"label\":\"Show VIP Only\",\"type\":10,\"isRequired\":true,\"typeSettings\":{\"showDefault\":false},\"jsonData\":\"[\\r\\n { \\\"value\\\": \\\"True\\\", \\\"label\\\": \\\"Yes\\\"},\\r\\n { \\\"value\\\": \\\"True,False\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true }\\r\\n]\",\"timeContext\":{\"durationMs\":86400000}},{\"id\":\"83befa26-eee0-49ab-9785-72653943bc6b\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"SensitiveOnly\",\"label\":\"Sensitive CmdLet Only\",\"type\":10,\"isRequired\":true,\"typeSettings\":{\"showDefault\":false},\"jsonData\":\"[\\r\\n { \\\"value\\\": \\\"True\\\", \\\"label\\\": \\\"Yes\\\" },\\r\\n { \\\"value\\\": \\\"True,False\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true }\\r\\n]\\r\\n\",\"timeContext\":{\"durationMs\":86400000}}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 0\"},{\"type\":1,\"content\":{\"json\":\"This section show all the Cmdlets executed in the selected time range. Possible filters are: \\r\\n- **VIP Only selected** Cmdlets used against VIP objects (based on the \\\"Exchange VIP\\\" watchlist)\\r\\n- **Sensitive Cmdlets** Cmdlets considered as Sensitive (based on the \\\"Monitored Exchange Cmdlets\\\" watchlist)\\r\\n\\r\\nThese informations can be useful to detect unexpected behaviors or to determine what are the action performed by the accounts (ie. service accounts).\\r\\n\\r\\nℹ️ It is recommended to delegated only the necessary privileges to an account.\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"CmdtListHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n| summarize count() by CmdletName\\r\\n| sort by count_\",\"size\":2,\"showAnalytics\":true,\"title\":\"List of all executed cmdlets during the last 90 days (based on Sentinel retention)\",\"exportFieldName\":\"Cmdlet\",\"exportParameterName\":\"CmdletFilter\",\"exportDefaultValue\":\"\\\"\\\"\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"CmdletName\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"count_\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}},\"showBorder\":false},\"graphSettings\":{\"type\":0,\"topContent\":{\"columnMatch\":\"Cmdlet\",\"formatter\":1},\"centerContent\":{\"columnMatch\":\"count_\",\"formatter\":1,\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}}},\"chartSettings\":{\"createOtherGroup\":20}},\"customWidth\":\"45\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n| summarize count() by CmdletName\\r\\n| join kind=leftouter ( MESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n | make-series Count=count() on TimeGenerated from ago(30d) to now() step 1d by CmdletName\\r\\n | extend Anomalies=series_decompose_anomalies(Count)\\r\\n) on CmdletName\\r\\n| project CmdletName, Total=count_, Count, Anomalies\\r\\n| sort by Total\",\"size\":2,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Cmdlet\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"31.5ch\"}},{\"columnMatch\":\"Total\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"9.3ch\"}},{\"columnMatch\":\"Count\",\"formatter\":21,\"formatOptions\":{\"palette\":\"blue\",\"customColumnWidthSetting\":\"330px\"},\"tooltipFormat\":{\"tooltip\":\"Trend\"}},{\"columnMatch\":\"Anomalies\",\"formatter\":9,\"formatOptions\":{\"palette\":\"redBright\",\"customColumnWidthSetting\":\"330px\"},\"tooltipFormat\":{\"tooltip\":\"Anomalies\"}}],\"rowLimit\":10000,\"filter\":true,\"labelSettings\":[{\"columnId\":\"Count\",\"label\":\"Count for the last 30 days\"}]}},\"customWidth\":\"55\",\"name\":\"CmdletTrends\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet: string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\", ignoreFirstRecord=true)\\r\\n | project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n| summarize Total = count() by Caller\\r\\n| join kind=leftouter ( MESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n | make-series Count=count() on TimeGenerated from ago(30d) to now() step 1d by Caller\\r\\n | extend Anomalies=series_decompose_anomalies(Count)\\r\\n) on Caller\\r\\n| project Caller, Total, Count, Anomalies\\r\\n| sort by Total desc\",\"size\":1,\"showAnalytics\":true,\"exportFieldName\":\"Caller\",\"exportParameterName\":\"CallerFilter\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Caller\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"70ch\"}},{\"columnMatch\":\"Total\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\",\"customColumnWidthSetting\":\"125px\"}},{\"columnMatch\":\"Count\",\"formatter\":21,\"formatOptions\":{\"palette\":\"blue\",\"customColumnWidthSetting\":\"300px\"},\"tooltipFormat\":{\"tooltip\":\"Trend\"}},{\"columnMatch\":\"Anomalies\",\"formatter\":10,\"formatOptions\":{\"palette\":\"redBright\",\"customColumnWidthSetting\":\"300px\"},\"tooltipFormat\":{\"tooltip\":\"Anomalies\"}}],\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_bar_Total_1\",\"sortOrder\":2}],\"labelSettings\":[{\"columnId\":\"Count\",\"label\":\"Count for the last 30 days\"}]},\"sortBy\":[{\"itemKey\":\"$gen_bar_Total_1\",\"sortOrder\":2}],\"chartSettings\":{\"createOtherGroup\":20}},\"name\":\"query - 4\"},{\"type\":1,\"content\":{\"json\":\"## List of Cmdlets\\r\\n\\r\\nBy default all accounts found in the log are displayed.\\r\\n\\r\\nSelect an caller, to display all Cmdlets launched by this administrator\\r\\n\\r\\n> **Legend** \\r\\n> \\r\\n> 👑 VIP user \\r\\n> 💥 Sensitive action\\r\\n\\r\\nIf needed, select an item in the dropdownlist. Dropdownlist are independent.\"},\"name\":\"text - 3\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"008273d1-a013-4d86-9e23-499e5175a85e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"CallerFilter\",\"label\":\"Caller\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| distinct Caller\\r\\n| extend Caller = replace_string(Caller, '\\\\\\\\', '\\\\\\\\\\\\\\\\')\\r\\n| sort by Caller asc\",\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"defaultValue\":\"value::all\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"21bd4e45-65ca-4b9b-a19c-177d6b37d807\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TargetObjectFilter\",\"label\":\"Target Object\",\"type\":2,\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where Caller in ({CallerFilter})\\r\\n| distinct TargetObject\\r\\n| sort by TargetObject asc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"9e93d5c3-0fcb-4ece-b2a0-fc3ff44a0b04\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"CmdletFilter\",\"label\":\"Cmdlet Filter\",\"type\":2,\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where Caller in ({CallerFilter})\\r\\n| distinct CmdletName\\r\\n| sort by CmdletName asc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 8\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet: string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\", ignoreFirstRecord=true)\\r\\n | project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n| where (Caller in ({CallerFilter}) or Caller == \\\"ALL\\\") and TargetObject contains \\\"{TargetObjectFilter}\\\" and CmdletName contains \\\"{CmdletFilter}\\\"\\r\\n and TargetObject contains \\\"\\\"\\r\\n and CmdletName contains \\\"\\\"\\r\\n| extend TargetObject = iif(IsVIP == true and TargetObject !=\\\"\\\" , strcat(\\\"👑 \\\",TargetObject), TargetObject )\\r\\n| extend Cmdlet = iif(IsSensitive == true and TargetObject !=\\\"\\\", strcat(\\\"💥 \\\",CmdletName), CmdletName )\\r\\n| extend IsVIP = iif(IsVIP == true and TargetObject !=\\\"\\\" , strcat(\\\"👑 \\\",tostring(IsVIP)), tostring(IsVIP ))\\r\\n| project TimeGenerated, Caller, TargetObject, Cmdlet, CmdletParameters\\r\\n| sort by TimeGenerated desc\",\"size\":2,\"showAnalytics\":true,\"title\":\"History\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"ActualCmdLet\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"120ch\"}}],\"rowLimit\":10000,\"filter\":true}},\"name\":\"query - 5\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Cmdlet\"},\"name\":\"Cmdlet Group\"}],\"fromTemplateId\":\"sentinel-MicrosoftExchangeSecurityAdminActivity-Online\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Microsoft Exchange Admin Activity\\r\\n\\r\\nThis workbook helps you visualize what is happening in your Exchange environment.\\r\\nResults removed :\\r\\n\\t- All Test-* and Set-AdServerSetting Cmdlets\\r\\n\\r\\n**Selection of an environment is unavailable. As this workbook is based on the OfficeActivity Logs (Microsoft 365 Solution) directly linked to the Microsoft Sentinel Environment, we cannot provide a view of another one.**\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"3792117c-d924-4ec7-a327-1e8d5e9f291a\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}],\"allowCustom\":true},\"value\":{\"durationMs\":14400000}},{\"id\":\"743317e2-ebcf-4958-861d-4ff97fc7cce1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"EnvironmentList\",\"label\":\"Environment\",\"type\":2,\"query\":\"OfficeActivity | where TimeGenerated {TimeRange}\\r\\n| summarize by OrganizationName\",\"isHiddenWhenLocked\":true,\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"8ac96eb3-918b-4a36-bcc4-df50d8f46175\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Help\",\"label\":\"Show Help\",\"type\":10,\"isRequired\":true,\"query\":\"{\\\"version\\\":\\\"1.0.0\\\",\\\"content\\\":\\\"[\\\\r\\\\n { \\\\\\\"value\\\\\\\": \\\\\\\"Yes\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"Yes\\\\\\\"},\\\\r\\\\n {\\\\\\\"value\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"label\\\\\\\": \\\\\\\"No\\\\\\\", \\\\\\\"selected\\\\\\\":true }\\\\r\\\\n]\\\\r\\\\n\\\"}\\r\\n\",\"timeContext\":{\"durationMs\":2592000000},\"queryType\":8}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"TimeRange\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"34188faf-7a02-4697-9b36-2afa986afc0f\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Cmdlet Analysis\",\"subTarget\":\"Cmdlet\",\"postText\":\"t\",\"style\":\"link\",\"icon\":\"3\",\"linkIsContextBlade\":true}]},\"name\":\"links - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Cmdlet summary\",\"items\":[{\"type\":1,\"content\":{\"json\":\"This tab parses the events from OfficeActivity logs :\\r\\n\\r\\n- list of cmdlets\\r\\n- filter on a VIP and/or Sensitive objects (based on Watchlist \\\"Exchange VIP\\\" and \\\" Monitored Exchange Cmdlets\\\")\\r\\n- anomalies detections are based on the KQL function series_decompose_anomalies\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"CmdletGroupHelp\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"5a942eba-c991-4b84-9a94-c153bca86e12\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"VIPOnly\",\"label\":\"Show VIP Only\",\"type\":10,\"isRequired\":true,\"typeSettings\":{\"showDefault\":false},\"jsonData\":\"[\\r\\n { \\\"value\\\": \\\"True\\\", \\\"label\\\": \\\"Yes\\\"},\\r\\n { \\\"value\\\": \\\"True,False\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true }\\r\\n]\",\"timeContext\":{\"durationMs\":86400000}},{\"id\":\"83befa26-eee0-49ab-9785-72653943bc6b\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"SensitiveOnly\",\"label\":\"Sensitive CmdLet Only\",\"type\":10,\"isRequired\":true,\"typeSettings\":{\"showDefault\":false},\"jsonData\":\"[\\r\\n { \\\"value\\\": \\\"True\\\", \\\"label\\\": \\\"Yes\\\" },\\r\\n { \\\"value\\\": \\\"True,False\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true }\\r\\n]\\r\\n\",\"timeContext\":{\"durationMs\":86400000}}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 0\"},{\"type\":1,\"content\":{\"json\":\"This section show all the Cmdlets executed in the selected time range. Possible filters are: \\r\\n- **VIP Only selected** Cmdlets used against VIP objects (based on the \\\"Exchange VIP\\\" watchlist)\\r\\n- **Sensitive Cmdlets** Cmdlets considered as Sensitive (based on the \\\"Monitored Exchange Cmdlets\\\" watchlist)\\r\\n\\r\\nThese informations can be useful to detect unexpected behaviors or to determine what are the action performed by the accounts (ie. service accounts).\\r\\n\\r\\nℹ️ It is recommended to delegated only the necessary privileges to an account.\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"CmdtListHelp\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n| summarize count() by CmdletName\\r\\n| sort by count_\",\"size\":2,\"showAnalytics\":true,\"title\":\"List of all executed cmdlets during the last 90 days (based on Sentinel retention)\",\"exportFieldName\":\"Cmdlet\",\"exportParameterName\":\"CmdletFilter\",\"exportDefaultValue\":\"\\\"\\\"\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"CmdletName\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"count_\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}},\"showBorder\":false},\"graphSettings\":{\"type\":0,\"topContent\":{\"columnMatch\":\"Cmdlet\",\"formatter\":1},\"centerContent\":{\"columnMatch\":\"count_\",\"formatter\":1,\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}}},\"chartSettings\":{\"createOtherGroup\":20}},\"customWidth\":\"45\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n| summarize count() by CmdletName\\r\\n| join kind=leftouter ( MESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n | make-series Count=count() on TimeGenerated from ago(30d) to now() step 1d by CmdletName\\r\\n | extend Anomalies=series_decompose_anomalies(Count)\\r\\n) on CmdletName\\r\\n| project CmdletName, Total=count_, Count, Anomalies\\r\\n| sort by Total\",\"size\":2,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Cmdlet\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"31.5ch\"}},{\"columnMatch\":\"Total\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"9.3ch\"}},{\"columnMatch\":\"Count\",\"formatter\":21,\"formatOptions\":{\"palette\":\"blue\",\"customColumnWidthSetting\":\"330px\"},\"tooltipFormat\":{\"tooltip\":\"Trend\"}},{\"columnMatch\":\"Anomalies\",\"formatter\":9,\"formatOptions\":{\"palette\":\"redBright\",\"customColumnWidthSetting\":\"330px\"},\"tooltipFormat\":{\"tooltip\":\"Anomalies\"}}],\"rowLimit\":10000,\"filter\":true,\"labelSettings\":[{\"columnId\":\"Count\",\"label\":\"Count for the last 30 days\"}]}},\"customWidth\":\"55\",\"name\":\"CmdletTrends\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet: string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\", ignoreFirstRecord=true)\\r\\n | project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n| summarize Total = count() by Caller\\r\\n| join kind=leftouter ( MESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n | make-series Count=count() on TimeGenerated from ago(30d) to now() step 1d by Caller\\r\\n | extend Anomalies=series_decompose_anomalies(Count)\\r\\n) on Caller\\r\\n| project Caller, Total, Count, Anomalies\\r\\n| sort by Total desc\",\"size\":1,\"showAnalytics\":true,\"exportFieldName\":\"Caller\",\"exportParameterName\":\"CallerFilter\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Caller\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"70ch\"}},{\"columnMatch\":\"Total\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\",\"customColumnWidthSetting\":\"125px\"}},{\"columnMatch\":\"Count\",\"formatter\":21,\"formatOptions\":{\"palette\":\"blue\",\"customColumnWidthSetting\":\"300px\"},\"tooltipFormat\":{\"tooltip\":\"Trend\"}},{\"columnMatch\":\"Anomalies\",\"formatter\":10,\"formatOptions\":{\"palette\":\"redBright\",\"customColumnWidthSetting\":\"300px\"},\"tooltipFormat\":{\"tooltip\":\"Anomalies\"}}],\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_bar_Total_1\",\"sortOrder\":2}],\"labelSettings\":[{\"columnId\":\"Count\",\"label\":\"Count for the last 30 days\"}]},\"sortBy\":[{\"itemKey\":\"$gen_bar_Total_1\",\"sortOrder\":2}],\"chartSettings\":{\"createOtherGroup\":20}},\"name\":\"query - 4\"},{\"type\":1,\"content\":{\"json\":\"## List of Cmdlets\\r\\n\\r\\nBy default all accounts found in the log are displayed.\\r\\n\\r\\nSelect an caller, to display all Cmdlets launched by this administrator\\r\\n\\r\\n> **Legend** \\r\\n> \\r\\n> 👑 VIP user \\r\\n> 💥 Sensitive action\\r\\n\\r\\nIf needed, select an item in the dropdownlist. Dropdownlist are independent.\"},\"name\":\"text - 3\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"008273d1-a013-4d86-9e23-499e5175a85e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"CallerFilter\",\"label\":\"Caller\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| distinct Caller\\r\\n| extend Caller = replace_string(Caller, '\\\\\\\\', '\\\\\\\\\\\\\\\\')\\r\\n| sort by Caller asc\",\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"defaultValue\":\"value::all\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"21bd4e45-65ca-4b9b-a19c-177d6b37d807\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TargetObjectFilter\",\"label\":\"Target Object\",\"type\":2,\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where Caller in ({CallerFilter})\\r\\n| distinct TargetObject\\r\\n| sort by TargetObject asc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"9e93d5c3-0fcb-4ece-b2a0-fc3ff44a0b04\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"CmdletFilter\",\"label\":\"Cmdlet Filter\",\"type\":2,\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where Caller in ({CallerFilter})\\r\\n| distinct CmdletName\\r\\n| sort by CmdletName asc\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 8\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet: string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\", ignoreFirstRecord=true)\\r\\n | project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where IsVIP in ({VIPOnly})\\r\\n| where IsSensitive in ({SensitiveOnly})\\r\\n| where (Caller in ({CallerFilter}) or Caller == \\\"ALL\\\") and TargetObject contains \\\"{TargetObjectFilter}\\\" and CmdletName contains \\\"{CmdletFilter}\\\"\\r\\n and TargetObject contains \\\"\\\"\\r\\n and CmdletName contains \\\"\\\"\\r\\n| extend TargetObject = iif(IsVIP == true and TargetObject !=\\\"\\\" , strcat(\\\"👑 \\\",TargetObject), TargetObject )\\r\\n| extend Cmdlet = iif(IsSensitive == true and TargetObject !=\\\"\\\", strcat(\\\"💥 \\\",CmdletName), CmdletName )\\r\\n| extend IsVIP = iif(IsVIP == true and TargetObject !=\\\"\\\" , strcat(\\\"👑 \\\",tostring(IsVIP)), tostring(IsVIP ))\\r\\n| project TimeGenerated, Caller, TargetObject, Cmdlet, CmdletParameters\\r\\n| sort by TimeGenerated desc\",\"size\":2,\"showAnalytics\":true,\"title\":\"History\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"ActualCmdLet\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"120ch\"}}],\"rowLimit\":10000,\"filter\":true}},\"name\":\"query - 5\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Cmdlet\"},\"name\":\"Cmdlet Group\"}],\"fallbackResourceIds\":[\"/subscriptions/6e1d3e11-a87d-4467-bb2a-c6b2d791fc03/resourcegroups/rg_sent-soc/providers/microsoft.operationalinsights/workspaces/sent-socdev\"],\"fromTemplateId\":\"sentinel-MicrosoftExchangeSecurityAdminActivity-Online\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -1648,7 +1662,7 @@ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" ], "properties": { - "description": "Microsoft Exchange Search AdminAuditLog - Online Workbook with template version 3.1.7", + "description": "Microsoft Exchange Search AdminAuditLog - Online Workbook with template version 4.0.0", "mainTemplate": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "[variables('workbookVersion4')]", @@ -1666,7 +1680,7 @@ }, "properties": { "displayName": "[parameters('workbook4-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"## Admin Audit Log\\r\\n\\r\\n** This workbook requires Option 1** (upload of the OfficeActivity logs)\\r\\n\\r\\n**Selection of an environment is unavailable. As this workbook is based on the OfficeActivity Logs (Microsoft 365 Solution) directly linked to the Microsoft Sentinel Environment, we cannot provide a view of another one.**\"},\"name\":\"text - 6\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"79f1e435-df12-4c83-9967-501ab5f6ad6a\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":86400000}},{\"id\":\"59486bcb-db99-43b3-97dc-a63b271a91d1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"EnvironmentList\",\"label\":\"Environment\",\"type\":2,\"query\":\"OfficeActivity | where TimeGenerated {TimeRange}\\r\\n | summarize by OrganizationName\",\"isHiddenWhenLocked\":true,\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"079b3cc5-dab3-4d38-b4d0-71101802949d\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Help\",\"label\":\"Show Help\",\"type\":10,\"isRequired\":true,\"typeSettings\":{\"showDefault\":false},\"jsonData\":\"[\\r\\n { \\\"value\\\": \\\"Yes\\\", \\\"label\\\": \\\"Yes\\\"},\\r\\n {\\\"value\\\": \\\"No\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true }\\r\\n]\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 4\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"9d830b00-95f4-4fd5-8cfb-95c2e63f5d0b\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Cmdlets Analysis\",\"subTarget\":\"CmdletAna\",\"style\":\"link\"},{\"id\":\"944a83ef-377f-4374-83e8-46816b6ce570\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Admin Audit Log - All Admins\",\"subTarget\":\"AllAAL\",\"style\":\"link\"},{\"id\":\"cdab541f-8d91-4882-ba46-7c04cdff257b\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Workbook Help\",\"subTarget\":\"Start\",\"style\":\"link\"}]},\"name\":\"links - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Global Admin Audit Log Search\",\"items\":[{\"type\":1,\"content\":{\"json\":\"If needed, select an item in the dropdownlist. Dropdownlist are independent.\"},\"name\":\"text - 4\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"e100ee8b-d63b-4c49-9004-6555b56051aa\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Admin\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| extend Caller = replace_string(Caller, '\\\\\\\\', '\\\\\\\\\\\\\\\\')\\r\\n| extend admin = Caller\\r\\n| distinct admin\\r\\n\\r\\n\",\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"defaultValue\":\"value::all\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"0d7c1223-d108-4d10-bb24-50891a3415fd\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"CmdLet\",\"type\":2,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where Caller in ({Admin})\\r\\n| distinct CmdletName\",\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"defaultValue\":\"value::all\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"**How to understand the data**\\r\\n\\r\\nThese information are extracted from the OfficeActivity logs.\\r\\n\\r\\nEach entry is analyzed regarding the following conditions :\\r\\n\\r\\n - Check if the Target Object is a VIP. The VIP list is based on the watchlist \\\"Exchange VIP\\\".\\r\\n\\r\\n - Check if the Cdmlet is a Sensitive Cmdlet. The Sensitive Cmdlet list is based on the watchlist \\\"Monitored Exchange Cmdlets\\\". \\r\\n - This list contains the list of Cmdlet that are considered as Sensitive. \\r\\n - Some Cmdlet will be considered as Sensitive only if some specific parameters defined in the \\\"Monitored Exchange Cmdlets\\\" watchlist are used.\\r\\n\\r\\nColumn explainatations : \\r\\n - Caller : Named of the Administrators that used this cmdlet\\r\\n - TargetObject : Object modified by the cmdlet\\r\\n - IsVIP : If the Target Object part of the \\\"Exchange VIP\\\" watchlist\\r\\n - Cmdlet : Name of the cmdlet that was used\\r\\n - CmdletParameters : Cmdlet parameters used with the command\\r\\n - IsSensitive :\\r\\n - true : This cmdlet is Sensitive because it was part of the list of the \\\"Monitored Exchange Cmdlets\\\" watchlist and Sensitive parameters have been used for cmdlet with specifc sensitive parameters \\r\\n\\r\\n\"},\"showPin\":false,\"name\":\"text - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"group - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where Caller in ({Admin}) and CmdletName in ({CmdLet})\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| extend TargetObject = iif(IsVIP == true and TargetObject !=\\\"\\\" , strcat(\\\"👑 \\\",TargetObject), TargetObject )\\r\\n| extend CmdletName = iif(IsSensitive == true and TargetObject !=\\\"\\\", strcat(\\\"💥 \\\",CmdletName), CmdletName )\\r\\n| extend IsVIP = iif(IsVIP == true and TargetObject !=\\\"\\\" , strcat(\\\"👑 \\\",tostring(IsVIP)), tostring(IsVIP ))\\r\\n| extend IsSensitive = iif(IsSensitive == true and TargetObject !=\\\"\\\", strcat(\\\"💥 \\\",tostring(IsSenstiveCmdlet)), tostring(IsSenstiveCmdlet))\\r\\n| project TimeGenerated, Caller,IsVIP,TargetObject,IsSensitive,CmdletName,CmdletParameters\\r\\n| sort by TimeGenerated desc\",\"size\":0,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"TimeGenerated\",\"sortOrder\":2}]},\"sortBy\":[{\"itemKey\":\"TimeGenerated\",\"sortOrder\":2}]},\"name\":\"query - 2\",\"styleSettings\":{\"showBorder\":true}}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"AllAAL\"},\"name\":\"Global Admin Audit Log\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Analysis of Administrators actions\",\"items\":[{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Total Cmdlets for the Time Range\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| extend Caller\\r\\n| extend CmdletName\\r\\n| summarize Count=count() by CmdletName\",\"size\":2,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"chartSettings\":{\"createOtherGroup\":10}},\"customWidth\":\"50\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| extend Account = Caller\\r\\n| summarize Count=dcount(CmdletName) by Account,CmdletName\",\"size\":2,\"showAnalytics\":true,\"title\":\"Total Unique Cmdlet per Account for the Time Range\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"tileSettings\":{\"showBorder\":false,\"titleContent\":{\"columnMatch\":\"Account\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"Count\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}}}},\"customWidth\":\"50\",\"name\":\"query - 1\"}]},\"name\":\"group - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| summarize Count=count() by CmdletName\\r\\n| sort by CmdletName asc\",\"size\":0,\"showAnalytics\":true,\"title\":\"Total List of Cmdlets\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"filter\":true}},\"customWidth\":\"50\",\"name\":\"query - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://aka.ms/ExcludedCmdletWatchlist\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| extend Account = Caller\\r\\n| summarize Count=count() by CmdletName, Account\\r\\n| sort by Count asc\",\"size\":0,\"showAnalytics\":true,\"title\":\"List of Cmdlet per Account\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true}},\"customWidth\":\"50\",\"name\":\"query - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Explanations\",\"expandable\":true,\"items\":[{\"type\":1,\"content\":{\"json\":\"This section displayed the list of Cmdlet used in your environment for the defined period of time with the number of time they have been used.\"},\"name\":\"text - 0\"}]},\"customWidth\":\"50\",\"name\":\"group - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Explanations\",\"expandable\":true,\"items\":[{\"type\":1,\"content\":{\"json\":\"This section will display the list of Cmdlet launch by Administrators for the defined period of time and the number of time they have been used\"},\"name\":\"text - 0\"}]},\"customWidth\":\"50\",\"name\":\"group - 3\"}]},\"name\":\"Result Analysis\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"CmdletAna\"},\"name\":\"Analysis of actions performed\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Workbook goals\\r\\nThe goals of this workbook is to allow search in the Exchange Admin Audit log.\\r\\n\\r\\nThe source of this workbook is not an export of the Admin Audit log mailbox but an export of the MSExchange Management for each Exchange servers.\\r\\n\\r\\nIf the Admin Audit Log is bypassed, the information won't be displayed in this workbook as there is no method to track this data.\\r\\n\\r\\n## Tabs\\r\\n\\r\\nLet quicly review the content of each tab\\r\\n\\r\\n### Cmdlets Analysis\\r\\n\\r\\nThis tab will show for the defined time range :\\r\\n - A summary of all cmdets used\\r\\n\\r\\n - A summary of all cmdlets used by each Account\\r\\n\\r\\n### Global Admin Audit Log\\r\\n\\r\\nThis tab allow to globally search in the exported Admin Audit log content.\\r\\n\\r\\nWhen Sensitive Cmdlets and/or Sensitive parameters are used, specific informations will be displayed.\\r\\n\\r\\nWhen VIP user are manipulated, specific informations will be displayed.\\r\\n\\r\\nFor more informations on how to understand each Column, refer to \\\"How to understand the data\\\"\\r\\n\\r\\n\\r\\n### AdminAuditLog for Org Mgmt\\r\\n\\r\\nThis tab allow to globally search in the exported Admin Audit log content for only account members on the Organization Management groups.\\r\\n\\r\\nWhen Sensitive Cmdlets and/or Sensitive parameters are used, specific informations will be displayed.\\r\\n\\r\\nWhen VIP user are manipulated, specific informations will be displayed.\\r\\n\\r\\nFor more informations on how to understand each Column, refer to \\\"How to understand the data\\\"\"},\"name\":\"text - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Start\"},\"name\":\"group - 5\"}],\"fromTemplateId\":\"sentinel-MicrosoftExchangeSearchAdminAuditLog-Online\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"## Admin Audit Log\\r\\n\\r\\n** This workbook requires Option 1** (upload of the OfficeActivity logs)\\r\\n\\r\\n**Selection of an environment is unavailable. As this workbook is based on the OfficeActivity Logs (Microsoft 365 Solution) directly linked to the Microsoft Sentinel Environment, we cannot provide a view of another one.**\"},\"name\":\"text - 6\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"79f1e435-df12-4c83-9967-501ab5f6ad6a\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000},{\"durationMs\":5184000000},{\"durationMs\":7776000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":86400000}},{\"id\":\"59486bcb-db99-43b3-97dc-a63b271a91d1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"EnvironmentList\",\"label\":\"Environment\",\"type\":2,\"query\":\"OfficeActivity | where TimeGenerated {TimeRange}\\r\\n | summarize by OrganizationName\",\"isHiddenWhenLocked\":true,\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"079b3cc5-dab3-4d38-b4d0-71101802949d\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Help\",\"label\":\"Show Help\",\"type\":10,\"isRequired\":true,\"typeSettings\":{\"showDefault\":false},\"jsonData\":\"[\\r\\n { \\\"value\\\": \\\"Yes\\\", \\\"label\\\": \\\"Yes\\\"},\\r\\n {\\\"value\\\": \\\"No\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true }\\r\\n]\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 4\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"9d830b00-95f4-4fd5-8cfb-95c2e63f5d0b\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Cmdlets Analysis\",\"subTarget\":\"CmdletAna\",\"style\":\"link\"},{\"id\":\"944a83ef-377f-4374-83e8-46816b6ce570\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Admin Audit Log - All Admins\",\"subTarget\":\"AllAAL\",\"style\":\"link\"},{\"id\":\"cdab541f-8d91-4882-ba46-7c04cdff257b\",\"cellValue\":\"selected\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Workbook Help\",\"subTarget\":\"Start\",\"style\":\"link\"}]},\"name\":\"links - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Global Admin Audit Log Search\",\"items\":[{\"type\":1,\"content\":{\"json\":\"If needed, select an item in the dropdownlist. Dropdownlist are independent.\"},\"name\":\"text - 4\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"e100ee8b-d63b-4c49-9004-6555b56051aa\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Admin\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| extend Caller = replace_string(Caller, '\\\\\\\\', '\\\\\\\\\\\\\\\\')\\r\\n| extend admin = Caller\\r\\n| distinct admin\\r\\n\\r\\n\",\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"defaultValue\":\"value::all\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"0d7c1223-d108-4d10-bb24-50891a3415fd\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"CmdLet\",\"type\":2,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| where Caller in ({Admin})\\r\\n| distinct CmdletName\",\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"defaultValue\":\"value::all\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"**How to understand the data**\\r\\n\\r\\nThese information are extracted from the OfficeActivity logs.\\r\\n\\r\\nEach entry is analyzed regarding the following conditions :\\r\\n\\r\\n - Check if the Target Object is a VIP. The VIP list is based on the watchlist \\\"Exchange VIP\\\".\\r\\n\\r\\n - Check if the Cdmlet is a Sensitive Cmdlet. The Sensitive Cmdlet list is based on the watchlist \\\"Monitored Exchange Cmdlets\\\". \\r\\n - This list contains the list of Cmdlet that are considered as Sensitive. \\r\\n - Some Cmdlet will be considered as Sensitive only if some specific parameters defined in the \\\"Monitored Exchange Cmdlets\\\" watchlist are used.\\r\\n\\r\\nColumn explainatations : \\r\\n - Caller : Named of the Administrators that used this cmdlet\\r\\n - TargetObject : Object modified by the cmdlet\\r\\n - IsVIP : If the Target Object part of the \\\"Exchange VIP\\\" watchlist\\r\\n - Cmdlet : Name of the cmdlet that was used\\r\\n - CmdletParameters : Cmdlet parameters used with the command\\r\\n - IsSensitive :\\r\\n - true : This cmdlet is Sensitive because it was part of the list of the \\\"Monitored Exchange Cmdlets\\\" watchlist and Sensitive parameters have been used for cmdlet with specifc sensitive parameters \\r\\n\\r\\n\"},\"showPin\":false,\"name\":\"text - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"group - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where Caller in ({Admin}) and CmdletName in ({CmdLet})\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| extend TargetObject = iif(IsVIP == true and TargetObject !=\\\"\\\" , strcat(\\\"👑 \\\",TargetObject), TargetObject )\\r\\n| extend CmdletName = iif(IsSensitive == true and TargetObject !=\\\"\\\", strcat(\\\"💥 \\\",CmdletName), CmdletName )\\r\\n| extend IsVIP = iif(IsVIP == true and TargetObject !=\\\"\\\" , strcat(\\\"👑 \\\",tostring(IsVIP)), tostring(IsVIP ))\\r\\n| extend IsSensitive = iif(IsSensitive == true and TargetObject !=\\\"\\\", strcat(\\\"💥 \\\",tostring(IsSenstiveCmdlet)), tostring(IsSenstiveCmdlet))\\r\\n| project TimeGenerated, Caller,IsVIP,TargetObject,IsSensitive,CmdletName,CmdletParameters\\r\\n| sort by TimeGenerated desc\",\"size\":0,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"TimeGenerated\",\"sortOrder\":2}]},\"sortBy\":[{\"itemKey\":\"TimeGenerated\",\"sortOrder\":2}]},\"name\":\"query - 2\",\"styleSettings\":{\"showBorder\":true}}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"AllAAL\"},\"name\":\"Global Admin Audit Log\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Analysis of Administrators actions\",\"items\":[{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Total Cmdlets for the Time Range\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| extend Caller\\r\\n| extend CmdletName\\r\\n| summarize Count=count() by CmdletName\",\"size\":2,\"showAnalytics\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"chartSettings\":{\"createOtherGroup\":10}},\"customWidth\":\"50\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| extend Account = Caller\\r\\n| summarize Count=dcount(CmdletName) by Account,CmdletName\",\"size\":2,\"showAnalytics\":true,\"title\":\"Total Unique Cmdlet per Account for the Time Range\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"tileSettings\":{\"showBorder\":false,\"titleContent\":{\"columnMatch\":\"Account\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"Count\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"maximumSignificantDigits\":3,\"maximumFractionDigits\":2}}}}},\"customWidth\":\"50\",\"name\":\"query - 1\"}]},\"name\":\"group - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| summarize Count=count() by CmdletName\\r\\n| sort by CmdletName asc\",\"size\":0,\"showAnalytics\":true,\"title\":\"Total List of Cmdlets\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"filter\":true}},\"customWidth\":\"50\",\"name\":\"query - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ExcludedCmdlet = externaldata (Cmdlet:string)[h\\\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\\\"]with(format=\\\"csv\\\",ignoreFirstRecord=true)| project Cmdlet;\\r\\nMESOfficeActivityLogs\\r\\n| where TimeGenerated {TimeRange}\\r\\n| where CmdletName !in (ExcludedCmdlet)\\r\\n| extend Account = Caller\\r\\n| summarize Count=count() by CmdletName, Account\\r\\n| sort by Count asc\",\"size\":0,\"showAnalytics\":true,\"title\":\"List of Cmdlet per Account\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":10000,\"filter\":true}},\"customWidth\":\"50\",\"name\":\"query - 1\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Explanations\",\"expandable\":true,\"items\":[{\"type\":1,\"content\":{\"json\":\"This section displayed the list of Cmdlet used in your environment for the defined period of time with the number of time they have been used.\"},\"name\":\"text - 0\"}]},\"customWidth\":\"50\",\"name\":\"group - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"title\":\"Explanations\",\"expandable\":true,\"items\":[{\"type\":1,\"content\":{\"json\":\"This section will display the list of Cmdlet launch by Administrators for the defined period of time and the number of time they have been used\"},\"name\":\"text - 0\"}]},\"customWidth\":\"50\",\"name\":\"group - 3\"}]},\"name\":\"Result Analysis\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"CmdletAna\"},\"name\":\"Analysis of actions performed\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"# Workbook goals\\r\\nThe goals of this workbook is to allow search in the Exchange Admin Audit log.\\r\\n\\r\\nThe source of this workbook is not an export of the Admin Audit log mailbox but an export of the MSExchange Management for each Exchange servers.\\r\\n\\r\\nIf the Admin Audit Log is bypassed, the information won't be displayed in this workbook as there is no method to track this data.\\r\\n\\r\\n## Tabs\\r\\n\\r\\nLet quicly review the content of each tab\\r\\n\\r\\n### Cmdlets Analysis\\r\\n\\r\\nThis tab will show for the defined time range :\\r\\n - A summary of all cmdets used\\r\\n\\r\\n - A summary of all cmdlets used by each Account\\r\\n\\r\\n### Global Admin Audit Log\\r\\n\\r\\nThis tab allow to globally search in the exported Admin Audit log content.\\r\\n\\r\\nWhen Sensitive Cmdlets and/or Sensitive parameters are used, specific informations will be displayed.\\r\\n\\r\\nWhen VIP user are manipulated, specific informations will be displayed.\\r\\n\\r\\nFor more informations on how to understand each Column, refer to \\\"How to understand the data\\\"\\r\\n\\r\\n\\r\\n### AdminAuditLog for Org Mgmt\\r\\n\\r\\nThis tab allow to globally search in the exported Admin Audit log content for only account members on the Organization Management groups.\\r\\n\\r\\nWhen Sensitive Cmdlets and/or Sensitive parameters are used, specific informations will be displayed.\\r\\n\\r\\nWhen VIP user are manipulated, specific informations will be displayed.\\r\\n\\r\\nFor more informations on how to understand each Column, refer to \\\"How to understand the data\\\"\"},\"name\":\"text - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"selected\",\"comparison\":\"isEqualTo\",\"value\":\"Start\"},\"name\":\"group - 5\"}],\"fallbackResourceIds\":[\"/subscriptions/6e1d3e11-a87d-4467-bb2a-c6b2d791fc03/resourcegroups/rg_sent-soc/providers/microsoft.operationalinsights/workspaces/sent-socdev\"],\"fromTemplateId\":\"sentinel-MicrosoftExchangeSearchAdminAuditLog-Online\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -1749,7 +1763,7 @@ "apiVersion": "2023-04-01-preview", "location": "[parameters('workspace-location')]", "properties": { - "version": "3.1.7", + "version": "4.0.0", "kind": "Solution", "contentSchemaVersion": "3.0.0", "displayName": "Microsoft Exchange Security - Exchange Online", @@ -1831,7 +1845,7 @@ { "kind": "Watchlist", "contentId": "[variables('_Exchange Online VIP')]", - "version": "3.1.7" + "version": "4.0.0" } ] }, diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/ReleaseNotes.md b/Solutions/Microsoft Exchange Security - Exchange Online/ReleaseNotes.md index f302f63832c..f973e5e9950 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/ReleaseNotes.md +++ b/Solutions/Microsoft Exchange Security - Exchange Online/ReleaseNotes.md @@ -1,5 +1,6 @@ | **Version** | **Date Modified (DD-MM-YYYY)** | **Change History** | |-------------|--------------------------------|---------------------------------------------| +| 4.0.0 | 29-07-2026 | Migrate from Log Analytics API to Azure Monitor API | | 3.1.7 | 26-03-2025 | Update documentation link to new repository | | 3.1.6 | 30-08-2024 | Correct bug on LasdtReceivedData of DataConnector. and change parser | | 3.1.5 | 15-05-2024 | Enhancement in existing **Parser** | diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Admin Activity - Online.json b/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Admin Activity - Online.json index bfbc47e7b8b..64b9216759c 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Admin Activity - Online.json +++ b/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Admin Activity - Online.json @@ -71,7 +71,6 @@ "query": "OfficeActivity | where TimeGenerated {TimeRange}\r\n| summarize by OrganizationName", "isHiddenWhenLocked": true, "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, @@ -85,10 +84,7 @@ "label": "Show Help", "type": 10, "isRequired": true, - "query": "{\"version\":\"1.0.0\",\"content\":\"[\\r\\n { \\\"value\\\": \\\"Yes\\\", \\\"label\\\": \\\"Yes\\\"},\\r\\n {\\\"value\\\": \\\"No\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true }\\r\\n]\\r\\n\",\"transformers\":null}", - "typeSettings": { - "additionalResourceOptions": [] - }, + "query": "{\"version\":\"1.0.0\",\"content\":\"[\\r\\n { \\\"value\\\": \\\"Yes\\\", \\\"label\\\": \\\"Yes\\\"},\\r\\n {\\\"value\\\": \\\"No\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true }\\r\\n]\\r\\n\"}\r\n", "timeContext": { "durationMs": 2592000000 }, @@ -155,7 +151,6 @@ "type": 10, "isRequired": true, "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[\r\n { \"value\": \"True\", \"label\": \"Yes\"},\r\n { \"value\": \"True,False\", \"label\": \"No\", \"selected\":true }\r\n]", @@ -171,7 +166,6 @@ "type": 10, "isRequired": true, "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[\r\n { \"value\": \"True\", \"label\": \"Yes\" },\r\n { \"value\": \"True,False\", \"label\": \"No\", \"selected\":true }\r\n]\r\n", @@ -203,7 +197,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n| summarize count() by CmdletName\r\n| sort by count_", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n| summarize count() by CmdletName\r\n| sort by count_", "size": 2, "showAnalytics": true, "title": "List of all executed cmdlets during the last 90 days (based on Sentinel retention)", @@ -264,7 +258,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n| summarize count() by CmdletName\r\n| join kind=leftouter ( MESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n | make-series Count=count() on TimeGenerated from ago(30d) to now() step 1d by CmdletName\r\n | extend Anomalies=series_decompose_anomalies(Count)\r\n) on CmdletName\r\n| project CmdletName, Total=count_, Count, Anomalies\r\n| sort by Total", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n| summarize count() by CmdletName\r\n| join kind=leftouter ( MESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n | make-series Count=count() on TimeGenerated from ago(30d) to now() step 1d by CmdletName\r\n | extend Anomalies=series_decompose_anomalies(Count)\r\n) on CmdletName\r\n| project CmdletName, Total=count_, Count, Anomalies\r\n| sort by Total", "size": 2, "showAnalytics": true, "showExportToExcel": true, @@ -326,7 +320,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let ExcludedCmdlet = externaldata (Cmdlet: string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\", ignoreFirstRecord=true)\r\n | project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n| summarize Total = count() by Caller\r\n| join kind=leftouter ( MESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n | make-series Count=count() on TimeGenerated from ago(30d) to now() step 1d by Caller\r\n | extend Anomalies=series_decompose_anomalies(Count)\r\n) on Caller\r\n| project Caller, Total, Count, Anomalies\r\n| sort by Total desc", + "query": "let ExcludedCmdlet = externaldata (Cmdlet: string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\", ignoreFirstRecord=true)\r\n | project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n| summarize Total = count() by Caller\r\n| join kind=leftouter ( MESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n | make-series Count=count() on TimeGenerated from ago(30d) to now() step 1d by Caller\r\n | extend Anomalies=series_decompose_anomalies(Count)\r\n) on Caller\r\n| project Caller, Total, Count, Anomalies\r\n| sort by Total desc", "size": 1, "showAnalytics": true, "exportFieldName": "Caller", @@ -424,7 +418,7 @@ "multiSelect": true, "quote": "'", "delimiter": ",", - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| distinct Caller\r\n| extend Caller = replace_string(Caller, '\\\\', '\\\\\\\\')\r\n| sort by Caller asc", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| distinct Caller\r\n| extend Caller = replace_string(Caller, '\\\\', '\\\\\\\\')\r\n| sort by Caller asc", "typeSettings": { "additionalResourceOptions": [ "value::all" @@ -441,14 +435,12 @@ "name": "TargetObjectFilter", "label": "Target Object", "type": 2, - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where Caller in ({CallerFilter})\r\n| distinct TargetObject\r\n| sort by TargetObject asc", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where Caller in ({CallerFilter})\r\n| distinct TargetObject\r\n| sort by TargetObject asc", "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "value": null + "resourceType": "microsoft.operationalinsights/workspaces" }, { "id": "9e93d5c3-0fcb-4ece-b2a0-fc3ff44a0b04", @@ -456,14 +448,12 @@ "name": "CmdletFilter", "label": "Cmdlet Filter", "type": 2, - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where Caller in ({CallerFilter})\r\n| distinct CmdletName\r\n| sort by CmdletName asc", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where Caller in ({CallerFilter})\r\n| distinct CmdletName\r\n| sort by CmdletName asc", "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "value": null + "resourceType": "microsoft.operationalinsights/workspaces" } ], "style": "pills", @@ -476,7 +466,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let ExcludedCmdlet = externaldata (Cmdlet: string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\", ignoreFirstRecord=true)\r\n | project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n| where (Caller in ({CallerFilter}) or Caller == \"ALL\") and TargetObject contains \"{TargetObjectFilter}\" and CmdletName contains \"{CmdletFilter}\"\r\n and TargetObject contains \"\"\r\n and CmdletName contains \"\"\r\n| extend TargetObject = iif(IsVIP == true and TargetObject !=\"\" , strcat(\"👑 \",TargetObject), TargetObject )\r\n| extend Cmdlet = iif(IsSensitive == true and TargetObject !=\"\", strcat(\"💥 \",CmdletName), CmdletName )\r\n| extend IsVIP = iif(IsVIP == true and TargetObject !=\"\" , strcat(\"👑 \",tostring(IsVIP)), tostring(IsVIP ))\r\n| project TimeGenerated, Caller, TargetObject, Cmdlet, CmdletParameters\r\n| sort by TimeGenerated desc", + "query": "let ExcludedCmdlet = externaldata (Cmdlet: string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\", ignoreFirstRecord=true)\r\n | project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where IsVIP in ({VIPOnly})\r\n| where IsSensitive in ({SensitiveOnly})\r\n| where (Caller in ({CallerFilter}) or Caller == \"ALL\") and TargetObject contains \"{TargetObjectFilter}\" and CmdletName contains \"{CmdletFilter}\"\r\n and TargetObject contains \"\"\r\n and CmdletName contains \"\"\r\n| extend TargetObject = iif(IsVIP == true and TargetObject !=\"\" , strcat(\"👑 \",TargetObject), TargetObject )\r\n| extend Cmdlet = iif(IsSensitive == true and TargetObject !=\"\", strcat(\"💥 \",CmdletName), CmdletName )\r\n| extend IsVIP = iif(IsVIP == true and TargetObject !=\"\" , strcat(\"👑 \",tostring(IsVIP)), tostring(IsVIP ))\r\n| project TimeGenerated, Caller, TargetObject, Cmdlet, CmdletParameters\r\n| sort by TimeGenerated desc", "size": 2, "showAnalytics": true, "title": "History", @@ -509,6 +499,9 @@ "name": "Cmdlet Group" } ], + "fallbackResourceIds": [ + "/subscriptions/6e1d3e11-a87d-4467-bb2a-c6b2d791fc03/resourcegroups/rg_sent-soc/providers/microsoft.operationalinsights/workspaces/sent-socdev" + ], "fromTemplateId": "sentinel-MicrosoftExchangeSecurityAdminActivity-Online", "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" } \ No newline at end of file diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Least Privilege with RBAC - Online.json b/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Least Privilege with RBAC - Online.json index da24ecd6d7f..44b004ef44b 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Least Privilege with RBAC - Online.json +++ b/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Least Privilege with RBAC - Online.json @@ -58,7 +58,8 @@ "showDefault": false }, "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.operationalinsights/workspaces", + "value": [] }, { "id": "a88b4e41-eb2f-41bf-92d8-27c83650a4b8", @@ -69,11 +70,11 @@ "isRequired": true, "query": "let _configurationEnv = split(iff(isnull({EnvironmentList}) or isempty({EnvironmentList}) or tolower({EnvironmentList}) == \"all\",\"All\",tostring({EnvironmentList})),',');\r\nESIExchangeOnlineConfig_CL\r\n| extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n| where ScopedEnvironment in (_configurationEnv)\r\n| extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n| summarize Collection = max(Collection)\r\n| project Collection = \"lastdate\", Selected = true\r\n| join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n | where ScopedEnvironment in (_configurationEnv)\r\n | where TimeGenerated > ago(90d)\r\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n | summarize by Collection \r\n | join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n | where ScopedEnvironment in (_configurationEnv)\r\n | where TimeGenerated > ago(90d)\r\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm ')\r\n | summarize by PreciseCollection, Collection \r\n | join kind=leftouter (\r\n ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n | where ScopedEnvironment in (_configurationEnv)\r\n | where TimeGenerated > ago(90d)\r\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm')\r\n | summarize by PreciseCollection, Collection \r\n | summarize count() by Collection\r\n ) on Collection\r\n ) on Collection\r\n) on Collection\r\n| project Value = iif(Selected,Collection,iif(count_ > 1,PreciseCollection,Collection1)), Label = iif(Selected,\"Last Known date\",iif(count_ > 1,PreciseCollection,Collection1)), Selected\r\n| sort by Selected, Value desc", "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.operationalinsights/workspaces", + "value": "2026-07-30" }, { "id": "8ac96eb3-918b-4a36-bcc4-df50d8f46175", @@ -105,7 +106,7 @@ { "type": 1, "content": { - "json": "The current delegation are compared to an export of default delegation available on Exchange Online.\r\n\r\nTo find which is used for the comparaison please follow this link.\r\nThe export is located on the public GitHub of the project.\r\n\r\ncheck this link : https://aka.ms/esiwatchlist\r\n\r\nIt will be updated by the team project.", + "json": "The current delegation are compared to an export of default delegation available on Exchange Online.\r\n\r\nTo find which is used for the comparaison please follow this link.\r\nThe export is located on the public GitHub of the project.\r\n\r\ncheck this link : https://aka.ms/esiwatchlist\r\n\r\nIt will be updated by the team project.", "style": "info" }, "name": "text - 2" @@ -134,7 +135,7 @@ "version": "KqlParameterItem/1.0", "name": "RoleAssignee", "type": 2, - "query": "let DefMRA = externaldata (Name:string)[h\"https://aka.ms/standardMRAOnline\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"User\"\r\n| project CmdletResultValue\r\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\r\n| distinct RoleAssigneeName\r\n", + "query": "let DefMRA = externaldata (Name:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"User\"\r\n| project CmdletResultValue\r\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\r\n| distinct RoleAssigneeName\r\n", "typeSettings": { "showDefault": false }, @@ -147,9 +148,8 @@ "version": "KqlParameterItem/1.0", "name": "Role", "type": 2, - "query": "let DefMRA = externaldata (Name:string)[h\"https://aka.ms/standardMRAOnline\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"User\"\r\n| project CmdletResultValue\r\n| extend Role=tostring (CmdletResultValue.Role)\r\n| distinct Role\r\n| sort by Role asc", + "query": "let DefMRA = externaldata (Name:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"User\"\r\n| project CmdletResultValue\r\n| extend Role=tostring (CmdletResultValue.Role)\r\n| distinct Role\r\n| sort by Role asc", "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, @@ -167,7 +167,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let DefMRA = externaldata (Name:string)[h\"https://aka.ms/standardMRAOnline\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.RoleAssigneeName endswith \"{RoleAssignee}\" \r\n| where CmdletResultValue.Role contains \"{Role}\"\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"User\" and CmdletResultValue.Name !contains \"Deleg\"\r\n| project CmdletResultValue\r\n| extend Name = tostring(CmdletResultValue.Name)\r\n| extend Role = tostring(CmdletResultValue.Role)\r\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\r\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\r\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\r\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\r\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\r\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\r\n| extend Status= tostring(CmdletResultValue.Enabled)\r\n| project Name, Role, RoleAssigneeName,Status,CustomRecipientWriteScope,CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope\r\n| sort by RoleAssigneeName asc\r\n", + "query": "let DefMRA = externaldata (Name:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.RoleAssigneeName endswith \"{RoleAssignee}\" \r\n| where CmdletResultValue.Role contains \"{Role}\"\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"User\" and CmdletResultValue.Name !contains \"Deleg\"\r\n| project CmdletResultValue\r\n| extend Name = tostring(CmdletResultValue.Name)\r\n| extend Role = tostring(CmdletResultValue.Role)\r\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\r\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\r\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\r\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\r\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\r\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\r\n| extend Status= tostring(CmdletResultValue.Enabled)\r\n| project Name, Role, RoleAssigneeName,Status,CustomRecipientWriteScope,CustomConfigWriteScope,CustomResourceScope,RecipientWriteScope,ConfigWriteScope\r\n| sort by RoleAssigneeName asc\r\n", "size": 3, "showAnalytics": true, "queryType": 0, @@ -277,8 +277,9 @@ "version": "KqlParameterItem/1.0", "name": "RoleAssignee", "type": 2, - "query": "let DefMRA = externaldata (Name:string)[h\"https://aka.ms/standardMRAOnline\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"RoleGroup\" and CmdletResultValue.RoleAssigneeName !contains \"RIM-MailboxAdmins\"\r\n| project CmdletResultValue\r\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\r\n| distinct RoleAssigneeName", + "query": "let DefMRA = externaldata (Name:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"RoleGroup\" and CmdletResultValue.RoleAssigneeName !contains \"RIM-MailboxAdmins\"\r\n| project CmdletResultValue\r\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\r\n| distinct RoleAssigneeName", "typeSettings": { + "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, @@ -290,14 +291,13 @@ "version": "KqlParameterItem/1.0", "name": "Role", "type": 2, - "query": "let DefMRA = externaldata (Name:string)[h\"https://aka.ms/standardMRAOnline\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"RoleGroup\" and CmdletResultValue.RoleAssigneeName !contains \"RIM-MailboxAdmins\"\r\n| project CmdletResultValue\r\n| extend Role=tostring (CmdletResultValue.Role)\r\n| distinct Role\r\n| sort by Role asc", + "query": "let DefMRA = externaldata (Name:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"RoleGroup\" and CmdletResultValue.RoleAssigneeName !contains \"RIM-MailboxAdmins\"\r\n| project CmdletResultValue\r\n| extend Role=tostring (CmdletResultValue.Role)\r\n| distinct Role\r\n| sort by Role asc", "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "value": "MR-CustMailRecipients" + "value": null } ], "style": "pills", @@ -310,7 +310,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let DefMRA = externaldata (Name:string)[h\"https://aka.ms/standardMRAOnline\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nlet RoleG = ExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n | project RoleAssigneeName=tostring(CmdletResultValue.Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.RoleAssigneeName endswith \"{RoleAssignee}\" \r\n| where CmdletResultValue.Role contains \"{Role}\"\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"RoleGroup\" and CmdletResultValue.RoleAssigneeName !contains \"RIM-MailboxAdmins\" and CmdletResultValue.Name !contains \"Deleg\"\r\n| project CmdletResultValue\r\n| extend ManagementRoleAssignment = tostring(CmdletResultValue.Name)\r\n| extend Role = tostring(CmdletResultValue.Role)\r\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\r\n| extend Status= tostring(CmdletResultValue.Enabled)\r\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\r\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\r\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\r\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\r\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\r\n|lookup RoleG on RoleAssigneeName \r\n| project-away CmdletResultValue\r\n| sort by RoleAssigneeName asc", + "query": "let DefMRA = externaldata (Name:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20Online/%23%20-%20General%20Content/Operations/Watchlists/standardMRAOnline.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| summarize make_list(Name);\r\nlet RoleG = ExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n | project RoleAssigneeName=tostring(CmdletResultValue.Name);\r\nExchangeConfiguration(SpecificSectionList=\"MRA\",SpecificConfigurationDate=\"{DateOfConfiguration:value}\",SpecificConfigurationEnv={EnvironmentList},Target = \"Online\")\r\n| where CmdletResultValue.RoleAssigneeName endswith \"{RoleAssignee}\" \r\n| where CmdletResultValue.Role contains \"{Role}\"\r\n| where CmdletResultValue.Name !in (DefMRA) and CmdletResultValue.RoleAssigneeType == \"RoleGroup\" and CmdletResultValue.RoleAssigneeName !contains \"RIM-MailboxAdmins\" and CmdletResultValue.Name !contains \"Deleg\"\r\n| project CmdletResultValue\r\n| extend ManagementRoleAssignment = tostring(CmdletResultValue.Name)\r\n| extend Role = tostring(CmdletResultValue.Role)\r\n| extend RoleAssigneeName = tostring(CmdletResultValue.RoleAssigneeName)\r\n| extend Status= tostring(CmdletResultValue.Enabled)\r\n| extend CustomRecipientWriteScope = tostring(CmdletResultValue.CustomRecipientWriteScope)\r\n| extend CustomConfigWriteScope = tostring(CmdletResultValue.CustomConfigWriteScope)\r\n| extend CustomResourceScope = tostring(CmdletResultValue.CustomResourceScope)\r\n| extend RecipientWriteScope = CmdletResultValue.RecipientWriteScope\r\n| extend ConfigWriteScope = CmdletResultValue.ConfigWriteScope\r\n|lookup RoleG on RoleAssigneeName \r\n| project-away CmdletResultValue\r\n| sort by RoleAssigneeName asc", "size": 3, "showAnalytics": true, "showExportToExcel": true, @@ -720,7 +720,7 @@ }, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "value": "MR-CustPF" + "value": null } ], "style": "pills", @@ -762,6 +762,9 @@ "name": "Custom Role" } ], + "fallbackResourceIds": [ + "/subscriptions/6e1d3e11-a87d-4467-bb2a-c6b2d791fc03/resourcegroups/rg_sent-soc/providers/microsoft.operationalinsights/workspaces/sent-socdev" + ], "fromTemplateId": "sentinel-MicrosoftExchangeLeastPrivilegewithRBAC-Online", "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" } \ No newline at end of file diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Search AdminAuditLog - Online.json b/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Search AdminAuditLog - Online.json index 9b8acc02885..197d37b7a48 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Search AdminAuditLog - Online.json +++ b/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Search AdminAuditLog - Online.json @@ -73,7 +73,6 @@ "query": "OfficeActivity | where TimeGenerated {TimeRange}\r\n | summarize by OrganizationName", "isHiddenWhenLocked": true, "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, @@ -88,7 +87,6 @@ "type": 10, "isRequired": true, "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "jsonData": "[\r\n { \"value\": \"Yes\", \"label\": \"Yes\"},\r\n {\"value\": \"No\", \"label\": \"No\", \"selected\":true }\r\n]" @@ -162,7 +160,7 @@ "multiSelect": true, "quote": "'", "delimiter": ",", - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| extend Caller = replace_string(Caller, '\\\\', '\\\\\\\\')\r\n| extend admin = Caller\r\n| distinct admin\r\n\r\n", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| extend Caller = replace_string(Caller, '\\\\', '\\\\\\\\')\r\n| extend admin = Caller\r\n| distinct admin\r\n\r\n", "typeSettings": { "additionalResourceOptions": [ "value::all" @@ -181,7 +179,7 @@ "multiSelect": true, "quote": "'", "delimiter": ",", - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where Caller in ({Admin})\r\n| distinct CmdletName", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| where Caller in ({Admin})\r\n| distinct CmdletName", "typeSettings": { "additionalResourceOptions": [ "value::all" @@ -226,7 +224,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where Caller in ({Admin}) and CmdletName in ({CmdLet})\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| extend TargetObject = iif(IsVIP == true and TargetObject !=\"\" , strcat(\"👑 \",TargetObject), TargetObject )\r\n| extend CmdletName = iif(IsSensitive == true and TargetObject !=\"\", strcat(\"💥 \",CmdletName), CmdletName )\r\n| extend IsVIP = iif(IsVIP == true and TargetObject !=\"\" , strcat(\"👑 \",tostring(IsVIP)), tostring(IsVIP ))\r\n| extend IsSensitive = iif(IsSensitive == true and TargetObject !=\"\", strcat(\"💥 \",tostring(IsSenstiveCmdlet)), tostring(IsSenstiveCmdlet))\r\n| project TimeGenerated, Caller,IsVIP,TargetObject,IsSensitive,CmdletName,CmdletParameters\r\n| sort by TimeGenerated desc", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where Caller in ({Admin}) and CmdletName in ({CmdLet})\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| extend TargetObject = iif(IsVIP == true and TargetObject !=\"\" , strcat(\"👑 \",TargetObject), TargetObject )\r\n| extend CmdletName = iif(IsSensitive == true and TargetObject !=\"\", strcat(\"💥 \",CmdletName), CmdletName )\r\n| extend IsVIP = iif(IsVIP == true and TargetObject !=\"\" , strcat(\"👑 \",tostring(IsVIP)), tostring(IsVIP ))\r\n| extend IsSensitive = iif(IsSensitive == true and TargetObject !=\"\", strcat(\"💥 \",tostring(IsSenstiveCmdlet)), tostring(IsSenstiveCmdlet))\r\n| project TimeGenerated, Caller,IsVIP,TargetObject,IsSensitive,CmdletName,CmdletParameters\r\n| sort by TimeGenerated desc", "size": 0, "showAnalytics": true, "showExportToExcel": true, @@ -281,7 +279,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| extend Caller\r\n| extend CmdletName\r\n| summarize Count=count() by CmdletName", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| extend Caller\r\n| extend CmdletName\r\n| summarize Count=count() by CmdletName", "size": 2, "showAnalytics": true, "showExportToExcel": true, @@ -299,7 +297,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| extend Account = Caller\r\n| summarize Count=dcount(CmdletName) by Account,CmdletName", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| extend Account = Caller\r\n| summarize Count=dcount(CmdletName) by Account,CmdletName", "size": 2, "showAnalytics": true, "title": "Total Unique Cmdlet per Account for the Time Range", @@ -346,7 +344,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| summarize Count=count() by CmdletName\r\n| sort by CmdletName asc", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| summarize Count=count() by CmdletName\r\n| sort by CmdletName asc", "size": 0, "showAnalytics": true, "title": "Total List of Cmdlets", @@ -364,7 +362,7 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://aka.ms/ExcludedCmdletWatchlist\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| extend Account = Caller\r\n| summarize Count=count() by CmdletName, Account\r\n| sort by Count asc", + "query": "let ExcludedCmdlet = externaldata (Cmdlet:string)[h\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/refs/heads/master/Solutions/Microsoft%20Exchange%20Security%20-%20Exchange%20On-Premises/%23%20-%20General%20Content/Operations/Watchlists/ExcludedCmdletWatchlist.csv\"]with(format=\"csv\",ignoreFirstRecord=true)| project Cmdlet;\r\nMESOfficeActivityLogs\r\n| where TimeGenerated {TimeRange}\r\n| where CmdletName !in (ExcludedCmdlet)\r\n| extend Account = Caller\r\n| summarize Count=count() by CmdletName, Account\r\n| sort by Count asc", "size": 0, "showAnalytics": true, "title": "List of Cmdlet per Account", @@ -374,8 +372,7 @@ "gridSettings": { "rowLimit": 10000, "filter": true - }, - "sortBy": [] + } }, "customWidth": "50", "name": "query - 1" @@ -456,6 +453,9 @@ "name": "group - 5" } ], + "fallbackResourceIds": [ + "/subscriptions/6e1d3e11-a87d-4467-bb2a-c6b2d791fc03/resourcegroups/rg_sent-soc/providers/microsoft.operationalinsights/workspaces/sent-socdev" + ], "fromTemplateId": "sentinel-MicrosoftExchangeSearchAdminAuditLog-Online", "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" } \ No newline at end of file diff --git a/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Security Review - Online.json b/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Security Review - Online.json index 9e8a49ae978..3cc45e1c724 100644 --- a/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Security Review - Online.json +++ b/Solutions/Microsoft Exchange Security - Exchange Online/Workbooks/Microsoft Exchange Security Review - Online.json @@ -41,7 +41,6 @@ "isRequired": true, "query": "let _configurationEnv = split(iff(isnull({EnvironmentList}) or isempty({EnvironmentList}) or tolower({EnvironmentList}) == \"all\",\"All\",tostring({EnvironmentList})),',');\r\nESIExchangeOnlineConfig_CL\r\n| extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n| where ScopedEnvironment in (_configurationEnv)\r\n| extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n| summarize Collection = max(Collection)\r\n| project Collection = \"lastdate\", Selected = true\r\n| join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n | where ScopedEnvironment in (_configurationEnv)\r\n | where TimeGenerated > ago(90d)\r\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n | summarize by Collection \r\n | join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n | where ScopedEnvironment in (_configurationEnv)\r\n | where TimeGenerated > ago(90d)\r\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm ')\r\n | summarize by PreciseCollection, Collection \r\n | join kind=leftouter (\r\n ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n | where ScopedEnvironment in (_configurationEnv)\r\n | where TimeGenerated > ago(90d)\r\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm')\r\n | summarize by PreciseCollection, Collection \r\n | summarize count() by Collection\r\n ) on Collection\r\n ) on Collection\r\n) on Collection\r\n| project Value = iif(Selected,Collection,iif(count_ > 1,PreciseCollection,Collection1)), Label = iif(Selected,\"Last Known date\",iif(count_ > 1,PreciseCollection,Collection1)), Selected\r\n| sort by Selected, Value desc", "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, @@ -54,9 +53,6 @@ "type": 10, "description": "If this button is checked, two collections will be compared", "isRequired": true, - "typeSettings": { - "additionalResourceOptions": [] - }, "jsonData": "[\r\n { \"value\": \"True\", \"label\": \"Yes\" },\r\n { \"value\": \"True,False\", \"label\": \"No\", \"selected\":true }\r\n]" }, { @@ -93,7 +89,6 @@ "isRequired": true, "query": "let _configurationEnv = split(iff(isnull({EnvironmentList}) or isempty({EnvironmentList}) or tolower({EnvironmentList}) == \"all\",\"All\",tostring({EnvironmentList})),',');\r\nESIExchangeOnlineConfig_CL\r\n| extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n| where ScopedEnvironment in (_configurationEnv)\r\n| extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n| summarize Collection = max(Collection)\r\n| project Collection = \"lastdate\", Selected = true\r\n| join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n | where ScopedEnvironment in (_configurationEnv)\r\n | where TimeGenerated > ago(90d)\r\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n | summarize by Collection \r\n | join kind= fullouter ( ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n | where ScopedEnvironment in (_configurationEnv)\r\n | where TimeGenerated > ago(90d)\r\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm ')\r\n | summarize by PreciseCollection, Collection \r\n | join kind=leftouter (\r\n ESIExchangeOnlineConfig_CL | extend ScopedEnvironment = iff(_configurationEnv contains \"All\", \"All\",ESIEnvironment_s) \r\n | where ScopedEnvironment in (_configurationEnv)\r\n | where TimeGenerated > ago(90d)\r\n | extend Collection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd')\r\n | extend PreciseCollection = format_datetime(todatetime(EntryDate_s), 'yyyy-MM-dd HH:mm')\r\n | summarize by PreciseCollection, Collection \r\n | summarize count() by Collection\r\n ) on Collection\r\n ) on Collection\r\n) on Collection\r\n| project Value = iif(Selected,Collection,iif(count_ > 1,PreciseCollection,Collection1)), Label = iif(Selected,\"Last Known date\",iif(count_ > 1,PreciseCollection,Collection1)), Selected\r\n| sort by Selected, Value desc", "typeSettings": { - "additionalResourceOptions": [], "showDefault": false }, "queryType": 0, @@ -668,8 +663,7 @@ "showDefault": false }, "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "value": null + "resourceType": "microsoft.operationalinsights/workspaces" } ], "style": "pills", @@ -1089,6 +1083,9 @@ "name": "Transport Security configuration" } ], + "fallbackResourceIds": [ + "/subscriptions/6e1d3e11-a87d-4467-bb2a-c6b2d791fc03/resourcegroups/rg_sent-soc/providers/microsoft.operationalinsights/workspaces/sent-socdev" + ], "fromTemplateId": "sentinel-MicrosoftExchangeSecurityReview-Online", "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" } \ No newline at end of file