functions/Invoke-XdrEndpointDeviceAction.ps1
|
function Invoke-XdrEndpointDeviceAction { <# .SYNOPSIS Invokes response actions on an endpoint device in Microsoft Defender XDR. .DESCRIPTION Unified cmdlet for executing device response actions including antivirus scans, device isolation, app execution restriction, investigation package collection, support log collection, troubleshooting mode, tag management, asset value, criticality level, exclusion state, policy sync, automated investigation, and live response sessions. For responseApiPortal actions (Scan, Isolate, Restrict, etc.), the cmdlet auto-fetches OsPlatform and SenseClientVersion from the device. For other actions, the cmdlet wraps dedicated cmdlets like Set-XdrEndpointDeviceTag, Set-XdrEndpointDeviceAssetValue, etc. .PARAMETER DeviceId The device ID (SenseMachineId) of the target device. Required for all actions. .PARAMETER Comment A comment describing the reason for the action. Used as RequestorComment for API calls. .PARAMETER Scan Runs an antivirus scan on the device. Valid values: Quick, Full. .PARAMETER Isolate Isolates the device from the network. Valid values: Full, Selective. .PARAMETER ReleaseFromIsolation Releases the device from network isolation. .PARAMETER RestrictAppExecution Restricts application execution on the device to Microsoft-signed binaries only. macOS note: this action is currently unsupported and should be attempted only for capability detection/documentation. .PARAMETER RemoveAppExecutionRestriction Removes application execution restriction from the device. macOS note: this action is currently unsupported and should be attempted only for capability detection/documentation. .PARAMETER CollectInvestigationPackage Collects a forensic investigation package from the device. .PARAMETER CollectSupportLogs Collects support diagnostic logs from the device. .PARAMETER StartTroubleshoot Enables troubleshooting mode on the device. .PARAMETER TroubleshootDurationHours Duration in hours for troubleshooting mode. Defaults to 4. Maximum 12. .PARAMETER StopTroubleshoot Disables troubleshooting mode on the device. .PARAMETER SetTags Array of tag strings to set on the device. Replaces all existing user-defined tags. Wraps Set-XdrEndpointDeviceTag. For add/remove semantics, use Set-XdrEndpointDeviceTag -Add or -Remove directly. .PARAMETER SetAssetValue Sets the asset value of the device. Valid values: Low, Normal, High. Wraps Set-XdrEndpointDeviceAssetValue. .PARAMETER SetCriticalityLevel Sets the criticality level. Valid values: VeryHigh, High, Medium, Low, Reset. Reset removes the criticality level. Wraps Set-XdrEndpointDeviceCriticalityLevel. .PARAMETER SetExclusionState Sets the exclusion state. Valid values: Excluded, Included. Wraps Set-XdrEndpointDeviceExclusionState. .PARAMETER Justification Justification for exclusion state change. .PARAMETER Notes Additional notes for the exclusion state change. .PARAMETER ForceSync Forces a policy sync on the device. Wraps Invoke-XdrEndpointDevicePolicySync. .PARAMETER StartInvestigation Starts an automated investigation. Wraps Invoke-XdrEndpointDeviceAutomatedInvestigation. macOS note: this action is currently unsupported and should be attempted only for capability detection/documentation. .PARAMETER LiveResponse Starts an interactive Live Response session. Wraps Connect-XdrEndpointDeviceLiveResponse. .PARAMETER Confirm Prompts for confirmation before executing the operation. .PARAMETER WhatIf Shows what would happen if the cmdlet runs without actually performing the action. .EXAMPLE Invoke-XdrEndpointDeviceAction -DeviceId "980dddb7036eae7e38d30dee7f11b51e573a6fc2" -Scan Quick Runs a quick antivirus scan on the specified device. .EXAMPLE Invoke-XdrEndpointDeviceAction -DeviceId "980dddb7036eae7e38d30dee7f11b51e573a6fc2" -Scan Full -Comment "macOS validation full scan" Runs a full antivirus scan with a comment. .EXAMPLE Invoke-XdrEndpointDeviceAction -DeviceId "980dddb7036eae7e38d30dee7f11b51e573a6fc2" -Isolate Full -Comment "macOS containment test" Fully isolates the device from the network. .EXAMPLE Invoke-XdrEndpointDeviceAction -DeviceId "980dddb7036eae7e38d30dee7f11b51e573a6fc2" -ReleaseFromIsolation -Comment "macOS containment test rollback" Releases the device from isolation. .EXAMPLE Invoke-XdrEndpointDeviceAction -DeviceId "980dddb7036eae7e38d30dee7f11b51e573a6fc2" -CollectInvestigationPackage -Comment "macOS evidence collection" Collects a forensic investigation package from the device. .EXAMPLE Invoke-XdrEndpointDeviceAction -DeviceId "980dddb7036eae7e38d30dee7f11b51e573a6fc2" -LiveResponse Opens an interactive Live Response session to the device. .EXAMPLE Invoke-XdrEndpointDeviceAction -DeviceId "980dddb7036eae7e38d30dee7f11b51e573a6fc2" -SetAssetValue High Sets the asset value to High. .EXAMPLE Invoke-XdrEndpointDeviceAction -DeviceId "980dddb7036eae7e38d30dee7f11b51e573a6fc2" -ForceSync -Comment "macOS policy sync validation" Forces a policy sync on the device. .NOTES macOS validation baseline: February 24, 2026. Validated on macOS: Scan (Quick, Full), Isolate (Full, Selective), ReleaseFromIsolation, CollectInvestigationPackage, StartTroubleshoot, StopTroubleshoot, SetTags, SetAssetValue, SetCriticalityLevel, SetExclusionState, ForceSync, LiveResponse. Service-dependent on macOS: CollectSupportLogs (may return transient backend InternalServerError). Not currently supported on macOS: RestrictAppExecution, RemoveAppExecutionRestriction, StartInvestigation. .OUTPUTS PSCustomObject Returns the API response from the action. For Live Response, enters an interactive session. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Delegated to ShouldProcess in process block')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Parameters define parameter sets')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'Scan')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias('MachineId', 'SenseMachineId')] [ValidateLength(40,40)] [ValidatePattern('^[0-9a-fA-F]{40}$')] [string]$DeviceId, [Parameter()] [string]$Comment, # Scan [Parameter(Mandatory = $true, ParameterSetName = 'Scan')] [ValidateSet('Quick', 'Full')] [string]$Scan, # Isolate [Parameter(Mandatory = $true, ParameterSetName = 'Isolate')] [ValidateSet('Full', 'Selective')] [string]$Isolate, # Release From Isolation [Parameter(Mandatory = $true, ParameterSetName = 'ReleaseFromIsolation')] [switch]$ReleaseFromIsolation, # Restrict App Execution [Parameter(Mandatory = $true, ParameterSetName = 'RestrictAppExecution')] [switch]$RestrictAppExecution, # Remove App Execution Restriction [Parameter(Mandatory = $true, ParameterSetName = 'RemoveAppExecutionRestriction')] [switch]$RemoveAppExecutionRestriction, # Collect Investigation Package [Parameter(Mandatory = $true, ParameterSetName = 'CollectInvestigationPackage')] [switch]$CollectInvestigationPackage, # Collect Support Logs [Parameter(Mandatory = $true, ParameterSetName = 'CollectSupportLogs')] [switch]$CollectSupportLogs, # Troubleshoot [Parameter(Mandatory = $true, ParameterSetName = 'StartTroubleshoot')] [switch]$StartTroubleshoot, [Parameter(ParameterSetName = 'StartTroubleshoot')] [ValidateRange(1, 12)] [int]$TroubleshootDurationHours = 4, [Parameter(Mandatory = $true, ParameterSetName = 'StopTroubleshoot')] [switch]$StopTroubleshoot, # Set Tags [Parameter(Mandatory = $true, ParameterSetName = 'SetTags')] [string[]]$SetTags, # Set Asset Value [Parameter(Mandatory = $true, ParameterSetName = 'SetAssetValue')] [ValidateSet('Low', 'Normal', 'High')] [string]$SetAssetValue, # Set Criticality Level [Parameter(Mandatory = $true, ParameterSetName = 'SetCriticalityLevel')] [ValidateSet('VeryHigh', 'High', 'Medium', 'Low', 'Reset')] [string]$SetCriticalityLevel, # Set Exclusion State [Parameter(Mandatory = $true, ParameterSetName = 'SetExclusionState')] [ValidateSet('Excluded', 'Included')] [string]$SetExclusionState, [Parameter(ParameterSetName = 'SetExclusionState')] [string]$Justification, [Parameter(ParameterSetName = 'SetExclusionState')] [string]$Notes, # Force Sync [Parameter(Mandatory = $true, ParameterSetName = 'ForceSync')] [switch]$ForceSync, # Start Investigation [Parameter(Mandatory = $true, ParameterSetName = 'StartInvestigation')] [switch]$StartInvestigation, # Live Response [Parameter(Mandatory = $true, ParameterSetName = 'LiveResponse')] [switch]$LiveResponse ) begin { Update-XdrConnectionSettings } process { switch ($PSCmdlet.ParameterSetName) { 'Scan' { $device = Get-XdrEndpointDevice -DeviceId $DeviceId $commentText = if ($Comment) { $Comment } else { "$Scan Scan - Performed by $env:USERNAME via XDRInternals" } $body = @{ MachineId = $DeviceId RequestorComment = $commentText OsPlatform = $device.OsPlatform SenseClientVersion = $device.SenseClientVersion Params = @{ ScanType = $Scan } Type = 'ScanRequest' } | ConvertTo-Json -Depth 10 if ($PSCmdlet.ShouldProcess("Device $($device.ComputerDnsName) ($DeviceId)", "$Scan Scan")) { try { $Uri = "https://security.microsoft.com/apiproxy/mtp/responseApiPortal/requests/create" Write-Verbose "Submitting $Scan scan for device $DeviceId" $result = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers $result.PSObject.TypeNames.Insert(0, 'XdrEndpointDeviceActionResult') return $result } catch { $errorDetail = Get-XdrParsedErrorDetail -ErrorRecord $_ if ($errorDetail.error.code -eq 'ActiveRequestAlreadyExists' -or $errorDetail.Message -match 'pending|already|conflict|concurrent') { Write-Error "A scan is already pending or running on this device. Wait for it to complete or cancel it first. API: $(if ($null -ne $errorDetail.error.message) { $errorDetail.error.message } else { $errorDetail.Message })" } else { Write-Error "Failed to submit $Scan scan: $_" } } } } 'Isolate' { $device = Get-XdrEndpointDevice -DeviceId $DeviceId $commentText = if ($Comment) { $Comment } else { "Isolate device ($Isolate) - Performed by $env:USERNAME via XDRInternals" } $body = @{ MachineId = $DeviceId RequestorComment = $commentText OsPlatform = $device.OsPlatform SenseClientVersion = $device.SenseClientVersion Type = 'IsolationRequest' IsolationType = $Isolate Action = 'Isolate' } | ConvertTo-Json -Depth 10 if ($PSCmdlet.ShouldProcess("Device $($device.ComputerDnsName) ($DeviceId)", "$Isolate Isolation")) { try { $Uri = "https://security.microsoft.com/apiproxy/mtp/responseApiPortal/requests/create" Write-Verbose "Submitting $Isolate isolation for device $DeviceId" $result = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers $result.PSObject.TypeNames.Insert(0, 'XdrEndpointDeviceActionResult') return $result } catch { $errorDetail = Get-XdrParsedErrorDetail -ErrorRecord $_ if ($errorDetail.error.code -eq 'ActiveRequestAlreadyExists' -or $errorDetail.Message -match 'already isolated|pending isolation') { Write-Error "Device is already isolated or has a pending isolation request. Release isolation first with -ReleaseFromIsolation. API: $(if ($null -ne $errorDetail.error.message) { $errorDetail.error.message } else { $errorDetail.Message })" } else { Write-Error "Failed to isolate device: $_" } } } } 'ReleaseFromIsolation' { $device = Get-XdrEndpointDevice -DeviceId $DeviceId $commentText = if ($Comment) { $Comment } else { "Release from isolation - Performed by $env:USERNAME via XDRInternals" } $body = @{ MachineId = $DeviceId RequestorComment = $commentText OsPlatform = $device.OsPlatform SenseClientVersion = $device.SenseClientVersion Type = 'IsolationRequest' Action = 'Unisolate' } | ConvertTo-Json -Depth 10 if ($PSCmdlet.ShouldProcess("Device $($device.ComputerDnsName) ($DeviceId)", "Release from isolation")) { try { $Uri = "https://security.microsoft.com/apiproxy/mtp/responseApiPortal/requests/create" Write-Verbose "Releasing device $DeviceId from isolation" $result = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers $result.PSObject.TypeNames.Insert(0, 'XdrEndpointDeviceActionResult') return $result } catch { $errorDetail = Get-XdrParsedErrorDetail -ErrorRecord $_ if ($errorDetail.error.code -eq 'ActiveRequestAlreadyExists' -or $errorDetail.Message -match 'not isolated|not in isolation') { Write-Error "Device is not currently isolated. API: $($errorDetail.Message)" } else { Write-Error "Failed to release isolation: $_" } } } } 'RestrictAppExecution' { $device = Get-XdrEndpointDevice -DeviceId $DeviceId $commentText = if ($Comment) { $Comment } else { "Restrict app execution - Performed by $env:USERNAME via XDRInternals" } $body = @{ MachineId = $DeviceId RequestorComment = $commentText OsPlatform = $device.OsPlatform SenseClientVersion = $device.SenseClientVersion ClientVersion = $device.SenseClientVersion PolicyType = 'Restrict' Type = 'RestrictExecutionRequest' } | ConvertTo-Json -Depth 10 if ($PSCmdlet.ShouldProcess("Device $($device.ComputerDnsName) ($DeviceId)", "Restrict app execution")) { try { $Uri = "https://security.microsoft.com/apiproxy/mtp/responseApiPortal/requests/create" Write-Verbose "Restricting app execution on device $DeviceId" $result = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers $result.PSObject.TypeNames.Insert(0, 'XdrEndpointDeviceActionResult') return $result } catch { $errorDetail = Get-XdrParsedErrorDetail -ErrorRecord $_ if ($errorDetail.error.code -eq 'ActiveRequestAlreadyExists' -or $errorDetail.Message -match 'already restricted|pending') { Write-Error "App execution restriction is already active or pending. Remove restriction first with -RemoveAppExecutionRestriction. API: $($errorDetail.Message)" } else { Write-Error "Failed to restrict app execution: $_" } } } } 'RemoveAppExecutionRestriction' { $device = Get-XdrEndpointDevice -DeviceId $DeviceId $commentText = if ($Comment) { $Comment } else { "Remove app restriction - Performed by $env:USERNAME via XDRInternals" } $body = @{ MachineId = $DeviceId RequestorComment = $commentText OsPlatform = $device.OsPlatform SenseClientVersion = $device.SenseClientVersion ClientVersion = $device.SenseClientVersion PolicyType = 'Unrestrict' Type = 'RestrictExecutionRequest' } | ConvertTo-Json -Depth 10 if ($PSCmdlet.ShouldProcess("Device $($device.ComputerDnsName) ($DeviceId)", "Remove app execution restriction")) { try { $Uri = "https://security.microsoft.com/apiproxy/mtp/responseApiPortal/requests/create" Write-Verbose "Removing app restriction on device $DeviceId" $result = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers $result.PSObject.TypeNames.Insert(0, 'XdrEndpointDeviceActionResult') return $result } catch { $errorDetail = Get-XdrParsedErrorDetail -ErrorRecord $_ if ($errorDetail.error.code -eq 'ActiveRequestAlreadyExists' -or $errorDetail.Message -match 'not restricted|no restriction') { Write-Error "App execution is not currently restricted. API: $($errorDetail.Message)" } else { Write-Error "Failed to remove app restriction: $_" } } } } 'CollectInvestigationPackage' { $device = Get-XdrEndpointDevice -DeviceId $DeviceId $commentText = if ($Comment) { $Comment } else { "Collect investigation package - Performed by $env:USERNAME via XDRInternals" } $body = @{ MachineId = $DeviceId RequestorComment = $commentText OsPlatform = $device.OsPlatform SenseClientVersion = $device.SenseClientVersion Type = 'ForensicsRequest' } | ConvertTo-Json -Depth 10 if ($PSCmdlet.ShouldProcess("Device $($device.ComputerDnsName) ($DeviceId)", "Collect investigation package")) { try { $Uri = "https://security.microsoft.com/apiproxy/mtp/responseApiPortal/requests/create" Write-Verbose "Collecting investigation package from device $DeviceId" $result = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers $result.PSObject.TypeNames.Insert(0, 'XdrEndpointDeviceActionResult') return $result } catch { $errorDetail = Get-XdrParsedErrorDetail -ErrorRecord $_ if ($errorDetail.error.code -eq 'ActiveRequestAlreadyExists') { Write-Error "An investigation package collection is already in progress on this device. Wait for it to complete first. API: $($errorDetail.error.message)" } else { Write-Error "Failed to collect investigation package: $_" } } } } 'CollectSupportLogs' { $device = Get-XdrEndpointDevice -DeviceId $DeviceId $commentText = if ($Comment) { $Comment } else { "Collect support logs - Performed by $env:USERNAME via XDRInternals" } $body = @{ MachineId = $DeviceId RequestorComment = $commentText OsPlatform = $device.OsPlatform SenseClientVersion = $device.SenseClientVersion Type = 'LogsCollectionRequest' } | ConvertTo-Json -Depth 10 if ($PSCmdlet.ShouldProcess("Device $($device.ComputerDnsName) ($DeviceId)", "Collect support logs")) { try { $Uri = "https://security.microsoft.com/apiproxy/mtp/responseApiPortal/requests/create" Write-Verbose "Collecting support logs from device $DeviceId" $result = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers $result.PSObject.TypeNames.Insert(0, 'XdrEndpointDeviceActionResult') return $result } catch { $errorDetail = Get-XdrParsedErrorDetail -ErrorRecord $_ if ($errorDetail.error.code -eq 'ActiveRequestAlreadyExists') { Write-Error "A support log collection is already in progress on this device. Wait for it to complete first. API: $($errorDetail.error.message)" } else { Write-Error "Failed to collect support logs: $_" } } } } 'StartTroubleshoot' { $device = Get-XdrEndpointDevice -DeviceId $DeviceId $commentText = if ($Comment) { $Comment } else { "Start troubleshoot mode - Performed by $env:USERNAME via XDRInternals" } $now = (Get-Date).ToUniversalTime() $expiration = $now.AddHours($TroubleshootDurationHours) $body = @{ MachineId = $DeviceId RequestorComment = $commentText Type = 'TroubleshootRequest' TroubleshootState = 1 TroubleshootExpirationDateTimeUtc = $expiration.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') TroubleshootStartDateTimeUtc = $now.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') ParamsJsonFormatVersion = 1 RequestSource = 2 OsPlatform = $device.OsPlatform SenseClientVersion = $device.SenseClientVersion } | ConvertTo-Json -Depth 10 if ($PSCmdlet.ShouldProcess("Device $($device.ComputerDnsName) ($DeviceId)", "Start troubleshoot mode ($TroubleshootDurationHours hours)")) { try { $Uri = "https://security.microsoft.com/apiproxy/mtp/responseApiPortal/requests/create" Write-Verbose "Starting troubleshoot mode on device $DeviceId for $TroubleshootDurationHours hours" $result = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers $result.PSObject.TypeNames.Insert(0, 'XdrEndpointDeviceActionResult') return $result } catch { $errorDetail = Get-XdrParsedErrorDetail -ErrorRecord $_ if ($errorDetail.error.code -eq 'ActiveRequestAlreadyExists' -or $errorDetail.Message -match 'already|active|enabled') { Write-Error "Troubleshoot mode is already active. Stop it first with -StopTroubleshoot. API: $(if ($null -ne $errorDetail.error.message) { $errorDetail.error.message } else { $errorDetail.Message })" } else { Write-Error "Failed to start troubleshoot mode: $_" } } } } 'StopTroubleshoot' { $device = Get-XdrEndpointDevice -DeviceId $DeviceId $commentText = if ($Comment) { $Comment } else { "Stop troubleshoot mode - Performed by $env:USERNAME via XDRInternals" } $body = @{ MachineId = $DeviceId RequestorComment = $commentText Type = 'TroubleshootRequest' TroubleshootState = 0 TroubleshootExpirationDateTimeUtc = (Get-Date).ToUniversalTime().AddMinutes(5).ToString('yyyy-MM-ddTHH:mm:ss.fffZ') ParamsJsonFormatVersion = 1 RequestSource = 2 OsPlatform = $device.OsPlatform SenseClientVersion = $device.SenseClientVersion } | ConvertTo-Json -Depth 10 if ($PSCmdlet.ShouldProcess("Device $($device.ComputerDnsName) ($DeviceId)", "Stop troubleshoot mode")) { try { $Uri = "https://security.microsoft.com/apiproxy/mtp/responseApiPortal/requests/create" Write-Verbose "Stopping troubleshoot mode on device $DeviceId" $result = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/json" -Body $body -WebSession $script:session -Headers $script:headers $result.PSObject.TypeNames.Insert(0, 'XdrEndpointDeviceActionResult') return $result } catch { $errorDetail = Get-XdrParsedErrorDetail -ErrorRecord $_ if ($errorDetail.error.code -eq 'ActiveRequestAlreadyExists' -or $errorDetail.Message -match 'not active|not enabled') { Write-Error "Troubleshoot mode is not currently active on this device. API: $(if ($null -ne $errorDetail.error.message) { $errorDetail.error.message } else { $errorDetail.Message })" } else { Write-Error "Failed to stop troubleshoot mode: $_" } } } } 'SetTags' { Set-XdrEndpointDeviceTag -DeviceId $DeviceId -Tags $SetTags } 'SetAssetValue' { Set-XdrEndpointDeviceAssetValue -DeviceId $DeviceId -AssetValue $SetAssetValue } 'SetCriticalityLevel' { Set-XdrEndpointDeviceCriticalityLevel -DeviceId $DeviceId -CriticalityLevel $SetCriticalityLevel } 'SetExclusionState' { $params = @{ DeviceId = $DeviceId ExclusionState = $SetExclusionState } if ($Justification) { $params['Justification'] = $Justification } if ($Notes) { $params['Notes'] = $Notes } Set-XdrEndpointDeviceExclusionState @params } 'ForceSync' { $params = @{ DeviceId = $DeviceId } if ($Comment) { $params['Comment'] = $Comment } Invoke-XdrEndpointDevicePolicySync @params } 'StartInvestigation' { Invoke-XdrEndpointDeviceAutomatedInvestigation -DeviceId $DeviceId } 'LiveResponse' { Connect-XdrEndpointDeviceLiveResponse -DeviceId $DeviceId } } } end { } } |