microsoft defender for endpoint
786 TopicsTriage vulnerabilities with the Vulnerability Remediation Agent, now in public preview
As automation and AI accelerate the pace of vulnerability discovery, the window between disclosure and exploitation continues to shrink. For IT and security teams, the challenge is no longer just finding vulnerabilities - it's prioritizing the ones that matter and acting on them before they can be exploited. To help organizations close that gap, we're pleased to announce that the Vulnerability Remediation Agent for Security Copilot in Microsoft Intune is now in public preview and rolling out to all customers. Following a successful limited preview, the agent is now broadly available. This release brings agentic vulnerability remediation out of an early-access cohort and into the hands of every eligible organization - an important step in our continued investment in helping admins reduce exposure faster and with greater confidence. View eligibility prerequisites here. How the agent helps you identify and triage vulnerabilities The Vulnerability Remediation Agent uses data from Microsoft Defender Vulnerability Management to identify Common Vulnerabilities and Exposures (CVEs) across your Intune-managed Windows devices and apps, then prioritizes them for remediation. Rather than leaving admins to sift through lengthy CVE lists with little context, the agent surfaces a prioritized set of recommendations directly in the Intune admin center - accessible from both the Agents and Endpoint security pages. When the agent runs, it evaluates vulnerability data and ranks threats based on factors such as CVSS scores, exposure impact, and affected device count, so the most critical issues rise to the top. Drilling into any suggestion provides: The count of associated CVEs A Copilot-assisted summarized impact analysis Suggested actions and affected systems Exposed devices and potential impact Step-by-step guidance for remediating the threat using Intune After acting on a recommendation, admins can mark it as applied, allowing the agent to retain a record for tracking remediation actions over time. The result is a meaningful reduction in the time it takes to investigate, prioritize, and remediate - strengthening overall security posture. Introducing agentic identity for the Vulnerability Remediation Agent With this release, the agent now operates under Microsoft Entra agentic identity - a meaningful advancement in how autonomous agents are governed and secured. What it is. Agentic identity is a specialized identity in Microsoft Entra ID that allows the agent to operate securely and independently. During setup, the agent provisions a dedicated agentic identity and a corresponding agentic user in your tenant's Microsoft Entra directory. The agent then runs under the permissions delegated to that agentic user rather than under a human user account. Why it matters. Agentic identity decouples the agent from any one person, ensuring its behavior is strictly bound to the permissions and scope you delegate to it. This delivers clearer accountability, a cleaner audit trail, and enterprise-grade governance for autonomous operations. How it helps. Admins remain firmly in control. After setup, delegate the required read permissions to the agentic user in the Microsoft Intune and Microsoft Defender admin centers, then use the built-in Readiness Check to confirm everything is configured correctly before the agent runs. Learn more in Agent identity. Getting started: Connect → Enable → Run → Remediate → Track One of the design goals behind the Vulnerability Remediation Agent is to make agentic security approachable, not complex. Rather than stitching together signals across multiple tools and admin centers, the agent guides admins through a clear, repeatable flow - from connecting your data to tracking measurable improvement over time. Connect — bring Defender and Intune data together. The agent draws on Microsoft Defender Vulnerability Management for CVE intelligence and Microsoft Intune for device and configuration context. With the required Microsoft Defender and Microsoft Intune plugins in place, your vulnerability and management signals work as one. Learn more on what is needed to connect the experience. Enable — turn on the agent. From the Agents node in the Microsoft Intune admin center, set up the agent in a few guided steps. During setup, the agent provisions its Microsoft Entra agentic identity and surfaces the permissions and plugins it needs, so you know exactly what to delegate before the first run. Run — let automated prioritization do the heavy lifting. Once permissions are delegated and the Run Readiness Check passes, you can configure the agent to run on demand or schedule it to run automatically in the background on a cadence you define; scheduling is a unique capability that helps teams stay ahead of emerging risks without requiring constant manual intervention. Each run analyzes your environment and produces a prioritized list of recommendations ranked by CVSS score, exposure impact, and affected device count so the most critical risks rise to the top automatically. Remediate — act with guided, Intune-ready actions. Each recommendation includes a Copilot-assisted impact summary, exposed devices, and step-by-step guidance for remediating the threat using Intune. Admins move directly from insight to action, without leaving the admin center. Track — measure improvement over time. Recommendations can be marked as applied, and the agent retains a record of your remediation actions. The outcome is a streamlined operating model: connect once, enable with confidence, and let the agent drive a continuous cycle of prioritization, remediation, and view progress. For full prerequisites, licensing, plugin, and role requirements, see Vulnerability Remediation Agent overview and set up. The Vulnerability Remediation Agent represents a meaningful step toward a more proactive, AI-assisted security posture, one where admins spend less time sifting through CVE lists and more time acting on what matters most. We invite you to try the public preview today, connect your Defender and Intune data, and experience how agentic remediation can help your team stay ahead of emerging threats. As always, we'd love to hear your feedback as we continue investing in making security in Intune faster, smarter, and more accessible. Share your tips and lessons learned in the comments below or reach out to us on X @IntuneSuppTeam.72Views0likes0CommentsAutomating Daily MDE Compliance Monitoring Across Azure VMs
The Problem We’re Solving Most security teams have no automated way to know when a VM silently falls out of MDE coverage, whether because the agent stopped, the VM was newly provisioned without onboarding, or the device stopped reporting. This Logic App closes that gap and puts the right information in front of the right people every day. Disclaimer: This solution is designed for Azure Virtual Machines only. For non-Azure VMs onboarded to Microsoft Defender for Endpoint through Azure Arc, a separate companion blog will be published soon to cover that scenario. What changes once you deploy this Challenge Without This Logic App How This Logic App Helps Security gaps go undetected for days or weeks Any VM that is not onboarded or has stopped reporting is caught within 24 hours of the daily run No automated owner notification The VM's ServerOwner tag is read automatically, and the owner is emailed directly with full compliance details VMs with no owner fall through the cracks Flagged explicitly in the IT summary report with instructions for how to assign the tag Manual compliance reporting is time-consuming Full CSV report auto-attached to every daily IT summary; no manual extraction needed Agents silently stop reporting after onboarding Detects "Onboarded, Not Reporting" as a distinct status, separate from "Not Onboarded" Large multi-subscription environments are hard to cover Paginated queries across all enabled subscriptions; every running VM is checked Compliance States Detected Compliance Status Priority What It Means Not Onboarded P2, High The VM is running in Azure but has never appeared in MDE. There is zero security telemetry for this machine. Onboarded, Not Reporting P3, Medium The VM was previously enrolled but has not checked in within the configured window. The MDE agent may be stopped or the VM may have lost network connectivity to MDE. Compliant No alert VM is onboarded and checked in within the required time window. It is excluded from all notifications. Running VMs Only: This workflow queries Azure Resource Graph with a filter of powerState == "VM running". Deallocated, stopped, and powered-off VMs are intentionally excluded — they are not expected to report to MDE while offline. Only machines that are turned on are evaluated. Workflow Architecture The workflow runs as a sequential daily pipeline. All Azure VM data and MDE device data are collected into memory first, then each VM is evaluated in a single For Each loop. Execution Pipeline Recurrence trigger fires daily at 08:00 IST. CONFIG compose action reads MDE_LASTSEEN_HOURS (default 24). This defines the compliance window: how recently a VM must have reported to MDE to be considered Compliant. Init-varITTeamEmail and Init-varSenderEmail load the configurable email addresses used for sending and receiving notifications. Get-AllSubscriptions calls the Azure Management API to discover all subscriptions in the tenant. ForEach-Subscription runs a paginated Azure Resource Graph query per enabled subscription, collecting all running VMs along with Private IP, OS Type, Location, ServerOwner tag, and VM UUID. Init-MDEVariables then Paginate-MDEDevices call the MDE Security Center API in pages of 10,000 to load every enrolled device into the AllMDEDevices array. ForEach-AzureVM looks each Azure VM up in AllMDEDevices and determines compliance status and priority. Non-compliant handling builds HTML and CSV rows. If the VM has a ServerOwner tag, a compliance alert email goes to the owner with the IT Team CC'd. If there's no owner, the VM is appended to NoOwnerList. IT Summary email is sent once all VMs are processed. If any non-compliant VMs were found, the consolidated IT report is sent with the CSV attachment. Otherwise an All Clear email is sent. How Azure VM Data is Matched to MDE Data Each Azure VM is matched against the MDE device list using a two-level strategy. Both checks run for every VM on every run. Match Method How It Works Primary: Azure VM ID Compares azureVmId from the MDE device record (lowercase) against the VmId captured from Azure Resource Graph (lowercase). Immune to hostname changes; this is the preferred match. Fallback: Hostname + IP Checks that MDE computerDnsName starts with the Azure VM name (case-insensitive) AND lastIpAddress matches the Azure Private IP. Both conditions must be true. Not Found A synthetic MDE record with onboardingStatus: "NotFound" is created. The VM is treated as Not Onboarded and a P2 High alert is raised. Pagination Design The workflow handles large environments through two independent pagination mechanisms that run before any compliance evaluation begins. Data Source Page Size Mechanism Azure Resource Graph 1,000 VMs per page Uses $skipToken from the response. The Until loop re-queries with the token until no token is returned (last page). Variables VMSkipToken and VMFetchComplete manage loop state per subscription. Supports up to 50,000 VMs (50 pages). MDE Security Center API 10,000 devices per page Uses the $skip offset parameter. MDESkip is incremented by 10,000 each iteration. The loop stops when a page returns fewer than 10,000 records. Supports up to 500,000 MDE devices (50 pages × 10,000). Prerequisites Azure Resources Resource Requirement Notes Azure Logic App Standard plan, Stateful workflow Consumption plan also supported Managed Identity System-assigned on the Logic App Enable under Logic App > Identity Sender mailbox (varSenderEmail) Licensed Microsoft 365 account Emails are sent FROM this address IT Team email (varITTeamEmail) Valid email address or distribution list Receives all reports; CC'd on owner alerts Azure VMs Running, with ServerOwner tag (recommended) Tag value must be a valid email address MDE licensing Microsoft Defender for Endpoint P1 or P2 Tenant must be enrolled in MDE The ServerOwner Tag Server owner notifications rely on a VM-level Azure tag. Without it, the VM is included in the IT summary, but no individual alert is sent to an owner. Tag Name Expected Value Effect ServerOwner Valid email, e.g. john@yourcompany.com Compliance alert sent TO this address; IT Team CC'd If the tag is missing or empty, the VM is flagged in the Action Required: No Owner Tag Found section of the IT summary email, with step-by-step instructions for tagging it in the Azure Portal. Required Permissions & Why The Logic App's Managed Identity must be granted three API permissions. These are Application permissions that cannot be assigned through the Azure Portal UI, so the PowerShell script in Section 4.3 must be used. Admin consent is required. Permission Summary Permission API / Service AppId Why It Is Required user_impersonation Azure Management 797f4846-ba00-4fd7-ba43-dac1f8f63013 Allows the Managed Identity to call the Azure Resource Graph API to query VM inventory across all subscriptions. Without this, the workflow cannot discover VMs. WindowsDefenderATP.Read.All MDE Security Center fc780465-2017-40d4-a0c5-307022471b92 Allows reading all device records from the MDE API (/api/machines). This returns onboarding status, last seen time, and health status — the core compliance data. Mail.Send Microsoft Graph 00000003-0000-0000-c000-000000000000 Allows sending emails via the Graph /sendMail endpoint on behalf of the varSenderEmail mailbox. Without this, no alerts or reports can be sent. Important: The Azure Management and MDE permissions belong to separate service principals — they are NOT part of Microsoft Graph. Each permission must be assigned to its own service principal using the AppId shown above. The script in Section 4.2 handles this correctly. Where to find the required values Parameter Where to find it in Azure Portal $tenantID Azure Portal > Microsoft Entra ID > Overview > Tenant ID $managedIdentityObjectId Logic App > Settings > Identity > System assigned tab > Object (principal) ID Permission Assignment Script Run this in Azure Cloud Shell or any terminal with the Microsoft.Graph PowerShell module installed. Update $tenantID and $managedIdentityObjectId before running. # PowerShell # ── Update these two values before running ─────────────────────────── $tenantID = "<tenantID>" # Your Tenant ID $managedIdentityObjectId = "<objectID>" # MI Object ID # Install Microsoft.Graph if not already present if (!(Get-Module -ListAvailable -Name Microsoft.Graph)) { Install-Module -Name Microsoft.Graph -Scope CurrentUser -Force } # Connect to Microsoft Graph Connect-MgGraph -TenantId $tenantID ` -Scopes "AppRoleAssignment.ReadWrite.All","Application.Read.All" # MDE Compliance Logic App needs 3 permissions across 3 different service principals $permissions = @( @{ Permission="user_impersonation"; AppId="797f4846-ba00-4fd7-ba43-dac1f8f63013" }, @{ Permission="WindowsDefenderATP.Read.All"; AppId="fc780465-2017-40d4-a0c5-307022471b92" }, @{ Permission="Mail.Send"; AppId="00000003-0000-0000-c000-000000000000" } ) foreach ($entry in $permissions) { $sp = Get-MgServicePrincipal -Filter "AppId eq '$($entry.AppId)'" $appRole = $sp.AppRoles | Where-Object { $_.Value -eq $entry.Permission } if ($appRole -ne $null) { New-MgServicePrincipalAppRoleAssignment ` -ServicePrincipalId $sp.Id ` -PrincipalId $managedIdentityObjectId ` -ResourceId $sp.Id ` -AppRoleId $appRole.Id Write-Host "Assigned: $($entry.Permission)" -ForegroundColor Green } else { Write-Host "Not found: $($entry.Permission)" -ForegroundColor Yellow } } Write-Host "All permissions assigned." -ForegroundColor Green Verify Permissions Assigned # PowerShell # Run after the assignment script to verify all 3 permissions are present Get-MgServicePrincipalAppRoleAssignment ` -ServicePrincipalId $managedIdentityObjectId | Select-Object AppRoleId, PrincipalDisplayName | Format-Table -AutoSize Note: You should see three assignment rows in the output — one for each permission. If any are missing, re-run the assignment script. An error saying the assignment already exists is normal and can be safely ignored. Creating the Logic App Create the resource Azure Portal > search Logic Apps > + Create. Select your Subscription and Resource Group. Logic App name: la-mde-compliance-monitor. Plan type: Standard > Windows > select or create a Hosting Plan > Review + Create > Create. Once deployed, click Go to resource. Enable System-assigned Managed Identity Open the Logic App > left menu: Settings > Identity. On the System assigned tab, toggle Status to On. Click Save > Yes on the confirmation dialog. The Object (principal) ID appears. Copy this value for the PowerShell script. Run the Permissions Assignment script to assign all three permissions to this identity. Why Managed Identity: A System-assigned Managed Identity is automatically scoped to this Logic App and deleted when the Logic App is deleted. It authenticates to Azure Management API, MDE API, and Microsoft Graph without any stored passwords or client secrets. Create the workflow and import the JSON Logic App > left menu: Workflows > + Add. Workflow name: MDEComplianceMonitor. State type: Stateful. Click Create. Click the workflow name > left menu: Code. Press Ctrl + A > Delete to clear the editor completely. Paste the complete workflow JSON from the companion file (see Appendix A). Click Save. It should succeed with no validation errors. Important: Always use Stateful. Stateless workflows do not support run history, have a 5-minute timeout, and do not retain intermediate state — all of which are required by this workflow's pagination loops. Configuration: What You Can Change After importing the JSON, update only the values described below. Everything else runs automatically. Email Address Variables Variable Description Where to Update varITTeamEmail The IT Team email address. All IT Summary reports are sent TO this address. All per-VM owner emails CC this address. 3000 varSenderEmail The Microsoft 365 licensed account that emails are sent FROM via Graph API. Must have Mail.Send permission granted to the Managed Identity. 3000 Compliance look-up window: MDE_LASTSEEN_HOURS This setting in the CONFIG compose action defines how recently a VM must have reported to MDE to count as Compliant. Default is 24 hours. Value Behaviour 24 (default) Compliant if the VM checked in with MDE within the last 24 hours. Recommended starting point. 12 Stricter check; suitable for high-security environments requiring near-real-time coverage. 48 More relaxed; suitable for environments with scheduled maintenance windows or intermittent connectivity. Running VMs Only The Azure Resource Graph query includes a filter for powerState == "VM running". This means: Deallocated VMs are excluded (not expected to report to MDE while offline). Stopped (allocated) VMs are excluded. Newly started VMs are included and checked on the next daily run. To Change the Filter: To change the power state filter, locate the "query" string inside the Build-VMQuery-Paged action and modify the | where powerState == clause. For example, removing the filter entirely will check all VMs regardless of state. Sample Email Notifications The screenshots below show actual emails generated by this workflow. All sensitive data (email addresses, VM names, subscription IDs, IP addresses) has been redacted. Per-VM owner alert Sent to the server owner (ServerOwner tag) when their VM is non-compliant. The IT Team is CC'd. The email contains full server details, compliance status, priority, last MDE check-in time, and resolution SLA. Note: If no ServerOwner tag is set the VM is skipped here and included in the "No Owner Tag Found" section of the IT summary instead. IT Team Daily Summary Report Sent once per day to the IT Team after all owner emails are dispatched. Shows up to 20 VMs inline with a full CSV attachment containing the complete list, plus a dedicated section for VMs with no owner tag. Note: The CSV attachment always contains the complete list of all non-compliant VMs regardless of count. The inline HTML table is limited to 20 rows to keep the email size manageable. All Compliant VMs: If all VMs are compliant, you’ll see email like this: Post-Deployment Checklist Before you leave the workflow running unattended, walk through this checklist once. # Item 1 Logic App resource created (Standard plan, Stateful workflow) 2 System-assigned Managed Identity enabled; Object ID copied 3 PowerShell script run; user_impersonation, WindowsDefenderATP.Read.All, and Mail.Send assigned 4 Permissions verified using Get-MgServicePrincipalAppRoleAssignment (3 rows expected) 5 Workflow JSON pasted into Code view; saved without validation errors 6 varITTeamEmail updated to your IT security team or distribution list address 7 varSenderEmail updated to a licensed Microsoft 365 mailbox 8 MDE_LASTSEEN_HOURS reviewed (default 24, adjust if needed) 9 At least one Azure VM has the ServerOwner tag set with a valid email 10 Manual run triggered: Logic App > Overview > Run Trigger > Run 11 Run history shows Succeeded; no 401 or 403 errors on any HTTP action 12 IT Team received the daily summary email with CSV attachment 13 Server owner received a per-VM alert with the IT Team CC'd 14 Recurrence trigger confirmed running daily at 08:00 IST Wrapping Up What I love about this is how much it accomplishes with so little: a Logic App, a Managed Identity, and three permissions. No connectors, no secrets to rotate, no third-party services. Yet every morning, your security team starts the day knowing exactly which VMs are out of MDE coverage and which owners have already been notified. If you adopt this pattern, here are a few natural next steps to consider: Hook into Microsoft Sentinel by writing non-compliant VMs to a custom table for trend analysis. Auto-create ServiceNow or Jira tickets for VMs that remain non-compliant for more than 48 hours. Extend the match logic to include Arc-enabled servers, not just Azure VMs. Add a Teams adaptive card notification alongside email for faster response. I'd love to hear how you're solving MDE coverage gaps in your environment. Appendix A: Workflow JSON The complete Logic App workflow definition is provided below. To import it: open the Logic App in Azure Portal, navigate to the workflow, click Code view, press Ctrl + A to clear the existing content, paste the entire JSON, then click Save. { "definition": { "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", "contentVersion": "1.0.0.0", "triggers": { "Recurrence": { "recurrence": { "frequency": "Day", "interval": 1, "schedule": { "hours": [ "8" ], "minutes": [ 0 ] }, "timeZone": "India Standard Time" }, "evaluatedRecurrence": { "frequency": "Day", "interval": 1, "schedule": { "hours": [ "8" ], "minutes": [ 0 ] }, "timeZone": "India Standard Time" }, "type": "Recurrence" } }, "actions": { "CONFIG": { "runAfter": {}, "type": "Compose", "inputs": { "MDE_LASTSEEN_HOURS": 24 } }, "Set-ExcludedSubscriptions": { "runAfter": { "CONFIG": [ "Succeeded" ] }, "type": "Compose", "inputs": [] }, "Init-varITTeamEmail": { "runAfter": { "Set-ExcludedSubscriptions": [ "Succeeded" ] }, "type": "InitializeVariable", "inputs": { "variables": [ { "name": "varITTeamEmail", "type": "string", "value": "admin@contoso.onmicrosoft.com" } ] } }, "Init-varSenderEmail": { "runAfter": { "Init-varITTeamEmail": [ "Succeeded" ] }, "type": "InitializeVariable", "inputs": { "variables": [ { "name": "varSenderEmail", "type": "string", "value": "admin@contoso.onmicrosoft.com" } ] } }, "Get-AllSubscriptions": { "runAfter": { "Init-varSenderEmail": [ "Succeeded" ] }, "type": "Http", "inputs": { "uri": "https://management.azure.com/subscriptions?api-version=2022-12-01", "method": "GET", "headers": { "Content-Type": "application/json" }, "authentication": { "type": "ManagedServiceIdentity", "audience": "https://management.azure.com" }, "retryPolicy": { "type": "fixed", "count": 3, "interval": "PT60S" } } }, "Parse-AllSubscriptions": { "runAfter": { "Get-AllSubscriptions": [ "Succeeded" ] }, "type": "ParseJson", "inputs": { "content": "@body('Get-AllSubscriptions')", "schema": { "type": "object", "properties": { "value": { "type": "array", "items": { "type": "object", "properties": { "subscriptionId": { "type": "string" }, "displayName": { "type": "string" }, "state": { "type": "string" } } } } } } } }, "Init-AllVMs": { "runAfter": { "Parse-AllSubscriptions": [ "Succeeded" ] }, "type": "InitializeVariable", "inputs": { "variables": [ { "name": "AllVMs", "type": "array", "value": [] }, { "name": "VMSkipToken", "type": "string", "value": "INIT" }, { "name": "VMFetchComplete", "type": "boolean", "value": false } ] } }, "ForEach-Subscription": { "foreach": "@body('Parse-AllSubscriptions')?['value']", "actions": { "Check-SubscriptionEnabled": { "actions": { "Reset-VMSkipToken": { "type": "SetVariable", "inputs": { "name": "VMSkipToken", "value": "INIT" } }, "Reset-VMFetchComplete": { "runAfter": { "Reset-VMSkipToken": [ "Succeeded" ] }, "type": "SetVariable", "inputs": { "name": "VMFetchComplete", "value": false } }, "Until": { "actions": { "Build-VMQuery-Paged": { "type": "Compose", "inputs": { "subscriptions": [ "@{items('ForEach-Subscription')?['subscriptionId']}" ], "query": "Resources | where type == 'microsoft.compute/virtualmachines' | extend VMName = tostring(name), ResourceGroup = tostring(resourceGroup), Location = tostring(location), OSType = tostring(properties.storageProfile.osDisk.osType), VMSize = tostring(properties.hardwareProfile.vmSize), ServerOwner = tostring(tags.ServerOwner), Environment = tostring(tags.Environment), SubscriptionId = tostring(subscriptionId), nicId = tolower(tostring(properties.networkProfile.networkInterfaces[0].id)), VmId = tolower(tostring(properties.vmId)) | join kind=leftouter (Resources | where type == 'microsoft.network/networkinterfaces' | extend privateIP = tostring(properties.ipConfigurations[0].properties.privateIPAddress) | project nicId = tolower(id), privateIP) on nicId | join kind=leftouter (Resources | where type == 'microsoft.compute/virtualmachines' | extend powerState = tostring(properties.extended.instanceView.powerState.displayStatus) | project id, powerState) on id | where powerState == 'VM running' | project VMName, ResourceGroup, Location, OSType, VMSize, ServerOwner, Environment = 'Azure', SubscriptionId, PrivateIP = privateIP, VmId, CloudEnvironment = 'Azure'", "options": { "$skipToken": "@if(equals(variables('VMSkipToken'), 'INIT'), '', variables('VMSkipToken'))" }, "$top": 1000 } }, "Get-VMs-Paged": { "runAfter": { "Build-VMQuery-Paged": [ "Succeeded" ] }, "type": "Http", "inputs": { "uri": "https://management.azure.com/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": "@outputs('Build-VMQuery-Paged')", "authentication": { "type": "ManagedServiceIdentity", "audience": "https://management.azure.com" } }, "runtimeConfiguration": { "contentTransfer": { "transferMode": "Chunked" } } }, "ForEach-VM-Result-Paged": { "foreach": "@body('Get-VMs-Paged')?['data']", "actions": { "Append-SingleVM-Paged": { "type": "AppendToArrayVariable", "inputs": { "name": "AllVMs", "value": "@items('ForEach-VM-Result-Paged')" } } }, "runAfter": { "Get-VMs-Paged": [ "Succeeded" ] }, "type": "Foreach" }, "Check-VMSkipToken": { "actions": { "Set-VMFetchComplete": { "type": "SetVariable", "inputs": { "name": "VMFetchComplete", "value": true } } }, "runAfter": { "ForEach-VM-Result-Paged": [ "Succeeded" ] }, "else": { "actions": { "Set-VMSkipToken": { "type": "SetVariable", "inputs": { "name": "VMSkipToken", "value": "@body('Get-VMs-Paged')?['$skipToken']" } } } }, "expression": { "or": [ { "equals": [ "@string(body('Get-VMs-Paged')?['$skipToken'])", "" ] } ] }, "type": "If" } }, "runAfter": { "Reset-VMFetchComplete": [ "Succeeded" ] }, "expression": "@equals(variables('VMFetchComplete'), true)", "limit": { "count": 50, "timeout": "PT1H" }, "type": "Until" } }, "else": { "actions": {} }, "expression": { "and": [ { "equals": [ "@items('ForEach-Subscription')?['state']", "Enabled" ] } ] }, "type": "If" } }, "runAfter": { "Init-AllVMs": [ "Succeeded" ] }, "type": "Foreach" }, "Init-MDEVariables": { "runAfter": { "ForEach-Subscription": [ "Succeeded" ] }, "type": "InitializeVariable", "inputs": { "variables": [ { "name": "AllMDEDevices", "type": "array" }, { "name": "MDESkip", "type": "integer", "value": 0 }, { "name": "MDEFetchComplete", "type": "boolean", "value": false } ] } }, "Paginate-MDEDevices": { "actions": { "Get-MDEDevices-Page": { "type": "Http", "inputs": { "uri": "https://api.securitycenter.microsoft.com/api/machines?$select=computerDnsName,id,osPlatform,lastSeen,onboardingStatus,healthStatus,lastIpAddress&$top=10000&$skip=@{variables('MDESkip')}", "method": "GET", "headers": { "Content-Type": "application/json" }, "authentication": { "type": "ManagedServiceIdentity", "audience": "https://api.securitycenter.microsoft.com" }, "retryPolicy": { "type": "fixed", "count": 3, "interval": "PT60S" } }, "runtimeConfiguration": { "contentTransfer": { "transferMode": "Chunked" } } }, "Parse-MDEPage": { "runAfter": { "Get-MDEDevices-Page": [ "Succeeded" ] }, "type": "ParseJson", "inputs": { "content": "@body('Get-MDEDevices-Page')", "schema": { "type": "object", "properties": { "value": { "type": "array", "items": { "type": "object", "properties": { "computerDnsName": { "type": [ "string", "null" ] }, "id": { "type": [ "string", "null" ] }, "osPlatform": { "type": [ "string", "null" ] }, "lastSeen": { "type": [ "string", "null" ] }, "onboardingStatus": { "type": [ "string", "null" ] }, "healthStatus": { "type": [ "string", "null" ] }, "lastIpAddress": { "type": [ "string", "null" ] }, "azureVmId": { "type": [ "string", "null" ] } } } } } } } }, "Append-MDEPage-ToArray": { "foreach": "@body('Parse-MDEPage')?['value']", "actions": { "Append-SingleMDEDevice": { "type": "AppendToArrayVariable", "inputs": { "name": "AllMDEDevices", "value": "@items('Append-MDEPage-ToArray')" } } }, "runAfter": { "Parse-MDEPage": [ "Succeeded" ] }, "type": "Foreach" }, "Check-PageSize": { "actions": { "Set-FetchComplete-True": { "type": "SetVariable", "inputs": { "name": "MDEFetchComplete", "value": true } } }, "runAfter": { "Append-MDEPage-ToArray": [ "Succeeded" ] }, "else": { "actions": { "Increment-MDESkip": { "type": "IncrementVariable", "inputs": { "name": "MDESkip", "value": 10000 } } } }, "expression": { "and": [ { "less": [ "@length(body('Parse-MDEPage')?['value'])", 10000 ] } ] }, "type": "If" } }, "runAfter": { "Init-MDEVariables": [ "Succeeded" ] }, "expression": "@equals(variables('MDEFetchComplete'), true)", "limit": { "count": 50, "timeout": "PT1H" }, "type": "Until" }, "Init-Variables": { "runAfter": { "Paginate-MDEDevices": [ "Succeeded" ] }, "type": "InitializeVariable", "inputs": { "variables": [ { "name": "EmailsSent", "type": "array", "value": [] }, { "name": "NoOwnerList", "type": "array", "value": [] }, { "name": "NonCompliantList", "type": "array", "value": [] }, { "name": "SummaryStats", "type": "object", "value": { "TotalNonCompliant": 0, "P1Critical": 0, "P2High": 0, "P3Medium": 0, "P4Low": 0, "EmailsSent": 0, "NoOwnerFound": 0 } }, { "name": "HTMLRows", "type": "string" }, { "name": "NonCompliantCount", "type": "integer", "value": 0 }, { "name": "CSVRows", "type": "string", "value": "@{concat('\"VM Name\",\"Private IP\",\"OS Type\",\"Location\",\"Server Owner\",\"MDE Status\",\"Last Seen\",\"Priority\",\"Action Taken\",\"Subscription ID\"', decodeUriComponent('%0A'))}" }, { "name": "HTMLRowCount", "type": "integer", "value": 0 } ] } }, "ForEach-AzureVM": { "foreach": "@variables('AllVMs')", "actions": { "Find-VMInMDE-Filter": { "type": "Query", "inputs": { "from": "@variables('AllMDEDevices')", "where": "@or(and(not(equals(item()?['azureVmId'], null)), not(equals(item()?['azureVmId'], '')), equals(toLower(item()?['azureVmId']), toLower(items('ForEach-AzureVM')?['VmId']))), and(or(equals(item()?['azureVmId'], null), equals(item()?['azureVmId'], '')), startsWith(toLower(item()?['computerDnsName']), toLower(items('ForEach-AzureVM')?['VMName'])), equals(item()?['lastIpAddress'], items('ForEach-AzureVM')?['PrivateIP'])))" } }, "Find-VMInMDE": { "runAfter": { "Find-VMInMDE-Filter": [ "Succeeded" ] }, "type": "Compose", "inputs": "@if(greater(length(body('Find-VMInMDE-Filter')), 0), first(body('Find-VMInMDE-Filter')), json('{\"computerDnsName\":\"NOT_FOUND\",\"onboardingStatus\":\"NotFound\",\"lastSeen\":\"1900-01-01T00:00:00Z\",\"lastIpAddress\":\"N/A\",\"healthStatus\":\"Unknown\"}'))" }, "Get-ComplianceStatus": { "runAfter": { "Find-VMInMDE": [ "Succeeded" ] }, "type": "Compose", "inputs": "@if(equals(outputs('Find-VMInMDE')?['computerDnsName'], 'NOT_FOUND'), 'Not Onboarded', if(equals(outputs('Find-VMInMDE')?['onboardingStatus'], 'Onboarded'), if(greater(outputs('Find-VMInMDE')?['lastSeen'], addHours(utcNow(), mul(-1, outputs('CONFIG')?['MDE_LASTSEEN_HOURS']))), 'Compliant', 'Onboarded - Not Reporting'), 'Not Onboarded'))" }, "Get-Priority": { "runAfter": { "Get-ComplianceStatus": [ "Succeeded" ] }, "type": "Compose", "inputs": "@if(equals(outputs('Get-ComplianceStatus'), 'Not Onboarded'), 'P2 - High', if(equals(outputs('Get-ComplianceStatus'), 'Onboarded - Not Reporting'), 'P3 - Medium', if(equals(outputs('Get-ComplianceStatus'), 'Compliant'), 'Compliant', 'P4 - Low')))" }, "Is-NonCompliant": { "actions": { "Append-CSVRows": { "type": "AppendToStringVariable", "inputs": { "name": "CSVRows", "value": "\"@{items('ForEach-AzureVM')?['VMName']}\",\"@{if(equals(items('ForEach-AzureVM')?['PrivateIP'], ''), 'N/A', items('ForEach-AzureVM')?['PrivateIP'])}\",\"@{items('ForEach-AzureVM')?['OSType']}\",\"@{items('ForEach-AzureVM')?['Location']}\",\"@{if(equals(items('ForEach-AzureVM')?['ServerOwner'], ''), 'No Owner Tag', items('ForEach-AzureVM')?['ServerOwner'])}\",\"@{outputs('Get-ComplianceStatus')}\",\"@{if(equals(outputs('Find-VMInMDE')?['onboardingStatus'], 'Onboarded'), if(equals(outputs('Find-VMInMDE')?['lastSeen'], '1900-01-01T00:00:00Z'), 'Never', concat(convertTimeZone(outputs('Find-VMInMDE')?['lastSeen'], 'UTC', 'India Standard Time', 'dd-MM-yyyy HH:mm:ss'), ' (', string(div(sub(ticks(utcNow()), ticks(outputs('Find-VMInMDE')?['lastSeen'])), 864000000000)), ' days ago)')), concat(outputs('Find-VMInMDE')?['onboardingStatus'], ' - Last Seen: ', convertTimeZone(outputs('Find-VMInMDE')?['lastSeen'], 'UTC', 'India Standard Time', 'dd-MM-yyyy HH:mm:ss')))}\",\"@{outputs('Get-Priority')}\",\"@{if(equals(items('ForEach-AzureVM')?['ServerOwner'], ''), 'IT Team Notified', 'Email sent to Server Owner')}\",\"@{items('ForEach-AzureVM')?['SubscriptionId']}\"@{decodeUriComponent('%0A')}" } }, "Check-HTMLRowCount": { "actions": { "Append-HTMLRows": { "type": "AppendToStringVariable", "inputs": { "name": "HTMLRows", "value": "<tr><td style=\"padding:8px 10px;border:1px solid #ddd;font-weight:600;\">@{items('ForEach-AzureVM')?['VMName']}</td><td style=\"padding:8px 10px;border:1px solid #ddd;word-wrap:break-word;\">@{if(equals(items('ForEach-AzureVM')?['PrivateIP'], ''), 'N/A', items('ForEach-AzureVM')?['PrivateIP'])}</td><td style=\"padding:8px 10px;border:1px solid #ddd;word-wrap:break-word;\">@{items('ForEach-AzureVM')?['OSType']}</td><td style=\"padding:8px 10px;border:1px solid #ddd;word-wrap:break-word;\">@{items('ForEach-AzureVM')?['Location']}</td><td style=\"padding:8px 10px;border:1px solid #ddd;word-wrap:break-word;\">@{if(equals(items('ForEach-AzureVM')?['ServerOwner'], ''), 'No Owner Tag', items('ForEach-AzureVM')?['ServerOwner'])}</td><td style=\"padding:8px 10px;border:1px solid #ddd;color:#c80000;\">@{outputs('Get-ComplianceStatus')}</td><td style=\"padding:8px 10px;border:1px solid #ddd;word-wrap:break-word;\">@{if(equals(outputs('Find-VMInMDE')?['onboardingStatus'], 'Onboarded'), if(equals(outputs('Find-VMInMDE')?['lastSeen'], '1900-01-01T00:00:00Z'), 'Never', concat(convertTimeZone(outputs('Find-VMInMDE')?['lastSeen'], 'UTC', 'India Standard Time', 'dd-MM-yyyy HH:mm:ss'), ' (', string(div(sub(ticks(utcNow()), ticks(outputs('Find-VMInMDE')?['lastSeen'])), 864000000000)), ' days ago)')), concat(outputs('Find-VMInMDE')?['onboardingStatus'], ' - Last Seen: ', convertTimeZone(outputs('Find-VMInMDE')?['lastSeen'], 'UTC', 'India Standard Time', 'dd-MM-yyyy HH:mm:ss')))}</td><td style=\"padding:8px 10px;border:1px solid #ddd;word-wrap:break-word;\">@{outputs('Get-Priority')}</td><td style=\"padding:8px 10px;border:1px solid #ddd;word-wrap:break-word;\">@{if(equals(items('ForEach-AzureVM')?['ServerOwner'], ''), 'IT Team Notified', 'Email sent to Server Owner')}</td><td style=\"padding:8px 10px;border:1px solid #ddd;word-break:break-all;\">@{items('ForEach-AzureVM')?['SubscriptionId']}</td></tr>" } }, "Increment-HTMLRowCount": { "runAfter": { "Append-HTMLRows": [ "Succeeded" ] }, "type": "IncrementVariable", "inputs": { "name": "HTMLRowCount", "value": 1 } } }, "runAfter": { "Append-CSVRows": [ "Succeeded" ] }, "else": { "actions": {} }, "expression": { "and": [ { "less": [ "@variables('HTMLRowCount')", 20 ] } ] }, "type": "If" }, "Increment-NonCompliantCount": { "runAfter": { "Check-HTMLRowCount": [ "Succeeded" ] }, "type": "IncrementVariable", "inputs": { "name": "NonCompliantCount", "value": 1 } }, "Check-ServerOwner": { "actions": { "Send-OwnerEmail": { "type": "Http", "inputs": { "uri": "@{concat('https://graph.microsoft.com/v1.0/users/', encodeURIComponent(variables('varSenderEmail')), '/sendMail')}", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "message": { "subject": "[@{outputs('Get-Priority')}] MDE Compliance Alert - @{items('ForEach-AzureVM')?['VMName']}", "body": { "contentType": "HTML", "content": "<html><body style=\"font-family:Segoe UI,Arial,sans-serif;color:#1a1a1a;\"><div style=\"max-width:680px;margin:24px auto;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden;\"><div style=\"background:#c80000;padding:20px 28px;\"><h2 style=\"color:#fff;margin:0;\">MDE Compliance Alert</h2><p style=\"color:#ffcccc;margin:6px 0 0;font-size:13px;\">Priority: @{outputs('Get-Priority')}</p></div><div style=\"padding:28px;\"><p style=\"margin-top:0;font-size:14px;\">Your server <strong>@{items('ForEach-AzureVM')?['VMName']}</strong> has a Microsoft Defender for Endpoint compliance issue requiring immediate attention.</p><table style=\"width:100%;border-collapse:collapse;font-size:14px;\"><thead><tr style=\"background:#f5f5f5;\"><th style=\"text-align:left;padding:10px 14px;border:1px solid #ddd;width:38%;\">Field</th><th style=\"text-align:left;padding:10px 14px;border:1px solid #ddd;word-wrap:break-word;\">Value</th></tr></thead><tbody><tr><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:600;\">Server Name</td><td style=\"padding:9px 14px;border:1px solid #ddd;word-wrap:break-word;\">@{items('ForEach-AzureVM')?['VMName']}</td></tr><tr style=\"background:#fafafa;\"><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:600;\">Private IP</td><td style=\"padding:9px 14px;border:1px solid #ddd;word-wrap:break-word;\">@{if(equals(items('ForEach-AzureVM')?['PrivateIP'], ''), 'N/A', items('ForEach-AzureVM')?['PrivateIP'])}</td></tr><tr><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:600;\">OS Type</td><td style=\"padding:9px 14px;border:1px solid #ddd;word-wrap:break-word;\">@{items('ForEach-AzureVM')?['OSType']}</td></tr><tr style=\"background:#fafafa;\"><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:600;\">Location</td><td style=\"padding:9px 14px;border:1px solid #ddd;word-wrap:break-word;\">@{items('ForEach-AzureVM')?['Location']}</td></tr><tr><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:600;\">Compliance Status</td><td style=\"padding:9px 14px;border:1px solid #ddd;color:#c80000;font-weight:700;\">@{outputs('Get-ComplianceStatus')}</td></tr><tr style=\"background:#fafafa;\"><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:600;\">Priority</td><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:700;\">@{outputs('Get-Priority')}</td></tr><tr><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:600;\">MDE Onboarding Status</td><td style=\"padding:9px 14px;border:1px solid #ddd;word-wrap:break-word;\">@{outputs('Find-VMInMDE')?['onboardingStatus']}</td></tr><tr style=\"background:#fafafa;\"><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:600;\">Last Seen in MDE (IST)</td><td style=\"padding:9px 14px;border:1px solid #ddd;word-wrap:break-word;\">@{if(equals(outputs('Find-VMInMDE')?['lastSeen'], '1900-01-01T00:00:00Z'), 'Never', concat(convertTimeZone(outputs('Find-VMInMDE')?['lastSeen'], 'UTC', 'India Standard Time', 'dd-MM-yyyy HH:mm:ss'), ' (', string(div(sub(ticks(utcNow()), ticks(outputs('Find-VMInMDE')?['lastSeen'])), 864000000000)), ' days ago)'))}</td></tr><tr><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:600;\">Resource Group</td><td style=\"padding:9px 14px;border:1px solid #ddd;word-wrap:break-word;\">@{items('ForEach-AzureVM')?['ResourceGroup']}</td></tr><tr style=\"background:#fafafa;\"><td style=\"padding:9px 14px;border:1px solid #ddd;font-weight:600;\">Subscription ID</td><td style=\"padding:9px 14px;border:1px solid #ddd;word-break:break-all;\">@{items('ForEach-AzureVM')?['SubscriptionId']}</td></tr></tbody></table><br/><table style=\"width:100%;border-collapse:collapse;\"><tr style=\"background:#fff8e1;\"><td style=\"padding:10px 14px;border:1px solid #ffe082;font-size:13px;\"><strong>Resolution SLA:</strong> P1 Critical - 24hrs | P2 High - 48hrs | P3 Medium - 72hrs</td></tr></table><br/><p style=\"font-size:13px;color:#555;\">For assistance contact IT Security: <a href=\"mailto:@{variables('varITTeamEmail')}\">@{variables('varITTeamEmail')}</a></p></div></div></body></html>" }, "toRecipients": [ { "emailAddress": { "address": "@{items('ForEach-AzureVM')?['ServerOwner']}" } } ], "ccRecipients": [ { "emailAddress": { "address": "@variables('varITTeamEmail')" } } ] }, "saveToSentItems": "true" }, "authentication": { "type": "ManagedServiceIdentity", "audience": "https://graph.microsoft.com" }, "retryPolicy": { "type": "fixed", "count": 2, "interval": "PT60S" } }, "runtimeConfiguration": { "contentTransfer": { "transferMode": "Chunked" } } }, "Append-EmailsSent": { "runAfter": { "Send-OwnerEmail": [ "Succeeded" ] }, "type": "AppendToArrayVariable", "inputs": { "name": "EmailsSent", "value": "@{items('ForEach-AzureVM')?['VMName']} → @{items('ForEach-AzureVM')?['ServerOwner']}" } } }, "runAfter": { "Increment-NonCompliantCount": [ "Succeeded" ] }, "else": { "actions": { "Append-NoOwnerList": { "type": "AppendToArrayVariable", "inputs": { "name": "NoOwnerList", "value": "<tr><td style=\"padding:8px 10px;border:1px solid #ddd;font-weight:600;\">@{items('ForEach-AzureVM')?['VMName']}</td><td style=\"padding:8px 10px;border:1px solid #ddd;word-wrap:break-word;\">@{if(equals(items('ForEach-AzureVM')?['PrivateIP'], ''), 'N/A', items('ForEach-AzureVM')?['PrivateIP'])}</td><td style=\"padding:8px 10px;border:1px solid #ddd;word-wrap:break-word;\">@{outputs('Get-ComplianceStatus')}</td><td style=\"padding:8px 10px;border:1px solid #ddd;font-weight:700;\">@{outputs('Get-Priority')}</td></tr>" } } } }, "expression": { "and": [ { "not": { "equals": [ "@items('ForEach-AzureVM')?['ServerOwner']", "" ] } } ] }, "type": "If" } }, "runAfter": { "Get-Priority": [ "Succeeded" ] }, "else": { "actions": {} }, "expression": { "and": [ { "not": { "equals": [ "@outputs('Get-ComplianceStatus')", "Compliant" ] } } ] }, "type": "If" } }, "runAfter": { "Init-Variables": [ "Succeeded" ] }, "type": "Foreach", "runtimeConfiguration": { "concurrency": { "repetitions": 1 } } }, "Check-AnyNonCompliant": { "actions": { "Send-ITSummaryEmail": { "type": "Http", "inputs": { "uri": "@{concat('https://graph.microsoft.com/v1.0/users/', encodeURIComponent(variables('varSenderEmail')), '/sendMail')}", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "message": { "subject": "MDE Compliance Report (Azure Workloads) - @{variables('NonCompliantCount')} Non-Compliant VMs Found", "body": { "contentType": "HTML", "content": "<html><body style=\"font-family:Segoe UI,Arial,sans-serif;color:#1a1a1a;\"><div style=\"max-width:1400px;margin:24px auto;border:1px solid #e0e0e0;border-radius:8px;\"><div style=\"background:#0078d4;padding:20px 28px;\"><h2 style=\"color:#fff;margin:0;\">MDE Compliance Daily Report</h2><p style=\"color:#cce4ff;margin:6px 0 0;font-size:13px;\">Generated: @{convertTimeZone(utcNow(), 'UTC', 'India Standard Time', 'dd-MM-yyyy HH:mm:ss')} IST</p></div><div style=\"padding:28px;\"><table style=\"border-collapse:collapse;font-size:14px;margin-bottom:28px;\"><thead><tr style=\"background:#f0f0f0;\"><th style=\"padding:10px 18px;border:1px solid #ddd;word-wrap:break-word;\">Metric</th><th style=\"padding:10px 18px;border:1px solid #ddd;word-wrap:break-word;\">Value</th></tr></thead><tbody><tr><td style=\"padding:9px 18px;border:1px solid #ddd;word-wrap:break-word;\">Total Non-Compliant VMs</td><td style=\"padding:9px 18px;border:1px solid #ddd;font-weight:700;color:#c80000;\">@{variables('NonCompliantCount')}</td></tr><tr style=\"background:#fafafa;\"><td style=\"padding:9px 18px;border:1px solid #ddd;word-wrap:break-word;\">Server Owners Notified</td><td style=\"padding:9px 18px;border:1px solid #ddd;color:#107c10;font-weight:600;\">@{length(variables('EmailsSent'))}</td></tr><tr><td style=\"padding:9px 18px;border:1px solid #ddd;word-wrap:break-word;\">No Owner Tag</td><td style=\"padding:9px 18px;border:1px solid #ddd;color:#e65100;font-weight:600;\">@{length(variables('NoOwnerList'))}</td></tr></tbody></table><p style=\"background:#fff3cd;border:1px solid #ffc107;padding:10px 14px;border-radius:4px;font-size:13px;margin-bottom:16px;\">This report shows the first <strong>20 non-compliant VMs</strong> only. <strong>Please check the attached CSV file</strong> for the complete list.</p><table style=\"width:100%;table-layout:fixed;border-collapse:collapse;font-size:13px;\"><colgroup><col style=\"width:120px\"><col style=\"width:90px\"><col style=\"width:70px\"><col style=\"width:100px\"><col style=\"width:160px\"><col style=\"width:110px\"><col style=\"width:165px\"><col style=\"width:80px\"><col style=\"width:90px\"><col style=\"width:195px\"></colgroup><thead><tr style=\"background:#0078d4;color:#fff;\"><th style=\"padding:10px 12px;border:1px solid #005a9e;\">VM Name</th><th style=\"padding:10px 12px;border:1px solid #005a9e;\">Private IP</th><th style=\"padding:10px 12px;border:1px solid #005a9e;\">OS Type</th><th style=\"padding:10px 12px;border:1px solid #005a9e;\">Location</th><th style=\"padding:10px 12px;border:1px solid #005a9e;\">Server Owner</th><th style=\"padding:10px 12px;border:1px solid #005a9e;\">MDE Status</th><th style=\"padding:10px 12px;border:1px solid #005a9e;\">Last Seen (IST)</th><th style=\"padding:10px 12px;border:1px solid #005a9e;\">Priority</th><th style=\"padding:10px 12px;border:1px solid #005a9e;\">Action Taken</th><th style=\"padding:10px 12px;border:1px solid #005a9e;\">Subscription ID</th></tr></thead><tbody>@{variables('HTMLRows')}</tbody></table><br/><h3 style=\"border-bottom:2px solid #e65100;padding-bottom:8px;\">Action Required - No Owner Tag Found</h3><div style=\"background:#fff8f0;border:1px solid #ffccbc;padding:16px;border-radius:4px;font-size:13px;margin-bottom:16px;\"><p style=\"margin:0 0 8px 0;\">The following <strong>@{length(variables('NoOwnerList'))}</strong> server(s) have no <strong>ServerOwner</strong> tag assigned.</p><ol style=\"margin:0;padding-left:20px;\"><li style=\"margin-bottom:6px;\">Identify the owner of each server below</li><li style=\"margin-bottom:6px;\">Go to the VM in Azure Portal → Tags → Add tag</li><li style=\"margin-bottom:6px;\"><strong>Tag Name:</strong> ServerOwner | <strong>Tag Value:</strong> owner email address</li><li>Once tagged, the next daily report will automatically notify the owner</li></ol></div><table style=\"width:100%;table-layout:fixed;border-collapse:collapse;font-size:13px;\"><thead><tr style=\"background:#e65100;color:#fff;\"><th style=\"padding:10px 12px;border:1px solid #bf360c;text-align:left;\">VM Name</th><th style=\"padding:10px 12px;border:1px solid #bf360c;text-align:left;\">Private IP</th><th style=\"padding:10px 12px;border:1px solid #bf360c;text-align:left;\">MDE Status</th><th style=\"padding:10px 12px;border:1px solid #bf360c;text-align:left;\">Priority</th></tr></thead><tbody>@{if(equals(length(variables('NoOwnerList')), 0), '<tr><td colspan=\"4\" style=\"padding:12px;text-align:center;\">None - All servers have owner tags assigned</td></tr>', join(variables('NoOwnerList'), ''))}</tbody></table></div></div></body></html>" }, "toRecipients": [ { "emailAddress": { "address": "@variables('varITTeamEmail')" } } ], "attachments": [ { "@@odata.type": "#microsoft.graph.fileAttachment", "name": "@{concat('MDE-Compliance-Report-', convertTimeZone(utcNow(), 'UTC', 'India Standard Time', 'dd-MM-yyyy'), '.csv')}", "contentType": "text/csv", "contentBytes": "@{base64(variables('CSVRows'))}" } ] }, "saveToSentItems": "true" }, "authentication": { "type": "ManagedServiceIdentity", "audience": "https://graph.microsoft.com" } }, "runtimeConfiguration": { "contentTransfer": { "transferMode": "Chunked" } } } }, "runAfter": { "ForEach-AzureVM": [ "Succeeded" ] }, "else": { "actions": { "Send-AllClearEmail": { "type": "Http", "inputs": { "uri": "@{concat('https://graph.microsoft.com/v1.0/users/', encodeURIComponent(variables('varSenderEmail')), '/sendMail')}", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "message": { "subject": "[@{convertTimeZone(utcNow(), 'UTC', 'India Standard Time', 'dd-MM-yyyy')}] MDE Compliance Report - All VMs Compliant", "body": { "contentType": "HTML", "content": "<html><body style=\"font-family:Segoe UI,Arial,sans-serif;color:#1a1a1a;\"><div style=\"max-width:600px;margin:24px auto;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden;\"><div style=\"background:#107c10;padding:20px 28px;\"><h2 style=\"color:#fff;margin:0;\">MDE Compliance Report</h2><p style=\"color:#c8e6c9;margin:6px 0 0;font-size:13px;\">Generated: @{convertTimeZone(utcNow(), 'UTC', 'India Standard Time', 'dd-MM-yyyy HH:mm:ss')} IST</p></div><div style=\"padding:28px;text-align:center;\"><h2 style=\"color:#107c10;\">All VMs Compliant</h2><p style=\"font-size:15px;color:#555;\">All Azure Virtual Machines are onboarded to Microsoft Defender for Endpoint and reporting within the required 24-hour window.</p><p style=\"font-size:13px;color:#888;\">No action required. The next report will be sent tomorrow at 08:00 IST.</p></div></div></body></html>" }, "toRecipients": [ { "emailAddress": { "address": "@variables('varITTeamEmail')" } } ] }, "saveToSentItems": "true" }, "authentication": { "type": "ManagedServiceIdentity", "audience": "https://graph.microsoft.com" } }, "runtimeConfiguration": { "contentTransfer": { "transferMode": "Chunked" } } } } }, "expression": { "and": [ { "greater": [ "@variables('NonCompliantCount')", 0 ] } ] }, "type": "If" } }, "parameters": { "$connections": { "type": "Object", "defaultValue": {} } } }, "parameters": { "$connections": { "type": "Object", "value": {} } } }Microsoft 365 Developer E5 license lacking endpoints and device on defender portal
Dear Support Team, I am a microsoft certified trainer (MCT). I currently have a Microsoft 365 Developer E5 license assigned to my tenant. However, I have noticed that my Microsoft Defender portal (security.microsoft.com) is missing several critical features. For example, I cannot see the Endpoints or Devices menus, which is preventing me from implementing and testing Microsoft Defender for Endpoint. Additionally, my Azure tenant and Microsoft 365 tenant are separate. This has created challenges when configuring security services such as Microsoft Sentinel (SIEM), as certain prerequisites and integrations require configuration through the Microsoft Defender portal. Due to the missing Defender features, I am unable to complete the necessary setup. I would appreciate your assistance in understanding: Why the Endpoints and Devices sections are unavailable in my Defender portal despite having a Microsoft 365 Developer E5 license. Whether additional licensing, onboarding steps, or tenant configurations are required to enable Microsoft Defender for Endpoint features. How best to integrate or align my separate Azure and Microsoft 365 tenants to support services such as Microsoft Sentinel and Defender XDR. These issues are significantly impacting my ability to evaluate and implement Microsoft's security solutions. I would appreciate any guidance or recommendations to resolve them. Thank you for your assistance. Kind regards, [Your Name]12Views0likes0CommentsPrompted to sign in to Microsoft Defender Platform on W11/W2025 using Entra
Hi Microsoft Defender XDR community, Since around May 18th, our users on devices that are onboarded to Microsoft Defender for Endpoint are being prompted to sign-in to the following application using Entra on login to Windows. Application Microsoft Defender Platform Application ID cab96880-db5b-4e15-90a7-f3f1d62ffe39 Is anyone aware of a change that requires user sign-in to Entra as a requirement for Microsoft Defender for Endpoint? I have tried raising a support topic on this topic. Regards Chris316Views0likes4CommentsReduce unnecessary internet exposure with Microsoft Defender
In today’s threat landscape, internet exposure, i.e. devices that allow inbound connectivity from the public internet, continues to be a major vector for initial access and compromise. Devices that are exposed to the public internet can significantly increase an organization’s attack surface, making them prime targets for initial access, exploitation, and lateral movement. However, not all internet-facing devices represent a security issue. Many are intentionally exposed to support business-critical scenarios such as hosting web applications, enabling remote access, or supporting communication services. The challenge for security teams is not just detecting internet-facing devices, but understanding why a device is exposed, whether that exposure is expected, and what action should be taken. That’s why we’re introducing a new security recommendation in Microsoft Defender that helps organizations identify, review, and reduce unnecessary internet exposure across their environment. Understand your internet-facing exposure This recommendation focuses specifically on devices that are accessible from the public internet, meaning they can receive inbound connections initiated from external sources, not devices that only use the internet for outbound communication. Externally reachable assets are often the first point of entry for attackers, making this a critical signal for security prioritization. Microsoft Defender identifies internet-facing devices based on signals that indicate external inbound reachability, including: External scan telemetry identifying devices reachable from the public internet Network telemetry showing inbound connections from external sources By correlating these signals, Defender surfaces devices that are externally reachable. Introducing internet-facing exposure assessment A new recommendation in Microsoft Defender provides a centralized view of devices that are externally reachable from the public internet, helping you understand and manage exposure across your environment. This assessment categorizes devices based on their exposure state: Exposed devices: Devices that are reachable from the public internet and require review Compliant devices: Devices that are not externally reachable, or where the internet exposure has been explicitly validated and accepted by the organization’s security team as intended Not applicable devices: Devices that do not exhibit inbound internet exposure From the recommendation view, you can: Drill down into exposed devices and understand why they are reachable Review context such as exposed services and connectivity Explore device-level details to support investigation Track exposure posture across your environment over time Take action on your internet exposure To access this recommendation in the Defender portal, navigate to Exposure management → Recommendations → Devices → Misconfigurations. Once Defender identifies internet-facing devices, it provides the context needed to review and take action. Your action plan 1. Assess your exposure Review the recommendation to understand which devices in your environment are externally reachable from the public internet and why they were classified as internet-facing. 2. Validate whether exposure is required Determine if the inbound connectivity is expected for each device. Confirm business need and ownership before taking action. 3. Prioritize high-risk assets Focus on critical servers or sensitive environments that are exposed to the internet, as they present the highest risk for initial access. 4. Reduce unnecessary exposure Restrict or remove inbound connectivity where it is not required by closing exposed ports, removing public access, or moving services behind controlled access layers. 5. Track and maintain posture over time Continuously monitor internet-facing devices to ensure unnecessary exposure is reduced and new exposure is validated as environments evolve. FAQ 1. Which devices are currently supported? This recommendation applies to supported Windows client and Windows Server devices. Supported versions include Windows 10, version 1607 and earlier; Windows 10, version 1809 and later; and Windows 11. 2. Why might there be differences between this recommendation and the Internet-facing filter in device inventory? This recommendation reflects devices observed as internet-facing during the recommendation assessment window. Device exposure can change over time, and different Microsoft Defender experiences may refresh at different times. As a result, temporary differences may occur between this recommendation and the Internet-facing filter in device inventory. For the most current device-level view, use the Internet-facing filter in device inventory. If a device was recently remediated or its exposure recently changed, allow time for the recommendation status to refresh. 3. Could regular employee laptops or personal devices appear as internet-facing? This recommendation evaluates supported devices onboarded to Microsoft Defender for Endpoint and focuses specifically on inbound internet reachability. Typical internet usage, such as web browsing, generates outbound traffic and does not by itself classify a device as internet-facing. Devices are identified as internet-facing only when they are externally reachable from the public internet. As a result: Personal devices that are not onboarded to Microsoft Defender for Endpoint are not included in this assessment. Corporate laptops may appear as internet-facing if they are directly reachable from the internet, which may indicate an unintended network exposure or configuration issue. Learn more For additional guidance on investigating and managing internet-facing devices, see: Learn how Defender identifies and maps externally reachable devices across your environment Discovering internet-facing devices using Microsoft Defender Learn how to review and investigate internet-facing device exposure Investigate devices in Microsoft Defender Learn more about Microsoft Secure Score for Devices in Microsoft Defender To learn more about endpoint protection with Microsoft Defender, check out our website. To learn more about Microsoft Security solutions, visit our website. Bookmark the Security blog to keep up with our expert coverage on security matters. Follow us on LinkedIn (Microsoft Security) and X (@MSFTSecurity) for the latest news and updates on cybersecurity.Elevate your telemetry using custom data collection in Microsoft Defender
At Ignite in November, we announced that Microsoft Defender is now the only endpoint protection solution that allows data-hungry security teams to meet specific telemetry needs by optimizing their data collection right within the Defender portal, without the need to rely on fragmented and siloed solutions. Since then, we've heard from customers that this tool has been a game changer, enabling them to hunt through new data types as well as richer data on events already reported. The release of custom data collection was a key milestone in our ongoing journey to make Defender easy to manage and customize. Security teams have been asking for guidance and examples of how to get the most out of the tool, so today we're sharing how some organizations can use custom data collection and dynamic tagging to detect command and control (C2) communications, giving defenders elevated visibility and deeper telemetry into attacker activity across the environment. See the data you want to see Defender's default telemetry is tuned to balance performance and signal-to-noise across millions of devices, so it focuses on the events most useful for high-fidelity detection at fleet scale, but many organizations want richer, more granular signals for deeper hunting, compliance, or auditing purposes. Custom data collection lets you go beyond what Defender already captures without ever leaving the Defender portal. Easily build custom collection rules based on your organization’s specific needs using natural language; no PhD required! It includes several highly requested data types, including AMSI for hunting over script content, and Kerberos for hunting auth-based and network attacks. This truly integrated custom data offering is possible thanks to Microsoft’s platform approach, as the additional telemetry can be collected and analyzed via Defender and stored via Microsoft Sentinel. It puts you in complete control of any customized, add-on data, including exactly which data types are collected and how long they are stored. No other security solution has fully integrated and customizable telemetry collection and analysis. Example custom telemetry scenario: detecting C2 communications Many organizations have a set of assets that require special attention, like internet-facing servers, domain controllers, and other high-value endpoints where deeper telemetry can make the difference between catching an intrusion early and discovering it after the damage is done. Imagine your organization has received threat intelligence on attacks using stealthy C2 frameworks: HTTPS beacons with jittered intervals, DNS-based data exchange, and persistence via scheduled tasks and registry modifications. You want richer visibility into those internet-facing servers and high-value endpoints so you can hunt for these patterns proactively, instead of reconstructing them after the fact. Dynamic tags scope these high-value devices into a targeted group, and custom data collection captures the extra process, network, and registry events from them, giving analysts the telemetry they need to hunt for beaconing, suspicious DNS patterns, and persistence before attackers establish a foothold. To detect C2 communications using dynamic tagging, follow these steps: Step 1: Tag your devices Custom Data Collection rules are scoped to dynamic tags; once set, those tags are automatically applied and removed based on conditions you define. Configure them in Settings > Microsoft Defender XDR > Asset Rule Management. Tag Rule name Conditions Tag to apply Internet-facing servers InternetFacing-Servers Internet facing = true AND OS platform equals Windows Server 2022 C2-Watchlist Devices under active investigation HighSev-Investigation Manual tag equals UnderInvestigation HighSev-Verbose Bringing manual tags into the dynamic model Custom data collection is built around dynamic tags by design: one leading, unified tagging experience that's more flexible and customizable. Dynamic tags can be driven by device properties, group membership, OS, or by existing manual tags, so anything your team already tags manually flows naturally into custom data collection through a simple Asset Rule Management rule, exactly as Tag 2 above does. In this example, analysts manually tag a device UnderInvestigation during incident response. The dynamic rule picks up that manual tag and applies HighSev-Verbose, which custom data collection rules can target. The analyst doesn't need to know about dynamic tags they tag the device the way they always have, and custom data collection activates automatically. Step 2: Build your collection rules Navigate to Settings > Endpoints > Rules > Custom Data Collection. Select your Microsoft Sentinel workspace in the top-right corner. Before creating rules, confirm you meet every prerequisite in the custom data collection documentation , in particular, your tenant must be onboarded to the Unified Security Operations Platform (USOP). Rule 1: Outbound network connections from high-risk processes Capture connections from processes commonly abused by C2 frameworks living-off-the-land binaries and scripting engines. Setting Value Rule name C2-OutboundConnections Table DeviceCustomNetworkEvents Action Connection Success Condition InitiatingProcessFileName Equals: powershell.exe, rundll32.exe, regsvr32.exe, mshta.exe, certutil.exe, msiexec.exe Scope Devices tagged C2-Watchlist Rule 2: DNS query activity Many C2 frameworks use DNS for beaconing or data exchange. Default telemetry captures limited DNS data. This rule collects all DNS queries from monitored devices. Setting Value Rule name C2-DNSActivity Table DeviceCustomNetworkEvents Action Connection Success Condition RemotePort equals 53 Scope Devices tagged C2-Watchlist Rule 3: Persistence mechanisms C2 implants establish persistence via scheduled tasks, registry run keys, or services. Capture process creation events for common persistence tools. Setting Value Rule name C2-Persistence Table DeviceCustomProcessEvents Action Process Created Condition FileName in (schtasks.exe, reg.exe, sc.exe, at.exe) Scope Devices tagged C2-Watchlist Rule 4: Full process and script telemetry during investigations When a device gets the HighSev-Verbose tag, collect everything. Setting Value Rule name HighSev-AllProcesses Table DeviceCustomProcessEvents Action Process Created Condition Broad (all process creation events) Scope Devices tagged HighSev-Verbose Setting Value Rule name HighSev-ScriptCapture Table DeviceCustomScriptEvents Action Script execution Condition Broad (all script events) – add a condition which is always true such as FileName not equals “” Scope Devices tagged HighSev-Verbose Collection profiles summary Tag Rules active What gets collected Use case C2-Watch list OutboundConnections, DNSActivity, Persistence Network connections from, DNS queries, persistence tool usage, DLL sideloading Persistent C2 monitoring HighSev-Verbose AllProcesses, ScriptCapture Every process creation, all script execution Full-depth incident response Important: when you remove the HighSev-Verbose tag after closing an incident, collection automatically drops back to baseline, no manual rule cleanup needed. This is what makes verbose collection safe to leave configured: it's only active while the tag is. Step 3: Hunt Rules deploy within 20 minutes to an hour. Query the data in AH directly. Detect beaconing patterns processes making regular-interval outbound connections: Find DNS queries to high-entropy domains (potential DGA): Spot persistence being established: Leverage the telemetry from your new collection rule into a Custom Detection so high-value findings raise alerts automatically, instead of waiting for the next manual hunt. Custom data collection effectively extends your endpoint protection into a targeted, general-purpose log collector, one that's now ready to serve advanced hunting, custom detections, and auditing or regulatory use cases, while default fleet-wide telemetry stays tuned for performance and signal-to-noise. By combining dynamic tagging with purpose-built collection rules, your highest-risk devices are always streaming the signals that matter most, ready for detection and investigation before and during an incident. Learn more To learn more about endpoint protection with Microsoft Defender, check out our website. To learn more about Microsoft Security solutions, visit our website. Bookmark the Security blog to keep up with our expert coverage on security matters. Follow us on LinkedIn (Microsoft Security) and X (@MSFTSecurity) for the latest news and updates on cybersecurity. To learn more about custom data collection and how to get started, see our documentation.Introducing scheduled antivirus scans on Microsoft Defender Linux
Security teams rely on scheduled scans to ensure consistent coverage across devices, detect dormant or missed threats, and meet compliance requirements. However, managing scans on Linux has traditionally required custom scripts and cron-based setups, which can be hard to scale and maintain. That’s why we’re excited to introduce centrally managed scheduled antivirus scans for Linux in Microsoft Defender, now available in public preview. With this release, we are bringing built-in, flexible scheduling capabilities directly into Defender - making it easier to manage and standardize scan behaviour across Linux environments. What’s new With this capability, customers can now configure scheduled antivirus scans on Linux using security settings management policies in the Microsoft Defender portal for centralized policy enforcement or local Managed JSON configuration that can be deployed via configuration management tools like ansible, puppet and chef. The feature supports a flexible set of scheduling options, including hourly quick scans (interval-based scheduling), daily quick scans at a defined time, and weekly scans with configurable scan type (quick or full). In addition, customers can control how scans run with advanced options such as: Running scans only when the device is idle Reducing CPU impact using low CPU priority Checking for definition updates before scanning Randomizing scans start times Ignoring exclusions during scheduled scans These capabilities allow security teams to balance coverage, performance, and operational needs across large Linux environments. Why this matters From a security perspective, scheduled scans play a critical role in detecting dormant threats, missed detections, and malicious artifacts that may not be caught through real-time protection alone. Without consistent and centrally enforced scheduling, these gaps can increase risk across the environment. With this release, scheduled scans are now: Centrally managed through Defender policies Consistently enforced across devices Aligned with security best practices for regular scanning Integrated into the broader Defender security posture This helps organizations strengthen their overall security posture while reducing operational complexity. Get started To get started, ensure devices are running agent version 101.26032.0000 or later (production ring), and configure scheduled scans using managed JSON or Defender portal policies. Learn more Learn more about how to schedule antivirus scans on Linux To learn more about endpoint protection with Microsoft Defender, check out our website. To learn more about Microsoft Security solutions, visit our website. Bookmark the Security blog to keep up with our expert coverage on security matters. Follow us on LinkedIn (Microsoft Security) and X (@MSFTSecurity) for the latest news and updates on cybersecurity.Pending Approval/Provisioning for Microsoft Defender XDR Lab/Trial Environment
Hello Microsoft Community Team, On June 26, 2026, our organization applied for a Microsoft 365 Developer Environment / Free Trial to support evaluation of the Microsoft Defender XDR Lab environment. To date, the environment has not been provisioned, and we have not received any status updates or confirmation. Impact: Current Status: We are currently utilizing our production environment to test project capabilities, which poses risks and limitations. Future Intent: Our organization plans to transition to a full, paid Business/Enterprise purchase immediately upon proving the platform’s benefits. Urgency: This delay is stalling our evaluation phase. We urgently need this environment onboarded and activated so we can proceed with deployment tests and subsequent procurement. Request: Please review the status of our registration and expedite the onboarding/provisioning of this developer environment. Thank you for your prompt assistance.10Views0likes0CommentsMicrosoft Defender now monitors RPC activity
Remote procedure call (RPC) is a protocol commonly abused by attackers that allows functions implemented in a separate process, and potentially on a remote machine, to be called as if they were local. Many core Windows and Active Directory capabilities are built on or make use of RPC, which makes it an attractive target. To help protect against remote RPC-based attacks, Microsoft Defender now monitors remote RPC calls, disrupts malicious activity that leverages them, and surfaces relevant telemetry in advanced hunting. RPC basics While RPC is a rich and complicated protocol, the main components that are relevant for security monitoring purposes are: Interface: A logical grouping of functionality exposed by an RPC server. Interfaces are identified by UUID. Example interfaces include Task Scheduler, Remote Registry, and the Service Control Manager, each exposing functionality related to a different Windows OS component. OpNum: Stands for Operation Number, an ordinal that denotes a specific function exposed by an RPC interface. Examples include RCreateServiceW (OpNum 12, Service Control Manager interface) and BaseRegQueryValue (OpNum 17, Remote Registry interface). Many remote attack techniques and tactics are based on RPC, for example: Lateral movement: often abuses RPC functionality for remotely creating tasks, services or invoking WMI. Credential theft: DCsync attacks, which abuse privileged compromised accounts to remotely extract credential material from Active Directory, are based on RPC functionality for directory replication. SecretsDump and similar attacks, which remotely extract SAM or LSA secrets, are based on querying a device’s registry remotely, using RPC. Privilege escalation: Multiple authentication coercion attacks abuse benign RPC interfaces to coerce servers to authenticate an attacker. Discovery: Tools such as SharpHound leverage RPC calls to enumerate users, sessions and shares. For a more comprehensive mapping of RPC interfaces to attack techniques, see work by Jonathan Johnson. RPC auditing in Defender Since RPC is so heavily used on Windows systems and in Active Directory domains, monitoring remote RPC traffic using network monitors is often expensive and infeasible. Additionally, if the underlying transport protocol is encrypted (such as SMB3), it might be impossible to observe RPC traffic. To enable efficient auditing of remote RPC activity regardless of transport-layer protection, Defender research and engineering expanded the existing RPC integration with the Windows Filtering Platform (WFP) to support OpNum-level granularity. This makes it possible to identify and audit the specific RPC function being invoked, rather than only the RPC interface. This capability is designed to help detect remote RPC-based attack techniques, where an attacker interacts with RPC interfaces exposed by a target device. For that reason, Defender focuses this monitoring on inbound remote RPC calls observed on the RPC server host. The telemetry is collected using audit-only WFP filters, which do not interfere with normal traffic, while still providing visibility into suspicious remote activity targeting the device. This approach does not require visibility into the source device. Local RPC calls, such as inter-process communication on the same device over local transport, and outbound RPC client calls are outside the scope of this monitoring mechanism. Using this capability, Defender monitors selected RPC calls, leverages the resulting telemetry to detect malicious activity, and exposes monitored calls in advanced hunting. Defender dynamically monitors selected remote operations from interfaces including, but not limited to: Remote Registry, Service Control Manager, Task Scheduler, and Windows Management Instrumentation (WMI). RPC monitoring for workstations is generally available, while server monitoring is currently in gradual rollout. RPC-based detections and disruption triggers are already available in Defender and include detections such as: Ongoing hands-on-keyboard attack via Impacket toolkit Suspicious service creation initiated remotely Indication of local security authority secrets theft Unusual RPC user and session discovery Authentication coercion attack Example Advanced Hunting queries 1. Remote registry key save events, abused for remote credential dumping. let remoteRegistryInterface = '338cd001-2244-31f1-aaaa-900038001003'; let registrySaveOpnums = dynamic([20, 31]); // BaseRegSaveKey, BaseRegSaveKeyEx DeviceEvents | where ActionType == 'InboundRemoteRpcCall' | extend AdditionalFields = parse_json(AdditionalFields) | extend RpcInterface = tostring(AdditionalFields.RpcInterfaceUuid), OpNum = toint(AdditionalFields.RpcOpNum) | where RpcInterface == remoteRegistryInterface and OpNum in(registrySaveOpnums) 2. Remote Service Creation events, could indicate lateral movement: let remoteServicesInterface = '367abb81-9844-35f1-ad32-98f038001003'; let serviceCreationOpnums = dynamic([12, 24, 44, 45, 60]); // RCreateServiceW, RCreateServiceA, RCreateServiceWOW64A, RCreateServiceWOW64W, RCreateWowService DeviceEvents | where ActionType == 'InboundRemoteRpcCall' | extend AdditionalFields = parse_json(AdditionalFields) | extend RpcInterface = tostring(AdditionalFields.RpcInterfaceUuid), OpNum = toint(AdditionalFields.RpcOpNum) | where RpcInterface == remoteServicesInterface and OpNum in(serviceCreationOpnums) 3. Session discovery events, could indicate account discovery: let srvsvcInterface = '4b324fc8-1670-01d3-1278-5a47bf6ee188'; let netrSessionEnumOpnum = 12; DeviceEvents | where ActionType == 'InboundRemoteRpcCall' | extend AdditionalFields = parse_json(AdditionalFields) | extend RpcInterface = tostring(AdditionalFields.RpcInterfaceUuid), OpNum = toint(AdditionalFields.RpcOpNum) | where RpcInterface == srvsvcInterface and OpNum == netrSessionEnumOpnum | summarize dcount(DeviceId) by AccountName, AccountDomain, AccountSid Check out the advanced hunting tab to see monitored RPC activity in your environment and stay tuned for more updates from Defender. Learn more To learn more about endpoint protection with Microsoft Defender, check out our website. To learn more about Microsoft Security solutions, visit our website. Bookmark the Security blog to keep up with our expert coverage on security matters. Follow us on LinkedIn (Microsoft Security) and X (@MSFTSecurity) for the latest news and updates on cybersecurity.The Worm in the Supply Chain: How Defender for Endpoint and Sentinel for SAP BTP Caught Shai-Hulud
On 29 April 2026, malicious versions of multiple SAP ecosystem npm packages were briefly published, creating a supply-chain exposure for SAP Cloud Application Programming (CAP) development environments and CI/CD pipelines. For a brief window that morning, affected developers have executed a credential-stealing payload on a workstation or, in higher-impact cases, within a CI/CD pipeline. SAP developers don't usually think of themselves as a juicy npm target. CAP, BTP, Fiori - that's enterprise turf, not crypto-stealer type territory – Until it is. Join me for the ride. See our latest click-video for an even more dynamic experience of SAP compromises. Affected packages and scope Four official npm packages from the SAP development ecosystem were published in malicious versions that day. Security researchers are calling the campaign "Mini Shai-Hulud" - the little cousin of the worm family that has been chewing its way through open-source registries for months. So, the "mini" part is a generous description in my opinion. Shai-Hulud has wriggled directly into the SAP supply chain, and that detail alone deserves a pause... SAP CAP is now interesting enough to have become a target. Four packages, all wearing legitimate SAP branding, all quietly swapped for evil twins: @cap-js/sqlite v2.2.2 @cap-js/postgres v2.2.2 @cap-js/db-service v2.10.1 mbt v1.2.48 These packages are not peripheral dependencies. The @cap-js/* modules are part of the SAP CAP Model used across custom development on SAP BTP, while mbt is the Cloud MTA Build Tool commonly embedded in CI/CD workflows that package and deploy Multi-Target Applications to BTP and on-premises environments. At roughly 930,000 weekly downloads, the combined exposure created meaningful downstream attack surface. The good news: SAP spotted the compromise fast, yanked the bad versions, and shipped clean replacements. The official guidance lives in SAP Security Note 3747787 - which carries the list of indicators of compromise, file hashes, and mitigation steps. Enough theory and evidence talk! Now, SHOW ME the detection! When the worm stirs beneath the sand, weak defenses vanish first. Observed telemetry in Microsoft Security products See below excerpt of Microsoft Defender for Endpoint from a compromised developer machine. The worm was neutralized immediately. Check the detection time (same day of release): Windows Defender AV detected malware ToString: DefenderDetection: File: /Users/User***/Projects/dara-api-manager-ui/node_modules/mbt/File***.js, Sha256: *** [Trojan:JS/SPchnStlr.BB], BlockingStatus: Prevented, BlockingStatusPriority: 900 DetectionTime: 2026-04-29 11:52:11Z DetectorName: Microsoft.Cyber.ObservationDetectors.DefenderConcreteDetector Observations (2): DefenderObservation Description: Defender detected and quarantined 'Trojan:JS/SPchnStlr.BB' in file 'File***.js' ThreatCategory = Trojan, ThreatFamily = SPchnStlr, How the Threat Actors Operationalized the Stolen Data The compromise allowed harvesting GitHub tokens, AWS/Azure/GCP secrets, npm credentials, Kubernetes config, SSH keys, .npmrc and .git-credentials files, and CI/CD environment variables. The hackers created a public GitHub repository on the victim’s own account, tagged with the description “A Mini Shai-Hulud has Appeared“ to exfiltrate their reaping. Within hours, more than a thousand such repositories were visible in public GitHub search. For additional views on the topic check out the blogs of our Sentinel for SAP partners: Onapsis, Pathlock, and SecurityBridge. Containment and Impact Reduction If you were not as lucky as the developer using Defender for Endpoint and VS Code, you need end to end monitoring of your landscape in and around SAP. Once the worm is loose with cloud tokens it may appear in various unexpected places. Microsoft Sentinel Solution for SAP covers your ERP crown jewels, your SAP BTP landscape and allows informed correlation with the rest of your IT estate. Microsoft’s correlation engine: ensures traceability automatic attack disruption and just-in-time hardening of potential attack paths. Developers using the cloud-based IDE SAP Business Application Studio are out of reach by Defender for Endpoint but profit from threat monitoring through Sentinel for SAP BTP integrating SAP BTP’s malware scanner the same way. See this in action in this click-video and in below screenshot. SOC analysts get actionable insights and tailored guidance from Security Copilot once SAP BTP signals are added to the Microsoft incident graph - no matter where the threat involving SAP originates from. Getting Started with Sentinel Solution for SAP Rollout of Sentinel for SAP BTP can happen immediately. Learn more from our deployment guide. Check out the security content reference for more info out-of-the-box detections. Sentinel for SAP which covers your ERP solutions and more, requires configuration of SAP Integration Suite as intermediary step. Learn more from our deployment guide. Check out the security content reference for more info out-of-the-box detections Final Words This incident illustrates how far the SAP BTP attack surface now extends and why patching alone is insufficient once malicious code reaches developer tooling and build infrastructure. Effective defense also requires telemetry, correlation, and response coverage across SAP and non-SAP environments. See you out there folks! #Kudos to Mahesh Mandva and Cameron Gardiner on riding shai-holud with me. Feel free to reach out to talk more about SAP Cyber Security. Cheers, Martin Useful Links SAP Note 3747787 with mitigation guide Mini Shai-Hulud: Multi-Ecosystem Developer Supply Chain Attack – Lab Space Click-Demo for SAP Cyber Security with Microsoft Sentinel for SAP Security Content | Microsoft Learn Sentinel for SAP BTP Security Content | Microsoft Learn341Views0likes0Comments