Il est essentiel de conserver un enregistrement propre et précis des logiciels installés pour assurer la visibilité des terminaux, les audits de conformité et l’intégrité du système. Pourtant, il n’est pas rare que les entreprises rencontrent des entrées antivirus « orphelines » dans le Centre de sécurité Windows, entrées qui persistent même après la suppression de l’antivirus. Ces enregistrements trompeurs peuvent être source de confusion, de fausses déclarations et d’efforts de remédiation inutiles. C’est là que le script PowerShell intervient en tant qu’outil puissant pour automatiser et optimiser le processus de nettoyage.
Dans cet article, nous verrons comment les professionnels de l’informatique peuvent utiliser des scripts PowerShell pour supprimer les entrées orphelines du Centre de sécurité Windows. Ce script garantit que seules les solutions antivirus réellement désinstallées sont éliminées, ce qui réduit les efforts manuels et le risque d’erreur humaine.
Contexte
Les solutions antivirus s’enregistrent souvent dans le Centre de sécurité Windows pour des raisons de visibilité et d’intégration avec les outils de surveillance de l’état du système. Cependant, les processus de désinstallation ne suppriment pas toujours proprement ces enregistrements. Par conséquent, les fournisseurs de services gérés (MSP) et les administrateurs informatiques peuvent rencontrer des entrées périmées indiquant qu’un logiciel antivirus obsolète protège toujours un système.
Ces entrées orphelines peuvent interférer avec les rapports de conformité ou déclencher des faux positifs dans les plateformes de surveillance et de gestion à distance (RMM) telles que NinjaOne. En automatisant la validation et le nettoyage de ces entrées, les équipes informatiques peuvent préserver la précision et l’efficacité de leurs environnements.
Le script
#Requires -Version 5.1
<#
.SYNOPSIS
    Remove a specified Security Center entry if the antivirus is not installed. You can get the antivirus name/Security Center entry in Ninja by navigating to Details > Antivirus.
.DESCRIPTION
    Remove a specified Security Center entry if the antivirus is not installed. You can get the antivirus name/Security Center entry in Ninja by navigating to Details > Antivirus.
By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
    Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
    Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
    Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
    Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
    Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
    Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
    EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
.EXAMPLE
    -AntivirusName "VIPRE Business Agent"
    Checking Add and Remove Programs for 'VIPRE Business Agent'.
    Verifying that 'VIPRE Business Agent' does not exist at path 'C:\Program Files\VIPRE Business Agent\SBAMWSC.EXE'.
    Verifying that 'VIPRE Business Agent' does not exist at path 'C:\Program Files\VIPRE Business Agent\ViprePPLSvc.exe'.
    Removing 'VIPRE Business Agent' from the Security Center.
    Successfully removed 'VIPRE Business Agent' from the Security Center.
PARAMETER: -AntivirusName "ReplaceMeWithTheNameOfTheAntivirusToRemove"
    Specify the name of the Security Center entry you would like to remove.
.NOTES
    Minimum OS Architecture Supported: Windows 10, Windows Server 2016
    Release Notes: Initial Release
#>
[CmdletBinding()]
param (
    [Parameter()]
    [String]$AntivirusName
)
begin {
    # If script form variables are used, replace the command line parameters with their value.
    if ($env:antivirusName -and $env:antivirusName -notlike "null") { $AntivirusName = $env:antivirusName }
    function Test-IsServer {
        # Determine the method to retrieve the operating system information based on PowerShell version
        try {
            $OS = if ($PSVersionTable.PSVersion.Major -lt 5) {
                Get-WmiObject -Class Win32_OperatingSystem -ErrorAction Stop
            }
            else {
                Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
            }
        }
        catch {
            Write-Host -Object "[Error] Unable to validate whether or not this device is a server."
            Write-Host -Object "[Error] $($_.Exception.Message)"
            exit 1
        }
    
        # Check if the ProductType is "2" or "3", which indicates that the system is a server
        if ($OS.ProductType -eq "2" -or $OS.ProductType -eq "3") {
            return $true
        }
    }
    # If the script is run on a server, display an error message and exit
    if (Test-IsServer) {
        Write-Host -Object "[Error] The Windows Security Center is not present on Windows Server."
        exit 1
    }
    # If $AntivirusName exists, trim any leading or trailing whitespace.
    if ($AntivirusName) {
        $AntivirusName = $AntivirusName.Trim()
    }
    # If $AntivirusName is still not set or empty after the previous checks, display an error and exit the script.
    if (!$AntivirusName) {
        Write-Host -Object "[Error] Please provide a valid antivirus name."
        exit 1
    }
    # Try to retrieve the Security Center entries for antivirus products
    try {
        $SecurityCenterEntries = Get-WmiObject -Namespace "root\SecurityCenter2" -ClassName "AntiVirusProduct" -ErrorAction Stop
    }
    catch {
        # If there is an error retrieving the Security Center entries, output an error message and exit the script.
        Write-Host -Object "[Error] Failed to retrieve any antivirus entries from the Security Center."
        Write-Host -Object "[Error] $($_.Exception.Message)"
        exit 1
    }
    # Check if no Security Center entries were found or if the number of entries is less than 1, then output an error and exit.
    if (!$SecurityCenterEntries -or ($SecurityCenterEntries.displayName | Measure-Object | Select-Object -ExpandProperty Count) -lt 1) {
        Write-Host -Object "[Error] No antivirus entries found in the Security Center."
        exit 1
    }
    # Check if the antivirus name provided does not exist in the Security Center entries.
    if ($SecurityCenterEntries.displayName -notcontains $AntivirusName) {
        Write-Host -Object "[Error] An invalid antivirus name was specified. Please specify one of the following valid antivirus names: " -NoNewline
        Write-Host -Object $SecurityCenterEntries.displayName -Separator ", "
        exit 1
    }
    function Find-InstallKey {
        [CmdletBinding()]
        param (
            [Parameter(ValueFromPipeline = $True)]
            [String]$DisplayName,
            [Parameter()]
            [Switch]$UninstallString,
            [Parameter()]
            [String]$UserBaseKey
        )
        process {
            # Initialize a list to store found installation keys
            $InstallList = New-Object System.Collections.Generic.List[Object]
    
            # If no custom user base key is provided, search in the standard HKLM paths
            if (!$UserBaseKey) {
                # Search in the 32-bit uninstall registry key and add results to the list
                $Result = Get-ChildItem -Path "Registry::HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | Get-ItemProperty | Where-Object { $_.DisplayName -like "*$DisplayName*" }
                if ($Result) { $InstallList.Add($Result) }
    
                # Search in the 64-bit uninstall registry key and add results to the list
                $Result = Get-ChildItem -Path "Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" | Get-ItemProperty | Where-Object { $_.DisplayName -like "*$DisplayName*" }
                if ($Result) { $InstallList.Add($Result) }
            }
            else {
                # If a custom user base key is provided, search in the corresponding Wow6432Node path and add results to the list
                $Result = Get-ChildItem -Path "$UserBaseKey\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | Get-ItemProperty | Where-Object { $_.DisplayName -like "*$DisplayName*" }
                if ($Result) { $InstallList.Add($Result) }
    
                # Search in the custom user base key for the standard uninstall path and add results to the list
                $Result = Get-ChildItem -Path "$UserBaseKey\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" | Get-ItemProperty | Where-Object { $_.DisplayName -like "*$DisplayName*" }
                if ($Result) { $InstallList.Add($Result) }
            }
    
            # If the UninstallString switch is set, return only the UninstallString property of the found keys
            if ($UninstallString) {
                $InstallList | Select-Object -ExpandProperty UninstallString -ErrorAction SilentlyContinue
            }
            else {
                $InstallList
            }
        }
    }    
    
    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }
    if (!$ExitCode) {
        $ExitCode = 0
    }
}
process {
    # Check if the script is being run with elevated (administrator) privileges.
    if (!(Test-IsElevated)) {
        Write-Host -Object "[Error] Access Denied. Please run with Administrator privileges."
        exit 1
    }
    # Inform the user that the script is checking Add and Remove Programs for the specified antivirus.
    Write-Host -Object "Checking Add and Remove Programs for '$AntivirusName'."
    # Call the Find-InstallKey function to check if the antivirus is installed by its display name.
    $IsInstalled = Find-InstallKey -DisplayName $AntivirusName
    # If the antivirus is found to be installed, display an error and exit the script.
    if ($IsInstalled) {
        Write-Host -Object "[Error] '$AntivirusName' is currently installed. Unable to remove the entry from the Security Center."
        exit 1
    }
    # Retrieve the Security Center entries that match the specified antivirus name.
    $EntryToRemove = $SecurityCenterEntries | Where-Object { $_.displayName -like $AntivirusName }
    # Loop through the matched Security Center entries to process each one.
    $EntryToRemove | ForEach-Object {
        # Retrieve the paths for the signed product executable and reporting executable.
        $SignedExe = $_.pathToSignedProductExe
        $SignedReportingExe = $_.pathToSignedReportingExe
        if ($SignedExe) {
            Write-Host -Object "Verifying that '$AntivirusName' does not exist at path '$SignedExe'."
        }
        # If the signed product executable path contains environment variables (denoted by '%' signs), attempt to expand them to their full path using the Environment class.
        if ($SignedExe -and $SignedExe -match '%.*%') {
            try {
                $ErrorActionPreference = "Stop"
                $SignedExe = [Environment]::ExpandEnvironmentVariables($SignedExe)
                $ErrorActionPreference = "Continue"
            }
            catch {
                Write-Host -Object "[Error] Failed to expand environment variable in '$($_.pathToSignedProductExe)'."
                Write-Host -Object "[Error] $($_.Exception.Message)"
                exit 1
            }
        }
        # If the path for the signed reporting executable exists, print a message indicating the verification of its presence.
        if ($SignedReportingExe) {
            Write-Host -Object "Verifying that '$AntivirusName' does not exist at path '$SignedReportingExe'."
        }
        
        # If the signed reporting executable path contains environment variables, attempt to expand them.
        if ($SignedReportingExe -and $SignedReportingExe -match '%.*%') {
            try {
                $ErrorActionPreference = "Stop"
                $SignedReportingExe = [Environment]::ExpandEnvironmentVariables($SignedReportingExe)
                $ErrorActionPreference = "Continue"
            }
            catch {
                Write-Host -Object "[Error] Failed to expand environment variable in '$($_.pathToSignedReportingExe)'."
                Write-Host -Object "[Error] $($_.Exception.Message)"
                exit 1
            }
        }
        
        # If the signed product executable still exists at the expanded path, output an error and exit the script.
        if ($SignedExe -and (Test-Path -Path $SignedExe -ErrorAction SilentlyContinue)) {
            Write-Host -Object "[Error] '$AntivirusName' is currently installed at '$SignedExe'. Unable to remove the entry."
            exit 1
        }
        # If the signed reporting executable still exists at the expanded path, output an error and exit the script.
        if ($SignedReportingExe -and (Test-Path -Path $SignedReportingExe -ErrorAction SilentlyContinue)) {
            Write-Host -Object "[Error] '$AntivirusName' is currently installed at '$SignedReportingExe'. Unable to remove the entry."
            exit 1
        }
    }
    # After verifying that the antivirus is not installed at the specified paths, proceed to remove the entries from the Security Center.
    Write-Host -Object "Removing '$AntivirusName' from the Security Center"
    # Loop through each entry in $EntryToRemove and attempt to delete it.
    $EntryToRemove | ForEach-Object {
        try {
            $ErrorActionPreference = "Stop"
            $_.Delete()
            $ErrorActionPreference = "Continue"
            Write-Host -Object "Successfully removed '$AntivirusName' from the Security Center."
        }
        catch {
            Write-Host -Object "[Error] Failed to remove '$AntivirusName' from the Security Center."
            Write-Host -Object "[Error] $($_.Exception.Message)"
            exit 1
        }
    }
    exit $ExitCode
}
end {
    
    
    
}
Description détaillée
Ce script est conçu pour supprimer des entrées antivirus orphelines spécifiques du Centre de sécurité Windows, mais uniquement après avoir vérifié que le logiciel n’est plus installé.
Voici une présentation détaillée de son fonctionnement :
Validation des paramètres et contrôle du serveur
Le script commence par capturer le nom de l’antivirus à partir de la ligne de commande ou des variables d’environnement. Il s’arrête immédiatement s’il est exécuté sur un serveur Windows, car le centre de sécurité n’existe pas dans ces environnements.
Récupération des entrées existantes
Il interroge l’espace de noms WMI root\SecurityCenter2 pour récupérer tous les produits antivirus actuellement enregistrés.
Vérification de l’antivirus
Le script vérifie que le nom de l’antivirus fourni existe dans la liste du centre de sécurité. Si ce n’est pas le cas, il affiche une liste d’options valides pour guider l’utilisateur.
Contrôle de l’installation
Avant la suppression, il recherche les clés de désinstallation du registre pour s’assurer que l’antivirus n’est pas actuellement installé. Il vérifie ensuite s’il reste des exécutables dans les chemins d’installation signalés.
Suppression d’entrée
Une fois que toutes les vérifications ont confirmé que le logiciel a vraiment disparu, il procède à la suppression de l’entrée orpheline du centre de sécurité à l’aide de la méthode .Delete() sur l’objet WMI.
Cas d’utilisation potentiels
Cas de figure : Nettoyage à distance d’un outil antivirus hors service
Un fournisseur de services gérés (MSP) déploie une nouvelle solution de protection des terminaux mais trouve des restes du produit précédent (VIPRE Business Agent) toujours listés dans le Centre de sécurité Windows sur de nombreuses machines. Bien que désinstallées, les entrées persistent, induisant en erreur les rapports de conformité.
En utilisant ce script PowerShell via le module de script de NinjaOne, l’entreprise MSP automatise le nettoyage sur des centaines de terminaux. En quelques minutes, seules les entrées antivirus valides sont conservées, ce qui améliore la précision du tableau de bord et la préparation à l’audit.
Comparaisons
Nettoyage manuel
La suppression manuelle de ces entrées implique de multiples modifications du registre et des appels WMI, ce qui prend du temps et est source d’erreurs, en particulier sur de nombreux systèmes.
Utilisation d’outils tiers
Certains utilitaires tiers permettent de nettoyer les entrées orphelines, mais ils ne procèdent pas à une vérification granulaire ou nécessitent une interaction élevée de la part de l’utilisateur. Ce script offre une alternative légère, vérifiable et automatisée adaptée aux environnements gérés.
Déclencheurs de stratégie RMM
Si certains RMM peuvent signaler les outils antivirus inactifs, ils n’offrent généralement pas de capacités de nettoyage. Ce script complète ces alertes en proposant une remédiation automatisée.
Questions fréquentes
Q : Que se passe-t-il si je l’exécute sur un serveur ?
Le script se termine prématurément en indiquant que le Centre de sécurité Windows n’est pas disponible sur les systèmes d’exploitation serveur.
Q : Puis-je l’exécuter sans les privilèges de l’administrateur ?
Non, le script nécessite des droits élevés pour accéder à WMI et aux chemins d’accès au registre pour le nettoyage.
Q : Que faire si l’antivirus est encore partiellement installé ?
Le script vérifie les clés de registre et les chemins d’accès aux exécutables connus pour s’assurer qu’il a été entièrement désinstallé avant de supprimer l’entrée.
Q : Ce script peut-il être intégré à NinjaOne ?
Oui, vous pouvez le déployer en tant que script personnalisé via NinjaOne et même introduire le nom de l’antivirus de manière dynamique via des variables d’environnement.
Implications
Refléter avec précision l’état de santé d’un système est primordial dans la gestion informatique moderne. Le fait de laisser des entrées antivirus orphelines introduit du bruit, fausse l’analyse des données et peut indiquer à tort l’état de la protection aux utilisateurs finaux et aux administrateurs. En utilisant PowerShell pour appliquer l’hygiène dans le Centre de sécurité, les équipes informatiques renforcent l’intégrité de leurs systèmes de surveillance et réduisent la surface d’attaque causée par les erreurs de configuration.
Recommandations
- Tester en laboratoire: Validez le script sur un terminal de test pour vous assurer de sa compatibilité avec votre solution antivirus.
 - Utiliser la journalisation descriptive: Modifiez le script pour obtenir des journaux plus précis en cas de déploiement sur de nombreux terminaux.
 - Tirer parti de l’intégration RMM: Associez ce script à la programmation et aux alertes de NinjaOne pour garantir une conformité continue.
 - Entrées de documents: Conservez une liste de référence des entrées antivirus attendues afin d’éviter la suppression accidentelle d’entrées légitimes.
 
Conclusion
L’utilisation de scripts PowerShell pour supprimer les entrées orphelines est un moyen intelligent et évolutif pour les professionnels de l’informatique de garantir l’exactitude des rapports sur les terminaux. Dans les environnements gérés, la possibilité d’automatiser ces tâches n’est pas seulement une commodité, c’est une nécessité.
Avec NinjaOne, des scripts de ce type peuvent être déployés à l’échelle de l’entreprise en quelques secondes, contrôlés et intégrés dans des flux de travail de conformité et de remédiation plus vastes. Que vous gériez des dizaines ou des milliers de terminaux, des données propres commencent par des outils précis, et ce script en est un.