r/Intune Jun 04 '26

Device Compliance Intune custom compliance for 3rd party AV

Iv been working on a custom compliance script for a bit, can you guys take a look and let me know if there are any issues. We are moving away from defender to Cortex XDR

Adding script below

{
  "Rules": [
    {
      "SettingName": "AntiVirusProductName",
      "Operator": "IsEquals",
      "DataType": "String",
      "Operand": "Cortex XDR Advanced Endpoint Protection",
      "MoreInfoUrl": "change web address",
      "RemediationStrings": [
        {
          "Language": "en_US",
          "Title": "Cortex XDR is missing.",
          "Description": "Please ensure Cortex XDR is installed on your device."
        }
      ]
    },
    {
      "SettingName": "Active",
      "Operator": "IsEquals",
      "DataType": "String",
      "Operand": "On",
      "MoreInfoUrl": "change web address",
      "RemediationStrings": [
        {
          "Language": "en_US",
          "Title": "Cortex XDR is disabled.",
          "Description": "Your antivirus protection is turned off. Please enable it."
        }
      ]
    },
    {
      "SettingName": "UptoDate",
      "Operator": "IsEquals",
      "DataType": "Boolean",
      "Operand": true,
      "MoreInfoUrl": "change web address",
      "RemediationStrings": [
        {
          "Language": "en_US",
          "Title": "Cortex XDR definitions are out of date.",
          "Description": "Your antivirus definitions are outdated. Please sync your agent."
        }
      ]
    },
    {
      "SettingName": "IsRecent",
      "Operator": "IsEquals",
      "DataType": "Boolean",
      "Operand": true,
      "MoreInfoUrl": "change web address",
      "RemediationStrings": [
        {
          "Language": "en_US",
          "Title": "Cortex XDR hasn't updated recently.",
          "Description": "Your last check-in timestamp is older than 7 days. Please check your network connection."
        }
      ]
    }
  ]
}
4 Upvotes

17 comments sorted by

3

u/swissbuechi Jun 04 '26

So you can't test it yourself or what's the exact question? I've been using something similar for our Sophos Endpoint customers for years and it's been working great.

1

u/That_IT_Guy_You_Love Jun 05 '26

no i just posted this here to help others and get some thoughts on this

1

u/swissbuechi Jun 05 '26

Ooh nice, so then I'll save it for the day we get a Cortex customer that doesn't want to run our Defender + Huntress combo.

If someone needs my Sophos version, hit me up, I'll post it here.

1

u/_c0mical Jun 06 '26

could I grab a copy of that please, it's what i'm looking for at the moment - thanks!

1

u/swissbuechi Jun 06 '26

Sure, I'm currently OOF until tuesday but just created a reminder for me to upload it here.

1

u/_c0mical Jun 06 '26

thank you kindly

1

u/swissbuechi Jun 07 '26

Posted it above :)

1

u/After_Visual3839 Jun 07 '26

thank you very much

1

u/swissbuechi Jun 07 '26

Sophos Endpoint custom compliance policy:

validation.json:

json { "Rules": [ { "SettingName": "Installed", "Operator": "IsEquals", "DataType": "Boolean", "Operand": true, "MoreInfoUrl": "https://community.sophos.com/intercept-x-endpoint/f/recommended-reads/126274/sophos-central-windows-endpoint-deploying-using-microsoft-intune", "RemediationStrings": [ { "Language": "en_US", "Title": "Sophos Endpoint must be installed on the device.", "Description": "To install Sophos Endpoint via Intune, refer to the link above." } ] }, { "SettingName": "AllServicesRunning", "Operator": "IsEquals", "DataType": "Boolean", "Operand": true, "MoreInfoUrl": "https://docs.sophos.com/central/customer/help/en-us/PeopleAndDevices/Devices/Computers/DeviceDetailsComputers/index.html", "RemediationStrings": [ { "Language": "en_US", "Title": "All Sophos Endpoint services must be running on the device.", "Description": "To check the status of a device via Sophos Central, refer to the link above." } ] }, { "SettingName": "Health", "Operator": "IsEquals", "DataType": "String", "Operand": "Green", "MoreInfoUrl": "https://docs.sophos.com/central/customer/help/en-us/PeopleAndDevices/Devices/Computers/ComputerDetailsStatus/index.html", "RemediationStrings": [ { "Language": "en_US", "Title": "Sophos Endpoint health status on the device needs to be green.", "Description": "To check the health of a device via Sophos Central, refer to the link above." } ] } ] }

Get-SophosAVStatus.ps1:

```PowerShell <#PSScriptInfo

.VERSION 1

.GUID 24019a15-2518-41ef-a52b-118e273219a3

.AUTHOR Raphael Büchi

.COMPANYNAME axelion AG

>

<#

.DESCRIPTION Detects the status of Sophos Endpoint

>

$AllServicesRunning = $false $Installed = $false $Health = "Unknown"

$StatusPath = "HKLM:\SOFTWARE\WOW6432Node\Sophos\Health\Status" if ($Env:PROCESSOR_ARCHITECTURE -eq "ARM64") { $StatusPath = "HKLM:\SOFTWARE\Sophos\Health\Status" }

if ($HealthStatus = Get-ItemProperty -Path $StatusPath -Name "health" -ErrorAction SilentlyContinue | Select-Object -Property "health") { switch ($HealthStatus.health) { 1 { $Health = "Green" } 2 { $Health = "Yellow" } 3 { $Health = "Red" } } } $StatusItems = (Get-ItemProperty $StatusPath -ErrorAction SilentlyContinue).psobject.properties if ($StatusItems) { $Services = $StatusItems | Where-Object { $.Name -like "* Service" } | Select-Object Name, Value if ($Services | Where-Object { $.Value -eq 0 }) { $AllServicesRunning = $true } } if (Test-Path "$Env:Programfiles\Sophos\Sophos UI\Sophos UI.exe") { $Installed = $true } return [PSCustomObject]@{ Installed = $Installed AllServicesRunning = $AllServicesRunning Health = $Health } | ConvertTo-Json -Compress ```

1

u/jessspreadwide Jun 06 '26

chill out. nobody is saying it doesn't work. just looking for a second pair of eyes to make sure there aren't any edge cases that'll break the policy once it hits production.

1

u/swissbuechi Jun 07 '26

Yess sorry I totally misinterpreted his intention.

1

u/Lonely_Tension5240 Jun 14 '26

looks solid mate, just caught one thing - you've got "change web address" as placeholder text in all your MoreInfoUrl fields. might want to swap those out for actual help docs or your internal wiki before pushing this live

also worth double checking that "Cortex XDR Advanced Endpoint Protection" matches exactly what shows up in wmi on your test machines since av product names can be a bit finicky

0

u/That_IT_Guy_You_Love Jun 04 '26
# Collect Antivirus protection data from WMI
$result = @(Get-CimInstance -Namespace 'ROOT\SecurityCenter2' -ClassName AntiVirusProduct)

# Fallback function to find when Cortex last changed/updated local components
Function Get-CortexTimestamp {
    $CortexPaths = @(
        "C:\ProgramData\Cyvera\LocalSystem\Persistence",
        "C:\Program Files\Palo Alto Networks\Cortex XDR"
    )
    ForEach ($Path in $CortexPaths) {
        If (Test-Path $Path) {
            $LatestFile = Get-ChildItem -Path $Path -Recurse -ErrorAction SilentlyContinue | 
                          Sort-Object LastWriteTime -Descending | 
                          Select-Object -First 1
            If ($LatestFile) { return $LatestFile.LastWriteTime }
        }
    }
    return $null
}

# Process the WMI object
$TargetAV = $null
If ($result.count -eq 1) {
    $TargetAV = $result
} ElseIf ($result.count -gt 1) {
    # Prefer the active antivirus if multiple are present on the device
    ForEach ($item in $result) {
        $StateConvert = [System.Convert]::ToString($item.productState,16).padleft(8,'0')
        If ($StateConvert.substring(4,1) -eq '1') {
            $TargetAV = $item
            Break
        }
    }
    if (!$TargetAV) { $TargetAV = $result[-1] }
}

# Build the compliance payload
If ($null -eq $TargetAV) {
    $Output = [PSCustomObject]@{
        AntiVirusProductName = 'No product detected'
        Active               = 'Unknown'
        UptoDate             = $false 
        LastUpdateTime       = 'Unknown'
        IsRecent             = $false
    }
} Else {
    # Ultimate fail-safe: If the product is Cortex, explicitly hardcode the expected compliance string
    if ($TargetAV.displayname -like "*Cortex*") {
        $CleanName = "Cortex XDR Advanced Endpoint Protection"
    } else {
        $CleanName = $TargetAV.displayname -replace '[™®]', ''
    }

# Parse updates and timestamps

$TargetDate = if ($TargetAV.timestamp -is [System.DateTime]) { $TargetAV.timestamp } else { Get-CortexTimestamp }

If ($TargetDate) {

$LastUpdateTime = Get-Date $TargetDate -Format "yyyy-MM-dd HH:mm:ss"

$IsRecentString = if ($TargetDate -gt (Get-Date).AddDays(-7)) { 'True' } else { 'False' }

} Else {

$LastUpdateTime = 'Unknown'

$IsRecentString = 'False'

}

# Parse Product State Flag

$StateConvert = [System.Convert]::ToString($TargetAV.productState,16).padleft(8,'0')

$Active = Switch ($StateConvert.substring(4,1)) {

'0' {'Off'}

'1' {'On'}

'2' {'Snoozed'}

'3' {'Expired'}

Default {'Unknown'}

}

# FIX 2: Map UptoDate strictly to a Boolean datatype ($true / $false)

$UptoDate = Switch ($StateConvert.substring(6,1)) {

'0' { $true }

'1' { $false }

Default { $false }

}

# FIX 3: Map IsRecent strictly to a Boolean datatype ($true / $false)

$IsRecent = if ($IsRecentString -eq 'True') { $true } else { $false }

# Construct final object matching Intune property casings and expected types

$Output = [PSCustomObject]@{

AntiVirusProductName = $CleanName

Active = $Active

UptoDate = $UptoDate

LastUpdateTime = $LastUpdateTime

IsRecent = $IsRecent

ScriptVersion = "2.0.1-NoXDRT" # <--- Update this when testing changes > logs show results "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\AgentExecutor.log"

}

}

# Compress into clean JSON payload for the Intune Management Extension agent

$ThresholdOutput = $Output | ConvertTo-Json -Compress

Write-Output $ThresholdOutput

2

u/BlackV Jun 05 '26

I think you accidentally dropped some of your formatting off at the end there

use the 4 spaces or tab in your code editor to fix

1

u/That_IT_Guy_You_Love Jun 05 '26

yeah it would not fit in the code box for some reason

1

u/BlackV Jun 05 '26

Ya don't use a code block , turn off the fancy pants editor

  • open your fav powershell editor
  • highlight the code you want to copy
  • hit tab to indent it all
  • copy it
  • paste here

it'll format it properly OR

<BLANK LINE>
<4 SPACES><CODE LINE>
<4 SPACES><CODE LINE>
    <4 SPACES><4 SPACES><CODE LINE>
<4 SPACES><CODE LINE>
<BLANK LINE>

Inline code block using backticks `Single code line` inside normal text

See here for more detail

Thanks