just from trial and error, I’m pretty sure the “Bitdefender Installed” column is just showing if it is enabled on the policy, and likely if Syncro is billing you for it. There is unfortunately no good way built into Syncro to see if BD actually successfully installed.
We are monitoring for the presence of 4 or 5 processes that BD runs on every machine it installs on to find the assets that do not have an active install. Unfortunately, most of these processes get killed when BD pushes an update, and either fail to restart until the next reboot, or Syncro never checks for new PIDs after the first boot. We haven’t gotten a chance to really dig into it, but it seems like it’s something along those lines where from time to time an asset will show missing those processes, and if we check the asset and Syncro shows BD as installed healthy we can generally clear the alert and it doesn’t come back.
Syncro could do a better job of making it easy to find devices with BD installs, but also BD just does a “” and quits if it sees residue from a competitor’s product during install.
You can do a saved asset search for installed application = bitdefender. Much more reliable that the built in detection unfortunately. But installed apps list only updates with 6 hour sync, so there is lag. I found and update a bitdefender monitor script you guys might find helpful:
<# Version 1.0.6
.DESCRIPTION
Test the health of the Bitdefender Endpoint Security Tools.
This function will check Bitdefender
* is installed
* is running
* is service healthy
* is up to date
* aggregate health of above checks
* signature version
* signature number
* signature published date
* signature update time
.EXAMPLE
IsBitdefenderInstalled : True
IsBitdefenderProcessRunning : True
IsBitdefenderServiceHealthy : True
IsBitdefenderUptodate : True
IsBitdefenderHealthy : True
SignatureVersion : 7.72508
SignatureNumber : 9697199
SignatureDate : 7/26/2017 8:03:50 AM
SignatureUpdateTime : 7/26/2017 10:54:59 AM
.NOTES
Created by: Jason Wasser @wasserja https://www.powershellgallery.com/packages/BitdefenderHealth/
Last updated by creator: 7/26/2017 v1.0.5
Modified by Isaac Good: 5/3/2021 v1.0.6
- removed remote computer connection code/references and bundle all functions into one script for use via RMM
- removed non-working component monitoring (Product.ActionCenter.conf no longer exists)
- fix errors caused by multiple files returned using wildcards
- expanded service status to check all services not just one
- added delay if recent kernel power event
- added alerting for Syncro
#>
Import-Module $env:SyncroModule -DisableNameChecking
# Check for a recent kernel power event and if found, give BD time to update
$lastkp = Get-EventLog -LogName system -Source Microsoft-Windows-Kernel-Power -Newest 1 | Select -ExpandProperty TimeGenerated
$recent = (New-Timespan -Start $lastkp -End (Get-Date)).TotalMinutes -le 15
if ($recent) {
Start-Sleep -Seconds 300
}
function Convert-UnixTimeToDateTime {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[int]$UnixTime
)
$UnixConvertedDateTime = (New-Object DateTime(1970, 1, 1, 0, 0, 0, 0, [DateTimeKind]::Utc)).AddSeconds($UnixTime)
Write-Verbose "Unix converted date time: $UnixConvertedDateTime"
$UnixConvertedDateTime
}
function Convert-TimeZone {
[CmdletBinding()]
param (
[datetime]$inputDateTime = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'),
[string]$fromTimeZone = "E. Europe Standard Time",
[string]$toTimeZone = [System.TimeZoneInfo]::Local.Id
)
begin {
function Test-TimeZone {
param (
$TimeZone
)
$ValidTimeZones = [System.TimeZoneInfo]::GetSystemTimeZones()
Write-Verbose -Message "Validating time zone $TimeZone"
# $IsValidTimeZone = $ValidTimeZones.id -contains $TimeZone
$IsValidTimeZone = ($ValidTimeZones | Select-Object -ExpandProperty Id) -contains $TimeZone
Write-Verbose -Message "Timezone $TimeZone is $IsValidTimeZone"
$IsValidTimeZone
}
}
process {
# Validate time zones
if ((Test-TimeZone -TimeZone $fromTimeZone) -and (Test-TimeZone -TimeZone $toTimeZone)) {
Write-Verbose -Message "Timezones have been validated."
Write-Verbose -Message "Converting $inputDateTime from $fromTimeZone to $toTimeZone"
$outputDateTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId(
$inputDateTime, $fromTimeZone, $toTimeZone)
Write-Output $outputDateTime
}
else {
Write-Error "Invalid time zone entered."
}
}
}
function Test-BitdefenderProcess {
param (
[string]$BitdefenderProcessName
)
try {
$BitdefenderProcess = Get-Process -Name $BitdefenderProcessName -ErrorAction Stop
if ($BitdefenderProcess) {
$IsBitdefenderProcessRunning = $true
$IsBitdefenderProcessRunning
}
}
catch [Microsoft.PowerShell.Commands.ProcessCommandException] {
Write-Warning "Unable to find process $BitdefenderProcessName"
$IsBitdefenderProcessRunning = $false
$IsBitdefenderProcessRunning
}
catch {
Write-Warning $_.Exception.Message
$IsBitdefenderProcessRunning = $false
$IsBitdefenderProcessRunning
}
}
function Test-BitdefenderInstallationPath {
param (
[string]$BitdefenderInstallationPath
)
$IsBitdefenderInstalled = Test-Path -Path $BitdefenderInstallationPath
if (!($IsBitdefenderInstalled)) {
Write-Warning "Unable to find $BitdefenderInstallationPath"
}
$IsBitdefenderInstalled
}
function Test-BitdefenderService {
[CmdletBinding()]
param (
[string]$ServiceName
)
try {
$Service = Get-Service -Name $ServiceName -ErrorAction Stop
if ($Service) {
$ServiceDetails = Get-WmiObject -Class win32_service -Filter "Name = '$ServiceName'"
$ServiceHealthProperties = @{
Name = $Service.Name
ServiceExists = $true
IsRunning = [bool]($Service.Status -eq 'Running')
IsAutomatic = [bool]($ServiceDetails.StartMode -eq 'Auto')
}
$ServiceHealth = New-Object -TypeName PSCustomObject -Property $ServiceHealthProperties
$ServiceHealth
}
}
catch {
$ServiceHealthProperties = @{
Name = $ServiceName
ServiceExists = $false
IsRunning = $false
IsAutomatic = $false
}
$ServiceHealth = New-Object -TypeName PSCustomObject -Property $ServiceHealthProperties
$ServiceHealth
}
}
function Get-BitdefenderSignatureDate {
[CmdletBinding()]
param (
$TimeZone = [System.TimeZoneInfo]::Local.Id
)
process {
$BitdefenderThreatScannerPath = 'C:\Program Files\Bitdefender\Endpoint Security\ThreatScanner'
try {
$BitdefenderUpdatePath = Get-Item -Path "$BitdefenderThreatScannerPath\Antivirus*\versions.id*" | Sort-Object LastWriteTime | Select-Object -last 1
[xml]$BitdefenderVersionsFile = Get-Content -Path $BitdefenderUpdatePath
# Bitdefender headquarters is in Eastern European Time Zone which also has daylight saving time.
$BitdefenderSignatureDate = Convert-TimeZone -inputDateTime ([datetime]$BitdefenderVersionsFile.info.time.'#text') -fromTimeZone 'E. Europe Standard Time' -toTimeZone $TimeZone
$BitdefenderSignatureDate
}
catch {
Write-Warning -Message "Unable to determine the signature date."
$BitdefenderSignatureDate = $null
$BitdefenderSignatureDate
}
}
}
function Get-BitdefenderUpdateFileData {
[CmdletBinding()]
param (
[ValidateScript({Test-Path -Path $_})]
[string]$BitdefenderThreatScannerPath = 'C:\Program Files\Bitdefender\Endpoint Security\ThreatScanner'
)
process {
$BitdefenderUpdatePath = Get-Item -Path "$BitdefenderThreatScannerPath\Antivirus*\Plugins\Update.txt" | Sort-Object LastWriteTime | Select-Object -last 1
$UpdateFile = Get-Content -Path $BitdefenderUpdatePath
$BitdefenderUpdateFileDataProperties = ''
$BitdefenderUpdateFileDataProperties = @{}
foreach ($Line in $UpdateFile) {
$BitdefenderUpdateFileDataProperties.Add(($Line.Split(':')[0]).Trim(), ($Line.Substring($Line.IndexOf(':') + 1)).Trim())
}
# Changing Update time to [datetime]
#$BitdefenderUpdateFileDataProperties['Update time'] = ([datetime]::ParseExact($BitdefenderUpdateFileDataProperties['Update time'],'ddd MMM dd HH:mm:ss yyyy',$null)).ToLocalTime()
$BitdefenderUpdateFileDataProperties['Update time'] = (Convert-UnixTimeToDateTime -UnixTime $BitdefenderUpdateFileDataProperties['Update time GMT'])
$BitdefenderUpdateFileData = New-Object -TypeName PSCustomObject -Property $BitdefenderUpdateFileDataProperties
$BitdefenderUpdateFileData
}
}
function Test-Bitdefender {
[CmdletBinding()]
param (
[parameter(
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)]
[string]$BitdefenderProcessName = 'epsecurityservice',
[string[]]$BitdefenderServiceNames = @((Get-Service | Where-Object {$_.DisplayName -like "*bitdef*"}).Name),
[string]$BitdefenderInstallationPath = 'C:\Program Files\Bitdefender\Endpoint Security\epsecurityservice.exe'
)
process {
try {
# Is Bitdefender Installed
Write-Verbose -Message "Check if Bitdefender is installed at $BitdefenderInstallationPath."
$IsBitdefenderInstalled = Test-BitdefenderInstallationPath $BitdefenderInstallationPath
if ($IsBitdefenderInstalled) {
# Is Bitdefender Running
Write-Verbose -Message "Check if $BitdefenderProcessName is running"
$IsBitdefenderProcessRunning = Test-BitdefenderProcess $BitdefenderProcessName
# Are Bitdefender Services Running and Automaticly Started
Write-Verbose -Message "Check Bitdefender services status"
foreach ($BitdefenderServiceName in $BitdefenderServiceNames) {
$BitdefenderService = Test-BitdefenderService $BitdefenderServiceName
$IsBitdefenderServiceHealthy = $BitdefenderService.ServiceExists -and $BitdefenderService.IsRunning -and $BitdefenderService.IsAutomatic
if ($IsBitdefenderServiceHealthy -eq $false) {
$IsBitdefenderServiceHealthyOverAll = $false
}
else { $IsBitdefenderServiceHealthyOverAll = $true }
}
# Is Bitdefender up to date
Write-Verbose -Message "Check if Bitdefender is up to date"
$BitdefenderSignatureDate = Get-BitdefenderSignatureDate
Write-Verbose "Bitdefender signature date: $BitdefenderSignatureDate"
$BitdefenderUpdateFileData = Get-BitdefenderUpdateFileData
if ($BitdefenderSignatureDate) {
$IsBitdefenderUptodate = (New-Timespan -Start $BitdefenderSignatureDate -End (Get-Date)).TotalDays -le 1
}
else {
$IsBitdefenderUptodate = (New-Timespan -Start $BitdefenderUpdateFileData.'Update time' -End (Get-Date)).TotalDays -le 1
}
}
else {
$IsBitdefenderProcessRunning = $false
$IsBitdefenderServiceHealthyOverall = $false
$IsBitdefenderUptodate = $false
}
# Creating Object
$BitdefenderStatusProperties = [ordered]@{
IsBitdefenderInstalled = $IsBitdefenderInstalled
IsBitdefenderProcessRunning = $IsBitdefenderProcessRunning
IsBitdefenderServiceHealthy = $IsBitdefenderServiceHealthyOverall
IsBitdefenderUptodate = $IsBitdefenderUptodate
IsBitdefenderHealthy = $IsBitdefenderInstalled -and $IsBitdefenderProcessRunning -and $IsBitdefenderServiceHealthy -and $IsBitdefenderUptodate
SignatureVersion = $BitdefenderUpdateFileData.Version
SignatureNumber = $BitdefenderUpdateFileData.'Signature Number'
SignatureDate = $BitdefenderSignatureDate
SignatureUpdateTime = $BitdefenderUpdateFileData.'Update time'
}
$BitdefenderStatus = New-Object -TypeName PSCustomObject -Property $BitdefenderStatusProperties | Out-String
$BitdefenderStatus
# Clear Variables
$BitdefenderUpdateFileData = $null
$BitdefenderSignatureDate = $null
# Alert on False conditions
$Status = $BitdefenderStatus -Join '|'
if ($Status -match 'false') {
RMM-Alert -Category 'Monitor - Bitdefender' -Body "$BitdefenderStatus"
exit 1
}
}
catch {
Write-Error -Message $_.Exception
}
}
}
Test-Bitdefender
Here’s another script for cleaning up AV leftovers that may cause BD install to fail. Only run this if you have already used the built in AV uninstall program or run their removal tools, this is just for cleaning up leftovers. It also cleans out BD leftovers so not to be run on machines with BD installed. And it reboots the machine after running.
Jordan is correct, that column lists only if BitDefender is installed on the policy. If anything prevents the installation when it runs then this is one way you can end up with it saying true even if it’s not installed. This could definitely be clarified.
Some good suggestions here, to add one more I usually say to do a Saved Asset Search since this will look at the installed apps list specifically.