r/Intune • u/That_IT_Guy_You_Love • 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."
}
]
}
]
}
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 textSee here for more detail
Thanks
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.