Use this skill when asked to investigate a computer, device, endpoint, or machine for security issues, suspicious activity, malware, or compliance review. Triggers on keywords like "investigate computer", "investigate device", "investigate endpoint", "check machine", "device security", "endpoint investigation", or when a device name/hostname is mentioned with investigation context. This skill provides comprehensive device security analysis including Defender alerts, sign-in patterns, logged-on users, vulnerabilities, software inventory, compliance status, network activity, and automated investigation tracking for Entra Joined, Hybrid Joined, and Entra Registered devices.
Use this skill when asked to investigate a computer, device, endpoint, or machine for security issues, suspicious activity, malware, or compliance review. Triggers on keywords like "investigate computer", "investigate device", "investigate endpoint", "check machine", "device security", "endpoint investigation", or when a device name/hostname is mentioned with investigation context. This skill provides comprehensive device security analysis including Defender alerts, sign-in patterns, logged-on users, vulnerabilities, software inventory, compliance status, network activity, and automated investigation tracking for Entra Joined, Hybrid Joined, and Entra Registered devices.
threat_pulse_domains
["endpoint"]
drill_down_prompt
Investigate device {entity} — Defender alerts, process activity, vulnerabilities, compliance
Computer Security Investigation - Instructions
Purpose
This skill performs comprehensive security investigations on Windows, macOS, and Linux devices registered in Microsoft Entra ID and/or managed by Microsoft Defender for Endpoint. It analyzes Defender alerts, device compliance, sign-in patterns, logged-on users, installed software, vulnerabilities, network connections, and automated investigation results for:
Entra Joined Devices: Cloud-only devices joined directly to Microsoft Entra ID
Hybrid Joined Devices: Devices joined to both on-premises Active Directory and Microsoft Entra ID
Entra Registered Devices: Personal devices (BYOD) registered with Microsoft Entra ID
⛔ Shortcut Default Rule: When a matching shortcut exists for the investigation context, use it — don't run the full workflow. Only run the full query set when the user explicitly requests "full investigation", "comprehensive", or "deep dive". Shortcuts render only the report sections relevant to their query chain (plus Executive Summary and Recommendations, always).
⚠️ CRITICAL WORKFLOW RULES - READ FIRST ⚠️
Before starting ANY computer investigation:
ALWAYS get Device ID FIRST (required for Defender API and Graph queries - multiple IDs exist!)
ALWAYS determine device type (Entra Joined, Hybrid Joined, or Entra Registered)
ALWAYS calculate date ranges correctly (use current date from context - see Date Range section)
ALWAYS ask the user for output mode if not specified: inline chat summary, markdown file report, or JSON export (see Output Modes)
ALWAYS track and report time after each major step (mandatory)
ALWAYS run independent queries in parallel (drastically faster execution)
ALWAYS use create_file for JSON export and markdown reports (NEVER use PowerShell terminal commands)
This skill requires a Sentinel workspace to execute queries. Follow these rules STRICTLY:
When invoked from a parent skill (incident-investigation, threat-pulse, etc.):
Inherit the workspace selection from the parent investigation context
If no workspace was selected in parent context: STOP and ask user to select
Use the SELECTED_WORKSPACE_IDS passed from the parent skill
Skip output mode prompts — default to inline chat (the parent skill controls the final output format)
When invoked standalone (direct user request):
ALWAYS call list_sentinel_workspaces MCP tool FIRST
If 1 workspace exists: Auto-select, display to user, proceed
If multiple workspaces exist:
Display all workspaces with Name and ID
ASK: "Which Sentinel workspace should I use for this investigation?"
⛔ STOP AND WAIT for user response
⛔ DO NOT proceed until user explicitly selects
If a query fails on the selected workspace:
⛔ DO NOT automatically try another workspace
STOP and report the error
Display available workspaces
ASK user to select a different workspace
WAIT for user response
Workspace Failure Handling
IF query returns "Failed to resolve table" or similar error:
- STOP IMMEDIATELY
- Report: "⚠️ Query failed on workspace [NAME] ([ID]). Error: [ERROR_MESSAGE]"
- Display: "Available workspaces: [LIST_ALL_WORKSPACES]"
- ASK: "Which workspace should I use instead?"
- WAIT for explicit user response
- DO NOT retry with a different workspace automatically
🔴 PROHIBITED ACTIONS:
❌ Selecting a workspace without user consent when multiple exist
❌ Switching to another workspace after a failure without asking
❌ Proceeding with investigation if workspace selection is ambiguous
❌ Assuming a workspace based on previous sessions
Device ID Types:
Entra Device ID (Azure AD Object ID): Used for Graph API queries - GUID format
Defender Device ID: Used for MDE API queries - GUID format (different from Entra ID!)
Device Name/Hostname: Human-readable name, use for initial search
Intune Device ID: Used for Intune management queries
Date Range Rules:
Real-time/recent searches: Add +2 days to current date for end range
Historical ranges: Add +1 day to user's specified end date
Example: Current date = Jan 23; "Last 7 days" → datetime(2026-01-16) to datetime(2026-01-25)
Device Types Reference
Entra Joined Devices
trustType: AzureAd
Characteristics: Cloud-only, no on-premises AD connection
Identity: Uses Entra ID for authentication
Common scenarios: Cloud-native organizations, Windows Autopilot deployments
Hybrid Joined Devices
trustType: ServerAd (indicates hybrid join with on-premises AD)
Characteristics: Joined to both on-premises AD and Entra ID
Identity: Uses both on-premises AD and Entra ID
Common scenarios: Traditional enterprise environments migrating to cloud
Entra Registered Devices
trustType: Workplace
Characteristics: Personal/BYOD devices, user adds work account
Identity: User authenticates with Entra ID, device not fully managed
Common scenarios: BYOD policies, personal device access to corporate resources
Available Investigation Types
Standard Investigation (7 days)
When to use: General security reviews, routine investigations
Example prompts:
"Investigate device WORKSTATION-001 for the last 7 days"
"Run security investigation for computer LAP-JSMITH from 2026-01-16 to 2026-01-23"
"Check endpoint security for DESKTOP-ABC123"
Quick Investigation (1 day)
When to use: Urgent cases, active malware alerts, recent suspicious activity
Example prompts:
"Quick investigate infected device SRV-SQL01"
"Run quick security check on machine WKS-FINANCE02"
"Urgent: check device LAPTOP-EXEC-01 for compromise"
Comprehensive Investigation (30 days)
When to use: Deep-dive analysis, lateral movement detection, thorough forensics
Example prompts:
"Full investigation for potentially compromised device SRV-DC01"
"Do a deep dive investigation on endpoint WORKSTATION-IT03 last 30 days"
"Comprehensive security analysis for hybrid joined device DESKTOP-HR01"
All types include: Defender alerts, device compliance, sign-in patterns from device, logged-on users, software inventory, vulnerabilities, network connections, file activities, automated investigation status, and security recommendations.
Output Modes
This skill supports three output modes. ASK the user which they prefer if not explicitly specified. Multiple modes may be selected simultaneously.
Mode 1: Inline Chat Summary (Default)
Render the full investigation analysis directly in the chat response
Includes device profile, risk assessment, alerts, vulnerabilities, logged-on users, and recommendations
Best for quick review and interactive follow-up questions
No file output — results stay in the chat context
Mode 2: Markdown File Report
Save a comprehensive investigation report to reports/computer-investigations/computer_investigation_<device_name>_<YYYYMMDD_HHMMSS>.md
All sections from inline mode plus additional detail (full vulnerability tables, process event samples, network connection details, query appendix)
Best for programmatic consumption or integration with other tools
Markdown Rendering Notes
✅ ASCII tables, box-drawing characters, and bar charts render perfectly in markdown code blocks
✅ Unicode block characters (█ full block, ─ box-drawing horizontal) display correctly in monospaced fonts
✅ Emoji indicators (🔴🟢🟡⚠️✅) render natively in GitHub-flavored markdown
✅ Standard markdown tables (| col |) render as formatted tables
Tip: Wrap all ASCII art in triple-backtick code fences for consistent rendering
Quick Start (TL;DR)
When a user requests a computer security investigation:
Get Device IDs:
# First, find the device and get both Entra ID and Defender ID
mcp_microsoft_mcp_microsoft_graph_get("/v1.0/devices?$filter=displayName eq '<DEVICE_NAME>'&$select=id,deviceId,displayName,operatingSystem,trustType,isCompliant,isManaged")
# Then get Defender device ID from MDE
Use Defender `ListDefenderMachines` or Advanced Hunting to find by device name
Step 1b: Get Defender Device ID
Use Advanced Hunting or Defender API to find the MDE device ID:
DeviceInfo
| where DeviceName startswith '<DEVICE_NAME>' // Use startswith to match both hostname and FQDN
| summarize arg_max(TimeGenerated, *) by DeviceName
| project DeviceId, DeviceName, OSPlatform, OSVersion, MachineGroup, OnboardingStatus, ExposureLevel, SensorHealthState, DeviceManualTags, DeviceDynamicTags, RegistryDeviceTag
Note: RiskScore is NOT in DeviceInfo - use GetDefenderMachine API to get riskScore and exposureLevel.
Why BOTH IDs are required:
Entra Device ID: Used for Graph API (compliance, registration, BitLocker, Intune)
Defender Device ID: Used for MDE API (alerts, vulnerabilities, logged-on users, investigations)
IDs are DIFFERENT: The same device has different GUIDs in Entra ID vs Defender for Endpoint
Device Type Determination:
Check trustType field from Graph API response:
AzureAd = Entra Joined
ServerAd = Hybrid Joined
Workplace = Entra Registered
Phase 2: Parallel Data Collection
CRITICAL: Use create_file tool to create JSON - NEVER use PowerShell terminal commands!
Batch 1: Sentinel/Advanced Hunting Queries (Run ALL in parallel)
Device sign-in events (Query 1) - Who signed into this device
Device alerts (Query 2) - SecurityAlert filtered by device
Process execution events (Query 3) - Suspicious process activity
Tool: Use the Sentinel Triage MCP's RunAdvancedHuntingQuery tool
Parameter name:kqlQuery (NOT query!)
Time column:Timestamp for XDR-native tables (Device*, Email*, etc.); TimeGenerated for LA/Sentinel tables (SigninLogs, SecurityAlert, etc.) — even in AH
Use for:Default choice for all ≤30d queries (free for Analytics-tier tables). Required for TVM tables (DeviceTvmSoftwareInventory, DeviceTvmSoftwareVulnerabilities) which don't exist in Data Lake.
Example invocation:
RunAdvancedHuntingQuery(
kqlQuery="DeviceTvmSoftwareVulnerabilities | where DeviceName startswith 'DEVICENAME' | take 30"
)
Tool Selection Guide
Follow the global Tool Selection Rule in .github/copilot-instructions.md (Data Lake vs Advanced Hunting). This skill does NOT override the global default — use Advanced Hunting first for ≤30d lookbacks (free for Analytics-tier tables), and fall back to Data Lake only for >30d windows or when AH is blocked by the safety filter.
When adapting the sample queries below: they are written with TimeGenerated for Data Lake compatibility. For Advanced Hunting on Device* tables, swap TimeGenerated → Timestamp. For SecurityAlert/SecurityIncident/SigninLogs in AH, keep TimeGenerated (LA/Sentinel tables retain their column name in AH).
Schema differences: Some MDE columns (e.g., SentBytes, ReceivedBytes in DeviceNetworkEvents) may not be available in Data Lake. If a column fails in one tool, try the other.
📅 Date Range Quick Reference
🔴 STEP 0: GET CURRENT DATE FIRST (MANDATORY) 🔴
ALWAYS check the current date from the context header BEFORE calculating date ranges
NEVER use hardcoded years - the year changes and you WILL query the wrong timeframe
Examples Table (Assuming Current Date = January 23, 2026):
User Request
<StartDate>
<EndDate>
Rule Applied
"Last 7 days"
2026-01-16
2026-01-25
Rule 1 (+2)
"Last 30 days"
2025-12-24
2026-01-25
Rule 1 (+2)
"Jan 15 to Jan 20"
2026-01-15
2026-01-21
Rule 2 (+1)
1. Device Sign-In Events (Who authenticated on this device)
Note: DeviceDetail is dynamic in SigninLogs but string in AADNonInteractiveUserSignInLogs. Query SigninLogs only for device context (interactive sign-ins contain device info). Do NOT use union with DeviceDetail filtering - causes schema conflicts in Sentinel Data Lake.
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let deviceName = '<DEVICE_NAME>';
SigninLogs
| where TimeGenerated between (start .. end)
| extend DeviceDetailStr = tostring(DeviceDetail)
| where DeviceDetailStr has deviceName
| extend ParsedDevice = parse_json(DeviceDetailStr)
| extend DeviceName = tostring(ParsedDevice.displayName)
| extend DeviceId = tostring(ParsedDevice.deviceId)
| extend DeviceOS = tostring(ParsedDevice.operatingSystem)
| extend DeviceTrustType = tostring(ParsedDevice.trustType)
| extend DeviceCompliant = tostring(ParsedDevice.isCompliant)
| summarize
SignInCount = count(),
SuccessCount = countif(ResultType == '0'),
FailureCount = countif(ResultType != '0'),
UniqueUsers = dcount(UserPrincipalName),
Users = make_set(UserPrincipalName, 10),
Applications = make_set(AppDisplayName, 10),
IPAddresses = make_set(IPAddress, 10),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by DeviceName, DeviceOS, DeviceTrustType, DeviceCompliant
| order by SignInCount desc
2. Device Security Alerts (SecurityAlert table)
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let deviceName = '<DEVICE_NAME>';
SecurityAlert
| where TimeGenerated between (start .. end)
| where Entities has deviceName or CompromisedEntity has deviceName
| summarize arg_max(TimeGenerated, *) by SystemAlertId
| project
TimeGenerated,
AlertName,
AlertSeverity,
Status,
Description,
ProviderName,
Tactics,
Techniques,
CompromisedEntity,
RemediationSteps
| order by TimeGenerated desc
| take 20
3. Process Execution Events (Suspicious processes)
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let deviceName = '<DEVICE_NAME>';
DeviceProcessEvents
| where TimeGenerated between (start .. end)
| where DeviceName startswith deviceName // Use startswith to match both hostname and FQDN
| where ActionType in ("ProcessCreated", "ProcessCreatedUsingWmiQuery")
| extend CommandLineLength = strlen(ProcessCommandLine)
| extend IsSuspicious = case(
ProcessCommandLine has_any ("powershell", "cmd", "wscript", "cscript") and ProcessCommandLine has_any ("-enc", "-e ", "bypass", "hidden", "downloadstring", "invoke-expression", "iex"), true,
ProcessCommandLine has_any ("certutil", "bitsadmin") and ProcessCommandLine has_any ("download", "transfer", "urlcache"), true,
ProcessCommandLine has_any ("reg", "registry") and ProcessCommandLine has_any ("add", "delete") and ProcessCommandLine has_any ("run", "runonce"), true,
FileName in~ ("mimikatz.exe", "procdump.exe", "psexec.exe", "cobaltstrike", "beacon.exe"), true,
CommandLineLength > 500, true,
false)
| summarize
ProcessCount = count(),
SuspiciousCount = countif(IsSuspicious),
UniqueProcesses = dcount(FileName),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
SampleCommands = make_set(ProcessCommandLine, 5)
by FileName, FolderPath, AccountName, AccountDomain
| where SuspiciousCount > 0 or ProcessCount > 50
| order by SuspiciousCount desc, ProcessCount desc
| take 20
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let deviceName = '<DEVICE_NAME>';
DeviceNetworkEvents
| where TimeGenerated between (start .. end)
| where DeviceName startswith deviceName // Use startswith to match both hostname and FQDN
| where ActionType == "ConnectionSuccess"
| where RemoteIPType != "Private" // Focus on public IPs
| summarize
ConnectionCount = count(),
UniqueRemoteIPs = dcount(RemoteIP),
UniqueRemotePorts = dcount(RemotePort),
Protocols = make_set(Protocol, 5),
InitiatingProcesses = make_set(InitiatingProcessFileName, 10),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by RemoteIP, RemotePort, RemoteUrl
| order by ConnectionCount desc
| take 30
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let deviceName = '<DEVICE_NAME>';
DeviceFileEvents
| where TimeGenerated between (start .. end)
| where DeviceName startswith deviceName // Use startswith to match both hostname and FQDN
| where ActionType in ("FileCreated", "FileModified", "FileDeleted", "FileRenamed")
| extend FileExtension = tostring(split(FileName, ".")[-1])
| extend IsSuspicious = case(
FileExtension in~ ("exe", "dll", "bat", "cmd", "ps1", "vbs", "js", "hta", "scr", "pif"), true,
FolderPath has_any ("\\temp\\", "\\tmp\\", "\\appdata\\local\\temp", "\\programdata\\", "\\users\\public\\"), true,
false)
| summarize
FileEventCount = count(),
SuspiciousCount = countif(IsSuspicious),
CreatedCount = countif(ActionType == "FileCreated"),
ModifiedCount = countif(ActionType == "FileModified"),
DeletedCount = countif(ActionType == "FileDeleted"),
UniqueFiles = dcount(FileName),
FileExtensions = make_set(FileExtension, 10),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by FolderPath, InitiatingProcessFileName
| where SuspiciousCount > 0 or FileEventCount > 100
| order by SuspiciousCount desc, FileEventCount desc
| take 20
6. Registry Events (Registry modifications)
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let deviceName = '<DEVICE_NAME>';
DeviceRegistryEvents
| where TimeGenerated between (start .. end)
| where DeviceName startswith deviceName // Use startswith to match both hostname and FQDN
| where ActionType in ("RegistryValueSet", "RegistryKeyCreated")
| extend IsPersistence = case(
RegistryKey has_any ("\\CurrentVersion\\Run", "\\CurrentVersion\\RunOnce", "\\CurrentVersion\\RunServices"), true,
RegistryKey has_any ("\\Policies\\Explorer\\Run", "\\Active Setup\\Installed Components"), true,
RegistryKey has_any ("\\Image File Execution Options\\", "\\Winlogon\\", "\\BootExecute"), true,
RegistryKey has_any ("\\Services\\", "\\Drivers\\"), true,
false)
| summarize
RegistryEventCount = count(),
PersistenceCount = countif(IsPersistence),
UniqueKeys = dcount(RegistryKey),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by RegistryKey, RegistryValueName, InitiatingProcessFileName
| where PersistenceCount > 0
| order by PersistenceCount desc, RegistryEventCount desc
| take 20
7. Security Incidents Containing Device
let deviceName = '<DEVICE_NAME>';
let deviceId = '<DEVICE_ID>';
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let relevantAlerts = SecurityAlert
| where TimeGenerated between (start .. end)
| where Entities has deviceName or Entities has deviceId or CompromisedEntity has deviceName
| summarize arg_max(TimeGenerated, *) by SystemAlertId
| project SystemAlertId, AlertName, AlertSeverity, ProviderName, Tactics;
SecurityIncident
| where CreatedTime between (start .. end)
| summarize arg_max(TimeGenerated, *) by IncidentNumber
| where not(tostring(Labels) has "Redirected")
| mv-expand AlertId = AlertIds
| extend AlertId = tostring(AlertId)
| join kind=inner relevantAlerts on $left.AlertId == $right.SystemAlertId
| extend ProviderIncidentUrl = tostring(AdditionalData.providerIncidentUrl)
| extend OwnerUPN = tostring(Owner.userPrincipalName)
| summarize
Title = any(Title),
Severity = any(Severity),
Status = any(Status),
Classification = any(Classification),
CreatedTime = any(CreatedTime),
LastModifiedTime = any(LastModifiedTime),
OwnerUPN = any(OwnerUPN),
ProviderIncidentUrl = any(ProviderIncidentUrl),
AlertCount = count(),
Tactics = make_set(Tactics)
by ProviderIncidentId
| order by LastModifiedTime desc
| take 10
8. Device Inventory and Configuration Changes
Note: RiskScore is NOT in DeviceInfo - use GetDefenderMachine API for risk/exposure scores.
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let deviceName = '<DEVICE_NAME>';
DeviceInfo
| where TimeGenerated between (start .. end)
| where DeviceName startswith deviceName // Use startswith to match both hostname and FQDN
| summarize arg_max(TimeGenerated, *) by DeviceId
| project
TimeGenerated,
DeviceId,
DeviceName,
OSPlatform,
OSVersion,
OSBuild,
OSArchitecture,
LoggedOnUsers,
MachineGroup,
DeviceCategory,
OnboardingStatus,
SensorHealthState,
ExposureLevel,
IsAzureADJoined,
IsInternetFacing,
JoinType,
PublicIP,
DeviceManualTags,
DeviceDynamicTags,
RegistryDeviceTag
9. Software Inventory on Device
⚠️ DO NOT use Sentinel Data Lake MCP (query_lake) for this query. The DeviceTvmSoftwareInventory table is NOT available in the Sentinel Data Lake. Use Advanced Hunting MCP (RunAdvancedHuntingQuery) only. TVM tables use snapshot ingestion with no TimeGenerated filtering.
let deviceName = '<DEVICE_NAME>';
DeviceTvmSoftwareInventory
| where DeviceName startswith deviceName // Use startswith to match both hostname and FQDN
| project
DeviceName,
SoftwareVendor,
SoftwareName,
SoftwareVersion,
EndOfSupportStatus,
EndOfSupportDate
| summarize by SoftwareVendor, SoftwareName, SoftwareVersion, EndOfSupportStatus, EndOfSupportDate
| order by NumberOfWeaknesses desc
| take 30
10. Vulnerabilities on Device
⚠️ DO NOT use Sentinel Data Lake MCP (query_lake) for this query. The DeviceTvmSoftwareVulnerabilities table is NOT available in the Sentinel Data Lake. Use Advanced Hunting MCP (RunAdvancedHuntingQuery) only. TVM tables use snapshot ingestion with no TimeGenerated filtering.
let deviceName = '<DEVICE_NAME>';
DeviceTvmSoftwareVulnerabilities
| where DeviceName startswith deviceName // Use startswith to match both hostname and FQDN
| project
CveId,
VulnerabilitySeverityLevel,
SoftwareVendor,
SoftwareName,
SoftwareVersion,
RecommendedSecurityUpdate,
RecommendedSecurityUpdateId
| summarize by CveId, VulnerabilitySeverityLevel, SoftwareVendor, SoftwareName, SoftwareVersion, RecommendedSecurityUpdate, RecommendedSecurityUpdateId
| order by case(VulnerabilitySeverityLevel == "Critical", 1, VulnerabilitySeverityLevel == "High", 2, VulnerabilitySeverityLevel == "Medium", 3, 4) asc
| take 30
11. Logon Events on Device
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let deviceName = '<DEVICE_NAME>';
DeviceLogonEvents
| where TimeGenerated between (start .. end)
| where DeviceName startswith deviceName // Use startswith to match both hostname and FQDN
| summarize
LogonCount = count(),
SuccessCount = countif(ActionType == "LogonSuccess"),
FailureCount = countif(ActionType == "LogonFailed"),
UniqueAccounts = dcount(AccountName),
LogonTypes = make_set(LogonType, 5),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
RemoteIPs = make_set(RemoteIP, 10)
by AccountName, AccountDomain, LogonType
| order by LogonCount desc
| take 20
12. Threat Intelligence IP Matches (Device Network Traffic)
Performance notes: ThreatIntelIndicators can be large (100K+ rows). Filter IsActive/ValidUntilbefore string transformations per KQL best practices — reduce data first, transform later. The triple replace_string was replaced with direct array indexing split(...)[0] which returns a clean string.
let start = datetime(<StartDate>);
let end = datetime(<EndDate>);
let deviceName = '<DEVICE_NAME>';
let device_ips = DeviceNetworkEvents
| where TimeGenerated between (start .. end)
| where DeviceName startswith deviceName // Use startswith to match both hostname and FQDN
| where RemoteIPType != "Private"
| distinct RemoteIP;
ThreatIntelIndicators
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| where tostring(split(ObservableKey, ":")[0]) in ("ipv4-addr", "ipv6-addr", "network-traffic")
| where ObservableValue in (device_ips)
| extend Description = tostring(parse_json(Data).description)
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
| summarize arg_max(TimeGenerated, *) by ObservableValue
| project
TimeGenerated,
IPAddress = ObservableValue,
ThreatDescription = Description,
Confidence,
ValidUntil,
IsActive
| order by Confidence desc
| take 20
Microsoft Graph Device Queries
Use these Graph API queries in Phase 2 (Batch 3) of investigation workflow
Returns: Array of users with accountName, accountDomain, firstSeen, lastSeen, logonTypes
Get Machine Alerts (via API)
Use the ListAlerts MCP tool filtered by device:
ListAlerts with machineId filter
Get Automated Investigations
ListDefenderInvestigations
Filter results by machineId to find investigations related to the device
Get Remediation Activities
ListDefenderRemediationActivities
Filter results by machineId to find remediation tasks for the device
Markdown Report Template
When outputting to markdown file (Mode 2), use this template. Populate ALL sections with actual query data. For sections with no data, use the explicit absence confirmation pattern.
# Computer Security Investigation Report**Generated:** YYYY-MM-DD HH:MM UTC
**Workspace:**<workspace_name>**Device:**`<DEVICE_NAME>`**OS:**<operating_system><os_version>**Trust Type:**<EntraJoined / HybridJoined / EntraRegistered> (`<trustType>`)
**Compliance:**<Compliant/Non-Compliant> | **Managed:**<Yes/No> | **MDM:**<Intune/None>**Investigation Period:**<start_date> → <end_date> (<N> days)
**Investigation Type:**<Standard (7d) / Quick (1d) / Comprehensive (30d)>**Data Sources:** DeviceInfo, DeviceProcessEvents, DeviceNetworkEvents, DeviceFileEvents, DeviceRegistryEvents, DeviceLogonEvents, SigninLogs, SecurityAlert, SecurityIncident, DeviceTvmSoftwareVulnerabilities, DeviceTvmSoftwareInventory, ThreatIntelIndicators, Microsoft Graph API, Defender for Endpoint API
---
## Executive Summary
<2-4 sentence summary: overall device risk level, key findings, most significant alerts or vulnerabilities, and primary recommendation. Ground every claim in evidence from query results.>
**Overall Risk Level:** 🔴 CRITICAL / 🔴 HIGH / 🟠 MEDIUM / 🟡 LOW / 🟢 INFORMATIONAL
---
## Device Profile
| Property | Value |
|----------|-------|
| **Device Name** | `<device_name>` |
| **OS** | <os_platform><os_version> (<os_build>) |
| **Architecture** | <os_architecture> |
| **Trust Type** | <EntraJoined / HybridJoined / EntraRegistered> |
| **Compliant** | 🟢 Yes / 🔴 No |
| **Managed** | 🟢 Yes / 🔴 No |
| **Manufacturer** | <manufacturer> |
| **Model** | <model> |
| **Registration Date** | <datetime> |
| **Last Sign-in** | <datetime> |
| **Internet Facing** | 🔴 Yes / 🟢 No |
### Defender for Endpoint Status
| Property | Value |
|----------|-------|
| **Onboarding Status** | 🟢 Onboarded / 🔴 Not Onboarded |
| **Sensor Health** | 🟢 Active / 🟠 Inactive / 🔴 Misconfigured |
| **Health Status** | <health_status> |
| **Risk Score** | 🔴/🟠/🟡/🟢 <None/Low/Medium/High> |
| **Exposure Level** | 🔴/🟠/🟡/🟢 <None/Low/Medium/High> |
| **Last Seen** | <datetime> |
| **Last Internal IP** | <ip_address> |
| **Last External IP** | <ip_address> |
| **Machine Group** | <group_name> |
| **Device Tags** | <comma-separatedlistfromDeviceManualTags + DeviceDynamicTags, or "None"> |
### Device Owners & Registered Users<Ifowners/usersfound:>
| User | UPN | Role |
|------|-----|------|
| <display_name> | <upn> | Owner / Registered User |
<Ifnoowners/users:>
✅ No registered owners or users found for this device.
---
## Key Metrics
| Metric | Value |
|--------|-------|
| **Security Alerts** | <count> (Critical: <n>, High: <n>, Medium: <n>, Low: <n>) |
| **Security Incidents** | <count> (Open: <n>, Closed: <n>) |
| **Logged-On Users** | <count> unique users |
| **Sign-ins from Device** | <count> (Success: <n>, Failed: <n>) |
| **Vulnerabilities** | <count> (Critical: <n>, High: <n>, Medium: <n>) |
| **Suspicious Processes** | <count> flagged |
| **Network Connections** | <count> external IPs |
| **TI Matches** | <count> threat intel hits |
| **End-of-Support Software** | <count> |
---
## Security Alerts<Ifalertsfound:>
| Time | Alert Name | Severity | Status | Provider | Tactics | Compromised Entity |
|------|-----------|----------|--------|----------|---------|---------------------|
| <datetime> | <alert_name> | 🔴/🟠/🟡 <severity> | <status> | <provider> | <tactics> | <entity> |
**Alert Summary:**-<X> total alerts (<breakdownbyseverity>)
-<Briefdescriptionofmostcriticalalert(s)>- Remediation steps: <summaryofrecommendedactionsfromalertdata><Ifnoalerts:>
✅ No security alerts detected for this device in the investigation period.
- Checked: SecurityAlert filtered by device name and device ID (0 matches)
---
## Security Incidents<Ifincidentsfound:>
| ID | Title | Severity | Status | Classification | Created | Owner | Alerts | Link |
|----|-------|----------|--------|----------------|---------|-------|--------|------|
| <provider_incident_id> | <title> | 🔴/🟠/🟡 <severity> | <New/Active/Closed> | <TP/FP/BP/—> | <date> | <owner_upn> | <count> | [View](<url>) |
**Incident Summary:**-<X> total incidents (<Y> open, <Z> closed)
- Highest severity: <level>-<Briefdescriptionofmostcriticalincident><Ifnoincidents:>
✅ No security incidents involving this device in the investigation period.
- Checked: SecurityAlert → SecurityIncident join on device name and device ID (0 matches)
---
## Logged-On Users<Ifusersfound:>
| Account | Domain | Logon Type | Logon Count | Success | Failed | First Seen | Last Seen |
|---------|--------|------------|:-----------:|:-------:|:------:|------------|-----------|
| <account_name> | <domain> | <Interactive/RemoteInteractive/Network/etc.> | <count> | <count> | <count> | <date> | <date> |
**User Analysis:**-<X> unique accounts authenticated on this device
-<Summaryoflogonpatterns — expectedvsunexpectedaccounts, after-hourslogons, remoteIPs><Ifnologondata:>
✅ No logon events detected for this device in the investigation period.
### Defender Logged-On Users (API)<IfMDElogged-onusersfound:>
| Account | Domain | First Seen | Last Seen | Logon Types |
|---------|--------|------------|-----------|-------------|
| <account_name> | <domain> | <date> | <date> | <types> |
<IfnoMDEdata:>
✅ No logged-on user data returned from Defender for Endpoint API.
---
## Sign-in Activity (From Device)<Ifsign-ineventsfound:>
| Device Name | OS | Trust Type | Compliant | Users | Applications | IPs | Sign-ins | Success | Failed | First Seen | Last Seen |
|-------------|-----|------------|-----------|:-----:|:------------:|:---:|:--------:|:-------:|:------:|------------|-----------|
| <name> | <os> | <trust> | 🟢/🔴 | <count> | <count> | <count> | <count> | <count> | <count> | <date> | <date> |
**Top Users:**<listofUPNs>**Top Applications:**<listofapps>**Top IPs:**<listofIPs><Ifnosign-inevents:>
✅ No sign-in events found for this device in the investigation period.
---
## Process Activity<Ifsuspiciousprocessesfound:>
| Process | Path | Account | Process Count | Suspicious | Sample Command Lines |
|---------|------|---------|:------------:|:----------:|----------------------|
| <filename> | <folder_path> | <account_name> | <count> | 🔴 <count> | <truncated_command> |
**Process Analysis:**-<X> suspicious process executions detected
-<Summaryofsuspiciouspatterns — encodedcommands, LOLBins, credentialdumpingtools, longcommandlines><Ifnosuspiciousprocesses:>
✅ No suspicious process activity detected on this device in the investigation period.
- Checked: DeviceProcessEvents filtered for suspicious indicators (0 flagged)
---
## Network Connections<Ifexternalconnectionsfound:>
| Remote IP | Remote Port | URL | Connections | Unique Ports | Protocols | Initiating Processes | First Seen | Last Seen |
|-----------|:-----------:|-----|:-----------:|:------------:|-----------|----------------------|------------|-----------|
| <ip> | <port> | <url> | <count> | <count> | <protocols> | <process_list> | <date> | <date> |
**Network Summary:**-<X> unique external IPs contacted
-<Y> unique remote ports
-<Topinitiatingprocesses><Ifnoexternalconnections:>
✅ No external network connections detected for this device in the investigation period.
### Threat Intelligence Matches<IfTImatchesfound:>
| IP Address | Threat Description | Confidence | Valid Until | Active |
|------------|-------------------|:----------:|------------|:------:|
| <ip> | <description> | <score> | <date> | ✅/❌ |
<IfnoTImatches:>
✅ No threat intelligence matches found for device network traffic.
- Checked: ThreatIntelIndicators joined with device external IPs (0 matches)
---
## File Activity<Ifsuspiciousfileeventsfound:>
| Folder Path | Initiating Process | Total Events | Suspicious | Created | Modified | Deleted | Extensions | First Seen | Last Seen |
|-------------|-------------------|:------------:|:----------:|:-------:|:--------:|:-------:|------------|------------|-----------|
| <path> | <process> | <count> | 🔴 <count> | <count> | <count> | <count> | <ext_list> | <date> | <date> |
**File Activity Analysis:**-<X> suspicious file operations detected
-<Summary — executabledropsintempfolders, scriptcreation, massfilemodifications><Ifnosuspiciousfileevents:>
✅ No suspicious file activity detected on this device in the investigation period.
- Checked: DeviceFileEvents for suspicious extensions and temp folder activity (0 flagged)
---
## Registry Modifications<Ifpersistence-relatedregistryeventsfound:>
| Registry Key | Value Name | Initiating Process | Total Events | Persistence | First Seen | Last Seen |
|-------------|------------|-------------------|:------------:|:-----------:|------------|-----------|
| <key> | <value_name> | <process> | <count> | 🔴 <count> | <date> | <date> |
**Registry Analysis:**-<X> persistence-related registry modifications detected
-<Summary — Runkeys, services, Winlogon, IFEOmodifications><Ifnopersistenceregistryevents:>
✅ No persistence-related registry modifications detected on this device in the investigation period.
- Checked: DeviceRegistryEvents for Run/RunOnce/Services/Winlogon/IFEO keys (0 flagged)
---
## Vulnerabilities<Ifvulnerabilitiesfound:>
| CVE ID | Severity | Vendor | Software | Version | Security Update |
|--------|----------|--------|----------|---------|-----------------|
| <cve_id> | 🔴/🟠/🟡 <severity> | <vendor> | <software> | <version> | <update_id> |
**Vulnerability Summary:**-<X> total vulnerabilities (Critical: <n>, High: <n>, Medium: <n>, Low: <n>)
-<MostcriticalCVEsandtheirremediationstatus><Ifnovulnerabilities:>
✅ No known vulnerabilities detected on this device.
- Checked: DeviceTvmSoftwareVulnerabilities (0 records)
---
## Software Inventory<Ifnotablesoftwarefound:>
| Vendor | Software | Version | End of Support | EOS Date |
|--------|----------|---------|:--------------:|----------|
| <vendor> | <software> | <version> | 🔴 Yes / 🟢 No | <date> |
**Software Summary:**-<X> total software packages installed
-<Y> end-of-support software detected
-<Notablefindings — outdatedbrowsers, deprecatedruntimes, riskyapplications><Ifnosoftwaredata:>
✅ No software inventory data available for this device.
- Checked: DeviceTvmSoftwareInventory (0 records)
---
## Device Configuration<Ifconfigurationdataavailable:>
| Property | Value |
|----------|-------|
| **Public IP** | <ip> |
| **Machine Group** | <group> |
| **Device Category** | <category> |
| **Onboarding Status** | <status> |
| **Sensor Health** | <health> |
| **Exposure Level** | <level> |
| **Azure AD Joined** | <Yes/No> |
| **Internet Facing** | <Yes/No> |
| **Join Type** | <type> |
---
## IP Intelligence<TableofexternalIPsfromnetworkconnectionsandsign-indata.Run `enrich_ips.py` fortopIPs.>
| IP Address | Source | Location | ISP/Org | VPN | Abuse Score | Reports | Risk |
|------------|--------|----------|---------|-----|-------------|---------|------|
| <ip> | 🔵 Network / 🔵 Sign-in / 🔴 TI Match | <city, country> | <org> | 🟢 No / 🔴 Yes | <score>% | <count> | HIGH/MED/LOW |
---
## Risk Assessment### Risk Score: <XX>/100 — 🔴 CRITICAL / 🔴 HIGH / 🟠 MEDIUM / 🟡 LOW / 🟢 INFORMATIONAL### Risk Factors
| Factor | Finding |
|--------|---------|
| 🔴/🟠/🟡 **<FactorName>** | <Evidence-groundedfindingwithspecificnumbers> |
### Mitigating Factors
| Factor | Finding |
|--------|---------|
| 🟢 **<FactorName>** | <Evidence-groundedfindingwithspecificnumbers> |
---
## Recommendations### Critical Actions<Numberedlistofcriticalactionswithevidence.Onlyincludeifcriticalfindingsexist.>### High Priority Actions<Numberedlistofhigh-priorityactionswithevidence.>### Monitoring Actions (14-Day Follow-Up)<Bulletedlistofongoingmonitoringrecommendations.>
---
## Appendix: Query Details
| # | Query | Table(s) | Tool | Records | Execution |
|---|-------|----------|------|--------:|----------:|
| 1 | Device Sign-In Events | SigninLogs | Data Lake | <count> | <time> |
| 2 | Security Alerts | SecurityAlert | Data Lake | <count> | <time> |
| 3 | Process Events | DeviceProcessEvents | Data Lake | <count> | <time> |
| 4 | Network Connections | DeviceNetworkEvents | Data Lake | <count> | <time> |
| 5 | File Events | DeviceFileEvents | Data Lake | <count> | <time> |
| 6 | Registry Events | DeviceRegistryEvents | Data Lake | <count> | <time> |
| 7 | Security Incidents | SecurityAlert, SecurityIncident | Data Lake | <count> | <time> |
| 8 | Device Inventory | DeviceInfo | Data Lake | <count> | <time> |
| 9 | Software Inventory | DeviceTvmSoftwareInventory | Advanced Hunting | <count> | <time> |
| 10 | Vulnerabilities | DeviceTvmSoftwareVulnerabilities | Advanced Hunting | <count> | <time> |
| 11 | Logon Events | DeviceLogonEvents | Data Lake | <count> | <time> |
| 12 | Threat Intelligence | ThreatIntelIndicators, DeviceNetworkEvents | Data Lake | <count> | <time> |
| — | Device Profile | Microsoft Graph API | Graph | 1 | <time> |
| — | Device Owners/Users | Microsoft Graph API | Graph | <count> | <time> |
| — | Machine Details | Defender for Endpoint API | MDE | 1 | <time> |
| — | Logged-On Users | Defender for Endpoint API | MDE | <count> | <time> |
*Query definitions: see the Sample KQL Queries section in this SKILL.md file.***Do NOT include full KQL text in the appendix** — the canonical queries are already documented in this SKILL.md file. The appendix serves as an audit trail only.
---
**Investigation Timeline:**- [MM:SS] ✓ Phase 1: Device ID retrieval (<X>s)
- [MM:SS] ✓ Phase 2: Parallel data collection (<X>s)
- [MM:SS] ✓ IP Enrichment (<X>s)
- [MM:SS] ✓ Phase 3: Report generation (<X>s)
-**Total Investigation Time:**<duration>
Markdown Report Authoring Guidelines
Populate every section — even if data is empty. Use the ✅ No <X> detected... pattern for empty sections.
Never invent data — follow the Evidence-Based Analysis global rule strictly. Every number in the report must come from a query result.
Risk assessment is dynamic — calculate risk score using the weighted framework in the Risk Assessment Framework section (Defender Risk Score 25%, Active Alerts 25%, Vulnerabilities 20%, Compliance Status 15%, Sign-in Anomalies 15%).
IP enrichment — run enrich_ips.py for external IPs from network connections and sign-in data. If enrich_ips.py is unavailable, use Sentinel ThreatIntelIndicators data as fallback.
PII-Free — the report file is saved to reports/ which is gitignored. However, exercise caution with any files that may be shared externally.
Emoji consistency — follow the Emoji Formatting table from copilot-instructions.md for all risk/status indicators.
Query appendix — include record counts and execution times but NOT full KQL text. Reference the SKILL.md query numbers.
Trust type context — always reference the device trust type in the Executive Summary and Risk Assessment, as it affects the security implications.
JSON Export Structure
Export MCP query results to a single JSON file with these required keys:
Try searching by deviceId instead of displayName, check case sensitivity
Defender Device ID not matching
Use Advanced Hunting to find correct Defender ID by device name
DeviceName query returns empty
Use startswith instead of =~ - DeviceName often contains FQDN (e.g., hostname.domain.com)
SigninLogs DeviceDetail fails with union
DeviceDetail is dynamic in SigninLogs but string in AADNonInteractiveUserSignInLogs - query tables separately, don't use union isfuzzy=true with DeviceDetail filtering
RiskScore column not found
RiskScore is NOT in DeviceInfo table - use GetDefenderMachine API for riskScore
Missing compliance data
Device may not be MDM enrolled - check isManaged field
No process events
Device may not be onboarded to Defender for Endpoint
Trust type is null
Device may be partially registered - check registrationDateTime
Query timeout on DeviceEvents
Reduce date range or add more specific filters
BitLocker query fails
Verify permissions and that BitLocker is enabled on device