Le protocole Remote Desktop Protocol (RDP) est une fonctionnalité essentielle pour la gestion des systèmes à distance, que vous travailliez dans l’assistance informatique, l’administration des systèmes ou que vous gériez une entreprise MSP. S’assurer que le RDP est correctement configuré et surveiller le port sur lequel il écoute sont des tâches vitales pour maintenir des terminaux sécurisés et fonctionnels. Ce billet examine un script PowerShell qui vérifie l’état et le port de Remote Desktop et enregistre éventuellement le résultat dans un champ personnalisé de NinjaOne. Il s’agit d’une solution pratique pour l’automatisation des rapports et des audits dans les environnements informatiques modernes.
Contexte
Le bureau à distance est activé par défaut dans de nombreuses entreprises, mais sa configuration peut dériver au fil du temps. Des facteurs tels que les changements de stratégie de groupe, les mises à jour de sécurité ou les modifications apportées par les utilisateurs peuvent désactiver RDP ou modifier son port d’écoute, ce qui rend les systèmes inaccessibles ou vulnérables. Dans les environnements plus vastes, en particulier ceux qui sont gérés par des plateformes RMM telles que NinjaOne, il est impossible de vérifier chaque machine manuellement. L’automatisation de ce contrôle au moyen d’un script PowerShell permet non seulement de gagner du temps, mais aussi de garantir l’exactitude et la conformité.
Ce script a été conçu en tenant compte de l’intégration de NinjaOne, ce qui le rend particulièrement utile pour les MSP et les services informatiques qui utilisent cette plateforme. Il ne se contente pas de recueillir l’état RDP, il prend également en charge l’enregistrement automatique dans un champ personnalisé, ce qui facilite la documentation des actifs.
Le script
#Requires -Version 5.1
<#
.SYNOPSIS
Reports the status of Remote Desktop and the port it is listening on.
.DESCRIPTION
Reports the status of Remote Desktop and the port it is listening on.
With the option to save the results to a custom field.
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
(No Parameters)
## EXAMPLE OUTPUT WITHOUT PARAMS ##
[Info] Enabled | Port: 3389
PARAMETER: -RdpStatusCustomFieldName "RDPStatus"
Name of a custom field to save the results to.
.EXAMPLE
-RdpStatusCustomFieldName "RDPStatus"
## EXAMPLE OUTPUT WITH RdpStatusCustomFieldName ##
[Info] Enabled | Port: 3389
[Info] Attempting to set Custom Field 'RDPStatus'.
[Info] Successfully set Custom Field 'RDPStatus'!
.NOTES
Minimum OS Architecture Supported: Windows 10, Windows Server 2016
Release Notes: Initial Release
#>
[CmdletBinding()]
param (
[Parameter()]
[String]$RdpStatusCustomFieldName
)
begin {
function Set-NinjaProperty {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True)]
[String]$Name,
[Parameter()]
[String]$Type,
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
$Value,
[Parameter()]
[String]$DocumentName
)
# Measure the number of characters in the provided value
$Characters = $Value | ConvertTo-Json | Measure-Object -Character | Select-Object -ExpandProperty Characters
# Throw an error if the value exceeds the character limit of 200,000 characters
if ($Characters -ge 200000) {
throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded: the value is greater than or equal to 200,000 characters.")
}
# Initialize a hashtable for additional documentation parameters
$DocumentationParams = @{}
# If a document name is provided, add it to the documentation parameters
if ($DocumentName) { $DocumentationParams["DocumentName"] = $DocumentName }
# Define a list of valid field types
$ValidFields = "Attachment", "Checkbox", "Date", "Date or Date Time", "Decimal", "Dropdown", "Email", "Integer", "IP Address", "MultiLine", "MultiSelect", "Phone", "Secure", "Text", "Time", "URL", "WYSIWYG"
# Warn the user if the provided type is not valid
if ($Type -and $ValidFields -notcontains $Type) { Write-Warning "$Type is an invalid type. Please check here for valid types: https://ninjarmm.zendesk.com/hc/en-us/articles/16973443979789-Command-Line-Interface-CLI-Supported-Fields-and-Functionality" }
# Define types that require options to be retrieved
$NeedsOptions = "Dropdown"
# If the property is being set in a document or field and the type needs options, retrieve them
if ($DocumentName) {
if ($NeedsOptions -contains $Type) {
$NinjaPropertyOptions = Ninja-Property-Docs-Options -AttributeName $Name @DocumentationParams 2>&1
}
}
else {
if ($NeedsOptions -contains $Type) {
$NinjaPropertyOptions = Ninja-Property-Options -Name $Name 2>&1
}
}
# Throw an error if there was an issue retrieving the property options
if ($NinjaPropertyOptions.Exception) { throw $NinjaPropertyOptions }
# Process the property value based on its type
switch ($Type) {
"Checkbox" {
# Convert the value to a boolean for Checkbox type
$NinjaValue = [System.Convert]::ToBoolean($Value)
}
"Date or Date Time" {
# Convert the value to a Unix timestamp for Date or Date Time type
$Date = (Get-Date $Value).ToUniversalTime()
$TimeSpan = New-TimeSpan (Get-Date "1970-01-01 00:00:00") $Date
$NinjaValue = $TimeSpan.TotalSeconds
}
"Dropdown" {
# Convert the dropdown value to its corresponding GUID
$Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name"
$Selection = $Options | Where-Object { $_.Name -eq $Value } | Select-Object -ExpandProperty GUID
# Throw an error if the value is not present in the dropdown options
if (!($Selection)) {
throw [System.ArgumentOutOfRangeException]::New("Value is not present in dropdown options.")
}
$NinjaValue = $Selection
}
default {
# For other types, use the value as is
$NinjaValue = $Value
}
}
# Set the property value in the document if a document name is provided
if ($DocumentName) {
$CustomField = Ninja-Property-Docs-Set -AttributeName $Name -AttributeValue $NinjaValue @DocumentationParams 2>&1
}
else {
# Otherwise, set the standard property value
$CustomField = $NinjaValue | Ninja-Property-Set-Piped -Name $Name 2>&1
}
# Throw an error if setting the property failed
if ($CustomField.Exception) {
throw $CustomField
}
}
}
process {
if ($env:rdpStatusCustomFieldName -and $env:rdpStatusCustomFieldName -notlike "null") { $RdpStatusCustomFieldName = $env:rdpStatusCustomFieldName }
# Terminal Server registry path
$RdpPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server'
# Deny RDP Connections
$DenyRdpConnections = Get-ItemProperty -Path $RdpPath -Name 'fDenyTSConnections' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty fDenyTSConnections -ErrorAction SilentlyContinue
# RDP Port
$RdpPort = Get-ItemProperty -Path "$RdpPath\WinStations\RDP-Tcp" -Name PortNumber -ErrorAction SilentlyContinue | Select-Object -ExpandProperty PortNumber -ErrorAction SilentlyContinue
# 1 or $null = Disabled (Default)
# 0 = Enabled
$RdpEnabled = if ($DenyRdpConnections -eq 0) { "Enabled" }else { "Disabled" }
# 3389 or $null = 3389 (Default)
$RdpPort = if ($null -eq $RdpPort) { "3389" }else { "$RdpPort" }
$Report = "$RdpEnabled | Port: $RdpPort"
Write-Host "[Info] $Report"
if ($RdpStatusCustomFieldName) {
try {
Write-Host "[Info] Attempting to set Custom Field '$RdpStatusCustomFieldName'."
Set-NinjaProperty -Name $RdpStatusCustomFieldName -Value $Report
Write-Host "[Info] Successfully set Custom Field '$RdpStatusCustomFieldName'!"
}
catch {
Write-Host "[Error] $($_.Exception.Message)"
exit 1
}
}
}
end {
}
Description détaillée
À un niveau élevé, le script effectue trois tâches :
- Vérifie si RDP est activé
- Détermine le port utilisé par RDP
- Optionnellement, les résultats sont enregistrés dans un champ personnalisé NinjaOne
Flux étape par étape
- Traitement des paramètres
Le script accepte un paramètre facultatif :
powershell
CopyEdit
[String]$RdpStatusCustomFieldName
Cette option permet à l’utilisateur de spécifier un champ personnalisé NinjaOne dans lequel les résultats seront enregistrés.
- Support des variables d’environnement
Si le paramètre n’est pas transmis, le script vérifie la variable d’environnement $env:rdpStatusCustomFieldName, afin d’assurer la compatibilité avec les variables d’exécution injectées.
- Consultation du registre
Deux clés de registre critiques sont interrogées :
- fDenyTSConnections sous HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server pour déterminer l’état du RDP.
- PortNumber sous WinStations\RDP-Tcp pour trouver le port RDP configuré.
- Formatage de la sortie
Sur la base des valeurs du registre, le script produit une chaîne de caractères comme :
yaml
CopyEdit
[Info] Enabled | Port: 3389
- Intégration de champs personnalisés
Si un nom de champ personnalisé est fourni, le script appelle la fonction Set-NinjaProperty, qui :
- Convertit et valide les données saisies en fonction du type de champ.
- Gère les listes déroulantes, les cases à cocher, les dates, etc.
- Utilise Ninja-Property-Set-Piped ou Ninja-Property-Docs-Set pour appliquer les changements.
Cette partie est enveloppée dans un bloc try/catch afin de gérer les erreurs de manière élégante.
powershell
CopyEdit
Set-NinjaProperty -Name $RdpStatusCustomFieldName -Value $Report
Cas d’utilisation potentiels
Cas de figure : Un fournisseur de services gérés (MSP) effectuant un audit du RDP
Une entreprise MSP gère 500 appareils Windows. L’équipe de sécurité veut s’assurer que le protocole RDP est désactivé sur toutes les machines qui n’en ont pas explicitement besoin. Au lieu de se connecter à chaque appareil, l’entreprise MSP utilise NinjaOne pour déployer ce script sur l’ensemble du parc informatique. Le script vérifie l’état et le port RDP, puis écrit le résultat dans un champ personnalisé comme RDPStatus. Un rapport est généré directement à partir de NinjaOne, indiquant les machines pour lesquelles le RDP est activé et les ports exposés.
Comparaisons
Méthode manuelle
- Naviguez vers Propriétés système > Paramètres à distance ou vérifiez via l’interface graphique (GUI).
Ou utiliser des commandes PowerShell :
powershell
CopyEdit
Get-ItemProperty -Path ‘HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server’ -Name ‘fDenyTSConnections’
Limites
- Pas d’automatisation
- Pas d’enregistrement centralisé
- Prend du temps pour les grands réseaux
Outils tiers
Certains outils de sécurité peuvent vérifier les paramètres RDP, mais ils manquent souvent d’intégration native avec les plateformes RMM. Ce script comble cette lacune en s’interfaçant directement avec NinjaOne, offrant ainsi une approche optimisée et centralisée.
Questions fréquentes
Q : Le script fonctionnera-t-il si le RDP n’a jamais été configuré ?
Oui. La valeur par défaut du script est Disabled | Port : 3389 si des clés de registre sont manquantes ou nulles.
Q : Que se passe-t-il si je saisis un nom de champ personnalisé non valide ?
Le script détectera l’erreur et l’affichera :
[Error] Field name not found or unsupported type (Nom de champ introuvable ou type non pris en charge).
Q : Est-il possible de l’exécuter en dehors de NinjaOne ?
Absolument. Le script affiche les résultats dans la console, indépendamment de l’intégration avec NinjaOne.
Implications
Les paramètres RDP mal configurés sont un vecteur courant de mouvement latéral et d’accès non autorisé. Ce script permet une détection précoce, ce qui permet aux équipes informatiques de s’attaquer aux vulnérabilités de manière proactive. L’enregistrement des résultats dans NinjaOne garantit une visibilité permanente, utile pour la conformité et les audits.
Si le protocole RDP est activé par inadvertance, en particulier avec des ports qui ne sont pas par défaut, il peut exposer les machines à des attaques par force brute. L’exécution régulière de ce script permet d’éviter de telles expositions.
Recommandations
- Automatiser le déploiement: Planifiez l’exécution hebdomadaire de ce script via les politiques d’automatisation de NinjaOne.
- Alerte : Combinez avec des alertes conditionnelles basées sur des valeurs de champs personnalisés (par exemple, RDP activé).
- Port de référence: Validez que seul le port 3389 est utilisé, à moins qu’une configuration personnalisée ne soit en place.
- Documentation : Conservez un journal des changements d’état historiques du RDP via un rapport de champ personnalisé versionné.
Conclusion
Ce script PowerShell illustre la puissance de l’automatisation des opérations informatiques. En vérifiant l’état et le port de Remote Desktop à l’aide de PowerShell et en intégrant éventuellement NinjaOne, il optimise l’audit et renforce la sécurité des terminaux.
Pour les MSP et les professionnels de l’informatique qui gèrent de nombreux appareils, un tel outil n’est pas seulement pratique, il est essentiel. Grâce à la prise en charge des champs personnalisés de NinjaOne, les informations sont centralisées, consultables et exploitables, ce qui permet des réponses plus rapides et une meilleure visibilité de tous les actifs gérés.