LibreDevOpsHelpers.Defender/LibreDevOpsHelpers.Defender.psm1
|
Set-StrictMode -Version Latest # This module spans four Microsoft Defender surfaces: # * Defender for Cloud - Azure posture via the 'az security' CLI. # * Defender for Endpoint / XDR - the Graph Security API plus the Defender for Endpoint # API (api.securitycenter.microsoft.com), via Invoke-LdoGraphRequest. # * Defender Antivirus - the built-in Windows 'Defender' module cmdlets (Windows only). # * Defender for Endpoint on Linux - the 'mdatp' CLI (Linux only). # --------------------------------------------------------------------------------------------- # Defender for Cloud (az security) # --------------------------------------------------------------------------------------------- function Get-LdoDefenderSecureScore { <# .SYNOPSIS Returns a Microsoft Defender for Cloud secure score. .DESCRIPTION Runs 'az security secure-scores show' and returns the parsed score object. Requires the Azure CLI to be signed in. .PARAMETER Name Secure score control name. Defaults to 'ascScore' (the overall subscription score). .EXAMPLE (Get-LdoDefenderSecureScore).properties.score.percentage .OUTPUTS System.Management.Automation.PSCustomObject #> [CmdletBinding()] [OutputType([pscustomobject])] param( [ValidateNotNullOrEmpty()] [string]$Name = 'ascScore' ) Assert-LdoCommand -Name 'az' Write-LdoLog -Level INFO -Message "Getting Defender for Cloud secure score '$Name'." $json = & az security secure-scores show --name $Name -o json Assert-LdoLastExitCode -Operation 'az security secure-scores show' return ($json | ConvertFrom-Json) } function Get-LdoDefenderRecommendation { <# .SYNOPSIS Returns Microsoft Defender for Cloud security assessments (recommendations). .DESCRIPTION Runs 'az security assessment list' and returns the assessments, optionally filtered to unhealthy ones. Requires the Azure CLI to be signed in. .PARAMETER UnhealthyOnly When set, returns only assessments with an unhealthy status. .EXAMPLE Get-LdoDefenderRecommendation -UnhealthyOnly .OUTPUTS System.Object[] #> [CmdletBinding()] [OutputType([object[]])] param( [switch]$UnhealthyOnly ) Assert-LdoCommand -Name 'az' Write-LdoLog -Level INFO -Message 'Listing Defender for Cloud assessments.' $json = & az security assessment list -o json Assert-LdoLastExitCode -Operation 'az security assessment list' $items = @($json | ConvertFrom-Json) if ($UnhealthyOnly) { $items = @($items | Where-Object { $_.status.code -eq 'Unhealthy' }) } return $items } function Get-LdoDefenderPlan { <# .SYNOPSIS Returns Microsoft Defender for Cloud plan (pricing) tiers. .DESCRIPTION Runs 'az security pricing list' (or 'show' for a single plan) and returns the result. Requires the Azure CLI to be signed in. .PARAMETER Name Optional plan name (for example VirtualMachines, StorageAccounts). Lists all when omitted. .EXAMPLE Get-LdoDefenderPlan -Name StorageAccounts .OUTPUTS System.Management.Automation.PSCustomObject #> [CmdletBinding()] [OutputType([pscustomobject])] param( [string]$Name ) Assert-LdoCommand -Name 'az' $azArgs = @('security', 'pricing') if ($Name) { $azArgs += @('show', '--name', $Name) } else { $azArgs += 'list' } $azArgs += @('-o', 'json') Write-LdoLog -Level INFO -Message "az $($azArgs -join ' ')" $json = & az @azArgs Assert-LdoLastExitCode -Operation 'az security pricing' return ($json | ConvertFrom-Json) } function Set-LdoDefenderPlan { <# .SYNOPSIS Sets the tier of a Microsoft Defender for Cloud plan. .DESCRIPTION Runs 'az security pricing create' to set a plan to Free or Standard. Requires the Azure CLI to be signed in with permission to change Defender plans. .PARAMETER Name Plan name (for example VirtualMachines, StorageAccounts, KeyVaults). .PARAMETER Tier Free or Standard. .EXAMPLE Set-LdoDefenderPlan -Name StorageAccounts -Tier Standard .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Name, [Parameter(Mandatory)][ValidateSet('Free', 'Standard')][string]$Tier ) Assert-LdoCommand -Name 'az' Write-LdoLog -Level INFO -Message "Setting Defender plan '$Name' to tier '$Tier'." & az security pricing create --name $Name --tier $Tier -o none Assert-LdoLastExitCode -Operation "az security pricing create ($Name)" Write-LdoLog -Level SUCCESS -Message "Defender plan '$Name' set to '$Tier'." } # --------------------------------------------------------------------------------------------- # Defender for Endpoint / XDR (Graph Security API + Defender for Endpoint API) # --------------------------------------------------------------------------------------------- $script:LdoMdeApiResource = 'https://api.securitycenter.microsoft.com' function Get-LdoDefenderAlert { <# .SYNOPSIS Returns Microsoft Defender XDR alerts from the Graph Security API. .DESCRIPTION Queries /security/alerts_v2, optionally filtered by severity and status. Requires an Az context with Graph permission to read security alerts (SecurityAlert.Read.All). .PARAMETER Severity Filter by severity: low, medium, high, or informational. .PARAMETER Status Filter by status: new, inProgress, or resolved. .PARAMETER Top Maximum number of alerts to return. Defaults to 50. .EXAMPLE Get-LdoDefenderAlert -Severity high -Status new .OUTPUTS System.Object[] #> [CmdletBinding()] [OutputType([object[]])] param( [ValidateSet('low', 'medium', 'high', 'informational')][string]$Severity, [ValidateSet('new', 'inProgress', 'resolved')][string]$Status, [ValidateRange(1, 1000)][int]$Top = 50 ) $filters = @() if ($Severity) { $filters += "severity eq '$Severity'" } if ($Status) { $filters += "status eq '$Status'" } $uri = "https://graph.microsoft.com/v1.0/security/alerts_v2?`$top=$Top" if ($filters) { $uri += "&`$filter=" + [uri]::EscapeDataString($filters -join ' and ') } Write-LdoLog -Level INFO -Message 'Querying Defender XDR alerts.' $response = Invoke-LdoGraphRequest -Uri $uri return @($response.value) } function Invoke-LdoDefenderHuntingQuery { <# .SYNOPSIS Runs an advanced hunting (KQL) query against Microsoft Defender XDR. .DESCRIPTION Posts the query to /security/runHuntingQuery and returns the result rows. Requires an Az context with Graph permission to run hunting queries (ThreatHunting.Read.All). .PARAMETER Query The KQL hunting query. .EXAMPLE Invoke-LdoDefenderHuntingQuery -Query 'DeviceProcessEvents | take 10' .OUTPUTS System.Object[] #> [CmdletBinding()] [OutputType([object[]])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Query ) Write-LdoLog -Level INFO -Message 'Running Defender advanced hunting query.' $response = Invoke-LdoGraphRequest -Method Post ` -Uri 'https://graph.microsoft.com/v1.0/security/runHuntingQuery' ` -Body @{ query = $Query } return @($response.results) } function Invoke-LdoDefenderDeviceIsolation { <# .SYNOPSIS Isolates (or releases) a device in Microsoft Defender for Endpoint. .DESCRIPTION Calls the Defender for Endpoint API to isolate a machine, or release it from isolation with -Release. Requires an Az context with the Machine.Isolate permission on the Defender for Endpoint API. .PARAMETER DeviceId The Defender for Endpoint machine id. .PARAMETER Comment Reason recorded with the action. .PARAMETER IsolationType Full or Selective. Ignored when -Release is set. .PARAMETER Release When set, releases the device from isolation instead of isolating it. .EXAMPLE Invoke-LdoDefenderDeviceIsolation -DeviceId $id -Comment 'IR-1234 containment' .OUTPUTS System.Management.Automation.PSCustomObject #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$DeviceId, [string]$Comment = 'Action performed via LibreDevOpsHelpers', [ValidateSet('Full', 'Selective')][string]$IsolationType = 'Full', [switch]$Release ) $action = if ($Release) { 'unisolate' } else { 'isolate' } $body = @{ Comment = $Comment } if (-not $Release) { $body['IsolationType'] = $IsolationType } Write-LdoLog -Level INFO -Message "Defender for Endpoint $action on device $DeviceId." $response = Invoke-LdoGraphRequest -Method Post ` -Uri "$script:LdoMdeApiResource/api/machines/$DeviceId/$action" ` -Resource $script:LdoMdeApiResource ` -Body $body Write-LdoLog -Level SUCCESS -Message "Submitted $action for device $DeviceId." return $response } function Invoke-LdoDefenderAvScan { <# .SYNOPSIS Triggers an antivirus scan on a Microsoft Defender for Endpoint device. .DESCRIPTION Calls the Defender for Endpoint API to run a Quick or Full antivirus scan on a machine. Requires an Az context with the Machine.Scan permission on the Defender for Endpoint API. .PARAMETER DeviceId The Defender for Endpoint machine id. .PARAMETER ScanType Quick or Full. Defaults to Quick. .PARAMETER Comment Reason recorded with the action. .EXAMPLE Invoke-LdoDefenderAvScan -DeviceId $id -ScanType Full .OUTPUTS System.Management.Automation.PSCustomObject #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$DeviceId, [ValidateSet('Quick', 'Full')][string]$ScanType = 'Quick', [string]$Comment = 'Scan triggered via LibreDevOpsHelpers' ) Write-LdoLog -Level INFO -Message "Defender for Endpoint $ScanType AV scan on device $DeviceId." $response = Invoke-LdoGraphRequest -Method Post ` -Uri "$script:LdoMdeApiResource/api/machines/$DeviceId/runAntiVirusScan" ` -Resource $script:LdoMdeApiResource ` -Body @{ Comment = $Comment; ScanType = $ScanType } Write-LdoLog -Level SUCCESS -Message "Submitted $ScanType AV scan for device $DeviceId." return $response } # --------------------------------------------------------------------------------------------- # Defender Antivirus (Windows, built-in Defender module) # --------------------------------------------------------------------------------------------- function Assert-LdoWindowsDefender { # Internal. Throws unless running on Windows with the Defender cmdlets available. [CmdletBinding()] [OutputType([void])] param() if ((Get-LdoOperatingSystem) -ne 'Windows') { throw 'Windows Defender Antivirus cmdlets are only available on Windows.' } Assert-LdoCommand -Name 'Get-MpComputerStatus' } function Get-LdoDefenderAvStatus { <# .SYNOPSIS Returns Windows Defender Antivirus status. .DESCRIPTION Wraps Get-MpComputerStatus. Windows only. .EXAMPLE (Get-LdoDefenderAvStatus).RealTimeProtectionEnabled .OUTPUTS System.Management.Automation.PSCustomObject #> [CmdletBinding()] [OutputType([pscustomobject])] param() Assert-LdoWindowsDefender return Get-MpComputerStatus } function Start-LdoDefenderAvScan { <# .SYNOPSIS Starts a Windows Defender Antivirus scan. .DESCRIPTION Wraps Start-MpScan. Windows only. .PARAMETER ScanType Quick or Full. Defaults to Quick. .EXAMPLE Start-LdoDefenderAvScan -ScanType Full .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param( [ValidateSet('Quick', 'Full')][string]$ScanType = 'Quick' ) Assert-LdoWindowsDefender Write-LdoLog -Level INFO -Message "Starting Windows Defender $ScanType scan." Start-MpScan -ScanType "${ScanType}Scan" Write-LdoLog -Level SUCCESS -Message "Windows Defender $ScanType scan started." } function Update-LdoDefenderAvSignature { <# .SYNOPSIS Updates Windows Defender Antivirus definitions. .DESCRIPTION Wraps Update-MpSignature. Windows only. .EXAMPLE Update-LdoDefenderAvSignature .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param() Assert-LdoWindowsDefender Write-LdoLog -Level INFO -Message 'Updating Windows Defender signatures.' Update-MpSignature Write-LdoLog -Level SUCCESS -Message 'Windows Defender signatures updated.' } function Add-LdoDefenderAvExclusion { <# .SYNOPSIS Adds one or more path exclusions to Windows Defender Antivirus. .DESCRIPTION Wraps Add-MpPreference -ExclusionPath. Windows only. .PARAMETER Path One or more paths to exclude. .EXAMPLE Add-LdoDefenderAvExclusion -Path 'C:\app', 'C:\cache' .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string[]]$Path ) Assert-LdoWindowsDefender Write-LdoLog -Level INFO -Message "Adding Defender exclusion path(s): $($Path -join ', ')." Add-MpPreference -ExclusionPath $Path Write-LdoLog -Level SUCCESS -Message 'Defender exclusion(s) added.' } # --------------------------------------------------------------------------------------------- # Defender for Endpoint on Linux (mdatp CLI) # --------------------------------------------------------------------------------------------- function Invoke-LdoMdatpCommand { # Internal. Asserts mdatp is available on Linux, runs it, throws on non-zero exit, and # returns the output. [CmdletBinding()] [OutputType([object])] param( [Parameter(Mandatory)][string[]]$ArgumentList, [string]$Operation ) if ((Get-LdoOperatingSystem) -ne 'Linux') { throw 'mdatp (Defender for Endpoint on Linux) is only available on Linux.' } Assert-LdoCommand -Name 'mdatp' if (-not $Operation) { $Operation = "mdatp $($ArgumentList -join ' ')" } Write-LdoLog -Level INFO -Message "Running: mdatp $($ArgumentList -join ' ')" $output = & mdatp @ArgumentList Assert-LdoLastExitCode -Operation $Operation return $output } function Get-LdoMdatpHealth { <# .SYNOPSIS Returns Microsoft Defender for Endpoint on Linux health. .DESCRIPTION Runs 'mdatp health', optionally for a single field. Linux only. .PARAMETER Field Optional single health field to return (for example healthy, real_time_protection_enabled). .EXAMPLE Get-LdoMdatpHealth -Field healthy .OUTPUTS System.Object #> [CmdletBinding()] [OutputType([object])] param( [string]$Field ) $mdatpArgs = @('health') if ($Field) { $mdatpArgs += @('--field', $Field) } return Invoke-LdoMdatpCommand -ArgumentList $mdatpArgs -Operation 'mdatp health' } function Start-LdoMdatpScan { <# .SYNOPSIS Starts a Microsoft Defender for Endpoint on Linux scan. .DESCRIPTION Runs 'mdatp scan quick' or 'mdatp scan full'. Linux only. .PARAMETER ScanType Quick or Full. Defaults to Quick. .EXAMPLE Start-LdoMdatpScan -ScanType Full .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param( [ValidateSet('Quick', 'Full')][string]$ScanType = 'Quick' ) $null = Invoke-LdoMdatpCommand -ArgumentList @('scan', $ScanType.ToLowerInvariant()) -Operation "mdatp scan $ScanType" Write-LdoLog -Level SUCCESS -Message "mdatp $ScanType scan completed." } function Update-LdoMdatpDefinition { <# .SYNOPSIS Updates Microsoft Defender for Endpoint on Linux definitions. .DESCRIPTION Runs 'mdatp definitions update'. Linux only. .EXAMPLE Update-LdoMdatpDefinition .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param() $null = Invoke-LdoMdatpCommand -ArgumentList @('definitions', 'update') -Operation 'mdatp definitions update' Write-LdoLog -Level SUCCESS -Message 'mdatp definitions updated.' } function Add-LdoMdatpExclusion { <# .SYNOPSIS Adds a folder exclusion to Microsoft Defender for Endpoint on Linux. .DESCRIPTION Runs 'mdatp exclusion folder add --path'. Linux only. .PARAMETER Path The folder path to exclude. .EXAMPLE Add-LdoMdatpExclusion -Path /opt/app .OUTPUTS None #> [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Path ) $null = Invoke-LdoMdatpCommand -ArgumentList @('exclusion', 'folder', 'add', '--path', $Path) -Operation 'mdatp exclusion folder add' Write-LdoLog -Level SUCCESS -Message "mdatp folder exclusion added: $Path" } Export-ModuleMember -Function ` Get-LdoDefenderSecureScore, ` Get-LdoDefenderRecommendation, ` Get-LdoDefenderPlan, ` Set-LdoDefenderPlan, ` Get-LdoDefenderAlert, ` Invoke-LdoDefenderHuntingQuery, ` Invoke-LdoDefenderDeviceIsolation, ` Invoke-LdoDefenderAvScan, ` Get-LdoDefenderAvStatus, ` Start-LdoDefenderAvScan, ` Update-LdoDefenderAvSignature, ` Add-LdoDefenderAvExclusion, ` Get-LdoMdatpHealth, ` Start-LdoMdatpScan, ` Update-LdoMdatpDefinition, ` Add-LdoMdatpExclusion |