Vous devez décider si des RemoteApps peuvent être retirées ? Voici des méthodes concrètes—de l’audit 4688 aux scripts PowerShell—pour savoir précisément qui lance quelle application et à quels horaires sur vos serveurs Remote Desktop Services (RDS).
Vue d’ensemble de la question
L’objectif de l’administrateur est de recenser les utilisateurs qui exécutent encore certaines applications publiées (RemoteApp) ou utilisées en session RDS, et d’obtenir des plages de dates/horaires fiables. Cette visibilité est indispensable pour planifier le retrait (décommissionnement) d’applications sans perturber les métiers.
Réponses & solutions
Objectif | Méthode native | Étapes principales | Points forts | Limites / précautions |
---|---|---|---|---|
Connaître quel utilisateur a lancé quelle appli et quand | Rapports d’utilisation “Remote Access Accounting” (anciennement “RRAS Reporting”) | Ouvrir Gestionnaire de serveur → Remote Access (si rôle installé). Dans Reports, activer Accounting (stockage NPS/SQL/CSV selon votre design). Générer un Usage Report et filtrer par collection/RemoteApp si disponible. | Pas de script à écrire. Rapports graphiques (tableaux et courbes) intégrés. | Souvent plus adapté au trafic VPN/Remote Access qu’au détail processus RDS. Ne fournit pas toujours le nom de processus ; usage limité pour l’inventaire applicatif fin. |
Journal d’audit “Suivi des processus” (ID 4688 / 4689) | GPO : Configuration ordinateur → Stratégies → Paramètres Windows → Paramètres de sécurité → Stratégies d’audit avancées → Audit des processus → Création de processus ; activer Succès (et idéalement Échec). Option sécurité : Inclure la ligne de commande dans les événements de création de processus (pour capturer les arguments). Exploiter les événements 4688 (création) et 4689 (fin) via Observateur d’événements ou Get-WinEvent , puis exporter en CSV. | Niveau de détail fin : utilisateur, processus, ligne de commande, horodatage. Fonctionne pour toute application (RemoteApp, session complète, services). | Volume de logs important → prévoir rétention/archivage. Nécessite filtrer le bruit (processus système, mises à jour, AV…). | |
“Remote Desktop Services Manager” (ou quser / qwinsta ) | Instantané : exécuter quser pour lister les sessions actives et leurs IDs. Associer à query process /ID:<SessionID> pour voir les processus par session. | Outils déjà présents sur le serveur. Aucun prérequis côté GPO. | Instantané uniquement : pas d’historique nativement. Nécessite script ou tâche planifiée pour journaliser dans le temps. | |
Data Collector Set – Performance Monitor | Ouvrir PerfMon → Data Collector Sets → User Defined → Create new → Performance counter. Ajouter des compteurs Process\ID Process, Process\% Processor Time, IO si besoin. Planifier un échantillonnage (ex. toutes les 5 min) et écrire en CSV. | Impact système faible avec un intervalle raisonnable. CSV directement exploitable (Excel, Power BI, scripts). | Ne montre pas l’utilisateur ; corrélation nécessaire (4688, WMI ou SID de session). Configuration manuelle initiale. | |
PowerShell & WMI/CIM (Automatisation légère) | Interroger les sessions (Get-RDUserSession ou quser ). Récupérer les processus par session et le propriétaire via WMI/CIM. Exporter en CSV (Export-Csv ) pour un suivi dans le temps. | Mise en place rapide, filtrage par nom/répertoire d’appli. Parfait pour un journal léger hors SIEM. | Par défaut, c’est un instantané (à planifier pour l’historique). La récupération du propriétaire de processus repose sur WMI/CIM. |
Conseils complémentaires
- Centraliser les journaux dans un SIEM (Azure Monitor, Splunk, ELK) pour répondre rapidement à la requête : « Qui a lancé
App.exe
du 01/09 au 30/09 ? ». - Conserver les logs selon votre politique de conformité (souvent 90 jours à 1 an, voire plus pour les métiers réglementés).
- Limiter le bruit : liste blanche des applications cibles, filtrage au niveau de l’audit Process Creation et en post‑traitement.
- Confidentialité : informer les utilisateurs, chiffrer les exports contenant des données personnelles et appliquer le principe de minimisation.
Implémentation pas à pas
Activer l’audit 4688 / 4689 (création/fin de processus)
Méthode via GPO (recommandée)
- Éditez une GPO liée aux serveurs RDS : Configuration ordinateur → Stratégies → Paramètres Windows → Paramètres de sécurité → Stratégies d’audit avancées → Audit des processus → Création de processus → Succès (et Échec si nécessaire).
- Activez la stratégie Système → Audit Process Creation → Inclure la ligne de commande dans les événements de création de processus.
- Forcer l’application :
gpupdate /force
.
Pourquoi la ligne de commande ? Elle permet de distinguer powershell.exe
lançant un script métier d’une simple console et d’identifier un exécutable lancé avec des paramètres spécifiques.
Méthode en ligne de commande (serveur unique)
# Activer l’audit de création de processus (succès/échec)
auditpol /set /subcategory:"Process Creation" /success:enable /failure:enable
# Inclure la ligne de commande dans l’événement 4688
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit" ^
/v ProcessCreationIncludeCmdLine_Enabled /t REG_DWORD /d 1 /f
Extraire l’historique : PowerShell « prêt à l’emploi »
But : obtenir un CSV listant utilisateur, processus, ligne de commande, heure de début (et optionnellement heure de fin), filtré sur certaines applications.
Extraction simple (événements 4688)
$From = [datetime]"2024-09-01"
$To = [datetime]"2024-09-30"
$Targets = @("App.exe","AutreAppli.exe") # adaptez
$filter = @{
LogName = 'Security'
Id = 4688
StartTime = $From
EndTime = $To
}
$rows = foreach ($ev in (Get-WinEvent -FilterHashtable $filter -ErrorAction Stop)) {
$xml = [xml]$ev.ToXml()
$d = @{}
foreach ($n in $xml.Event.EventData.Data) { $d[$n.Name] = $n.'#text' }
$proc = Split-Path -Leaf $d['NewProcessName']
if ($Targets -and ($Targets -notcontains $proc)) { continue }
[pscustomobject]@{
TimeCreated = $ev.TimeCreated
User = if ($d['SubjectDomainName']) { "$($d['SubjectDomainName'])$($d['SubjectUserName'])" } else { $d['SubjectUserName'] }
Computer = $xml.Event.System.Computer
ProcessName = $proc
NewProcessId = $d['NewProcessId']
CommandLine = $d['CommandLine']
ParentProcess = $d['ParentProcessName']
LogonId = $d['SubjectLogonId']
}
}
$rows | Sort-Object TimeCreated | Export-Csv .\RDS_App_Usage_4688.csv -NoTypeInformation -Encoding UTF8
Write-Host "Export : .\RDS_App_Usage_4688.csv"
Résultat : pour chaque lancement, vous avez l’utilisateur, le serveur, le nom de l’exécutable (et sa ligne de commande si activée), avec l’horodatage précis. Cela répond déjà à la majorité des besoins pour décider du retrait d’une application.
Ajouter l’heure de fin (corrélation 4688 ↔ 4689)
L’événement 4689 (fin de processus) peut être corrélé à 4688 via la paire LogonId + ProcessId. Attention : les PIDs sont réutilisés par Windows ; limitez la recherche dans une fenêtre temporelle raisonnable.
$From = [datetime]"2024-09-01"
$To = [datetime]"2024-09-30"
$start = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4688; StartTime=$From; EndTime=$To}
$stop = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4689; StartTime=$From; EndTime=$To}
# Index des fins par (LogonId, ProcessId)
$ends = @{}
foreach ($ev in $stop) {
$xml = [xml]$ev.ToXml()
$d = @{}
foreach ($n in $xml.Event.EventData.Data) { $d[$n.Name] = $n.'#text' }
$key = "{0}|{1}" -f $d['SubjectLogonId'],$d['ProcessId']
if (-not $ends.ContainsKey($key)) { $ends[$key] = @() }
$ends[$key] += $ev.TimeCreated
}
$result = foreach ($ev in $start) {
$xml = [xml]$ev.ToXml()
$d = @{}
foreach ($n in $xml.Event.EventData.Data) { $d[$n.Name] = $n.'#text' }
$proc = Split-Path -Leaf $d['NewProcessName']
$key = "{0}|{1}" -f $d['SubjectLogonId'],$d['NewProcessId']
$endTimes = if ($ends.ContainsKey($key)) { $ends[$key] } else { @() }
[pscustomobject]@{
StartTime = $ev.TimeCreated
EndTime = ($endTimes | Sort-Object | Select-Object -Last 1)
DurationMin = if ($endTimes) { [math]::Round(((($endTimes | Sort-Object | Select-Object -Last 1) - $ev.TimeCreated).TotalMinutes),2) } else { $null }
User = if ($d['SubjectDomainName']) { "$($d['SubjectDomainName'])$($d['SubjectUserName'])" } else { $d['SubjectUserName'] }
Computer = $xml.Event.System.Computer
ProcessName = $proc
CommandLine = $d['CommandLine']
LogonId = $d['SubjectLogonId']
PidHex = $d['NewProcessId']
}
}
$result | Export-Csv .\RDS_App_Usage_4688_4689.csv -NoTypeInformation -Encoding UTF8
Instantané « qui exécute quoi maintenant » (sessions RDS)
Pour un contrôle ponctuel (ou un journal léger planifié), utilisez WMI/CIM pour obtenir le propriétaire de chaque processus. Ci‑dessous, un script qui cible des exécutables précis et enregistre un CSV.
$Targets = @("App.exe","AutreAppli.exe") # Laissez vide @() pour tout
function Get-ProcessWithOwner {
Get-CimInstance Win32_Process | ForEach-Object {
$o = Invoke-CimMethod -InputObject $_ -MethodName GetOwner -ErrorAction SilentlyContinue
[pscustomobject]@{
TimeStamp = (Get-Date)
Computer = $env:COMPUTERNAME
ProcessName = $*.Name
ProcessId = $*.ProcessId
SessionId = $*.SessionId
StartTime = ([System.Management.ManagementDateTimeConverter]::ToDateTime($*.CreationDate))
CommandLine = $_.CommandLine
User = if ($o.ReturnValue -eq 0) { "{0}{1}" -f $o.Domain, $o.User } else { $null }
}
}
}
$rows = Get-ProcessWithOwner | Where-Object { ($Targets.Count -eq 0) -or ($Targets -contains $*.ProcessName) }
$path = ".\RDS_Process_Snapshot*{0:yyyyMMdd_HHmmss}.csv" -f (Get-Date)
$rows | Sort-Object SessionId, User, ProcessName | Export-Csv $path -NoTypeInformation -Encoding UTF8
Write-Host "Export : $path"
Astuce : planifiez ce script toutes les 5–10 minutes via le Planificateur de tâches pour obtenir une série temporelle légère (quasi‑historique), sans passer par un SIEM.
Alternative « intégrée RDS » : quser
et query process
# Lister les sessions
quser
# Lister les processus d'une session donnée (ex. ID 3)
query process /ID:3
# Tout capturer et exporter (exemple simple)
$now = Get-Date -Format "yyyyMMdd_HHmmss"
"SessionId,User,Process" | Out-File ".\quser_$now.csv" -Encoding UTF8
$sessionLines = (quser) -split "`r?`n" | Select-Object -Skip 1
foreach ($line in $sessionLines) {
if ($line -match "^\s*(\S+).+?\s+(\d+)\s") {
$user = $matches[1]; $sid = $matches[2]
$procs = (query process /ID:$sid 2>&1) -join "; "
"$sid,$user,""$procs""" | Out-File ".\quser_$now.csv" -Encoding UTF8 -Append
}
}
Suivi par compteurs de performance (PerfMon)
Pour mesurer l’usage CPU/mémoire/IO par processus à intervalles réguliers :
- Créez un Data Collector Set avec les compteurs Process\ID Process, Process\% Processor Time, Process\Working Set…
- Enregistrez au format CSV toutes les 5 minutes (adaptable).
- Corrélez ensuite avec l’audit 4688 pour attribuer un processus à un utilisateur.
Automatisation possible avec logman
:
logman create counter "RDS_ProcLog" -f csv -o "C:\Logs\RDS_Proc\proc" -v mmddhhmm -si 00:05:00 ^
-c "\Process(*)\ID Process" "\Process(*)\% Processor Time" "\Process(*)\Working Set"
logman start "RDS_ProcLog"
# ... pour arrêter : logman stop "RDS_ProcLog"
Centralisation & requêtes (SIEM ou exports)
Structure de données recommandée
Colonne | Description | Exemple |
---|---|---|
TimeCreated | Date/heure UTC de l’événement | 2024‑09‑14T08:35:22Z |
User | Domaine\Utilisateur | CONTOSO\jdupont |
Computer | Nom du serveur RDS | RDSH‑02 |
ProcessName | Nom de l’exécutable | App.exe |
CommandLine | Ligne de commande complète (si activée) | « C:\Apps\App.exe » /client ABC |
ParentProcess | Processus parent (diagnostic) | explorer.exe |
LogonId | Identifiant de session de sécurité | 0x12abc3 |
StartTime / EndTime | Horaires de début et fin (si 4689 corrélé) | 09:02 / 09:37 |
Exemples de requêtes utiles
Top utilisateurs par application (CSV/PowerShell)
Import-Csv .\RDS_App_Usage_4688.csv |
Group-Object ProcessName, User |
Sort-Object Count -Descending |
Select-Object @{n='ProcessName';e={$_.Group[0].ProcessName}},
@{n='User';e={$_.Group[0].User}},
Count |
Export-Csv .\Top_Users_By_App.csv -NoTypeInformation -Encoding UTF8
Fenêtre de vie d’une application en journée
Import-Csv .\RDS_App_Usage_4688_4689.csv |
Where-Object { $_.ProcessName -eq "App.exe" } |
Select-Object User, StartTime, EndTime, DurationMin |
Sort-Object StartTime |
Export-Csv .\App_Timeline.csv -NoTypeInformation -Encoding UTF8
Estimations de volumétrie & rétention
- Un serveur RDS actif peut générer plusieurs dizaines de milliers d’événements 4688/jour.
- Prévoir un stockage compressé et un plan d’archivage (ex. export hebdo en CSV + purge contrôlée du journal Sécurité).
- Définissez une rétention conforme (ex. 180 jours) et un processus d’extraction documenté.
Checklist « Retrait d’une RemoteApp »
- Activer l’audit 4688 (+ ligne de commande) sur l’ensemble des hôtes RDS.
- Collecter au moins 30 jours de données (idéalement un cycle métier complet).
- Filtrer les processus cibles (liste blanche d’exécutables ou de répertoires).
- Valider avec les équipes métiers (utilisateurs identifiés, scénarios d’usage légitimes).
- Notifier un gel de publication (phase de test sans retirer physiquement l’appli).
- Planifier le retrait et mettre en place un rollback (package RemoteApp conservé 2–4 semaines).
- Surveiller post‑retrait (aucun 4688 résiduel de l’appli, tickets support).
Bonnes pratiques & conformité
- Moindre privilège : seuls les admins RDS/ sécurité doivent accéder aux exports.
- Minimisation : limitez l’audit aux serveurs/collections pertinents et aux applications cibles.
- Transparence : informez les utilisateurs que l’activité applicative est enregistrée.
- Exactitude : synchronisez l’heure (NTP) pour éviter des écarts d’horodatage en environnement à plusieurs hôtes.
FAQ & pièges courants
- RemoteApp vs session complète : l’audit 4688 fonctionne dans les deux cas, car il trace la création de processus au niveau du serveur, pas seulement de la session.
- Où retrouver la ligne de commande ? : dans 4688 uniquement si l’option « Inclure la ligne de commande… » est activée. Sans cela, vous n’aurez que le chemin du binaire.
- Corrélation 4624 (connexion) → 4688 (processus) : utilisez l’Identifiant d’ouverture de session (
SubjectLogonId
) pour relier un utilisateur connecté (type 10 RDP) aux processus qu’il lance. - Get‑Process et nom d’utilisateur : selon la version,
Get-Process
ne retourne pas le nom d’utilisateur. Préférez WMI/CIM (Invoke‑CimMethod GetOwner
) pour une fiabilité maximale. - Volume de logs trop élevé ? Filtrez côté SIEM ou collecteur (par répertoire applicatif ou liste blanche d’exécutables) et augmentez la taille du journal Sécurité.
Architecture de référence minimale
- Collecte : activer 4688 (+ commande) sur chaque hôte RDS.
- Transport : transfert via abonnement WEF ou agent de votre SIEM (ou export CSV planifié si pas de SIEM).
- Stockage : conserver 90–180 jours, avec archivage mensuel compressé.
- Exploitation : requêtes prêtes à l’emploi (Top apps, Top users, Horaires de pointe, Durées par session).
Exemple de rapport décisionnel
Application | Utilisateurs uniques (30 j) | Exécutions totales | Dernière exécution | Fenêtre horaire typique | Recommandation |
---|---|---|---|---|---|
App.exe | 3 | 42 | 2024‑09‑29 16:48 | 08:00–18:00 | Garder (usage métier actif) |
LegacyTool.exe | 0 | 0 | — | — | Retirer (aucune utilisation 30 j) |
BatchClient.exe | 1 | 7 | 2024‑09‑15 02:10 | Nuit (01:00–03:00) | Remplacer par job serveur |
Synthèse rapide
- Remote Access Reporting : pratique mais rarement suffisant pour l’inventaire applicatif RDS.
- Audit 4688/4689 : la source la plus fiable pour connaître application + utilisateur + horodatage.
- PowerShell instantané (
WMI/CIM
,quser
) : idéal pour contrôle ponctuel ou journalisation légère. - PerfMon : ajoute la dimension performance, à corréler avec l’utilisateur.
- Bonne pratique : combinez 4688 (historique détaillé) + export CSV/SIEM pour décider sereinement du retrait des RemoteApps.
Annexes
Modèle de rapport d’usage (CSV)
TimeCreated,User,Computer,ProcessName,CommandLine,StartTime,EndTime,DurationMin
2024-09-05T09:14:33Z,CONTOSO\jdupont,RDSH-02,App.exe,"C:\Apps\App.exe /client ABC",2024-09-05T09:14:33Z,2024-09-05T09:52:11Z,37.6
Script complet « du lancement à la décision »
param(
[datetime]$From = (Get-Date).AddDays(-30),
[datetime]$To = (Get-Date),
[string[]]$Targets = @("App.exe","AutreAppli.exe")
)
# 1) Extraction 4688
$filter = @{ LogName='Security'; Id=4688; StartTime=$From; EndTime=$To }
$starts = foreach ($ev in (Get-WinEvent -FilterHashtable $filter -ErrorAction Stop)) {
$xml = [xml]$ev.ToXml(); $d=@{}; foreach($n in $xml.Event.EventData.Data){$d[$n.Name]=$n.'#text'}
$proc = Split-Path -Leaf $d['NewProcessName']
if ($Targets -and ($Targets -notcontains $proc)) { continue }
[pscustomobject]@{
StartTime = $ev.TimeCreated
User = if ($d['SubjectDomainName']) { "$($d['SubjectDomainName'])$($d['SubjectUserName'])" } else { $d['SubjectUserName'] }
Computer = $xml.Event.System.Computer
ProcessName = $proc
CommandLine = $d['CommandLine']
LogonId = $d['SubjectLogonId']
PidHex = $d['NewProcessId']
}
}
# 2) Optionnel : corrélation 4689 pour la fin
$endsIndex = @{}
foreach ($ev in (Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4689; StartTime=$From; EndTime=$To})) {
$xml = [xml]$ev.ToXml(); $d=@{}; foreach($n in $xml.Event.EventData.Data){$d[$n.Name]=$n.'#text'}
$key = "{0}|{1}" -f $d['SubjectLogonId'],$d['ProcessId']
if (-not $endsIndex.ContainsKey($key)) { $endsIndex[$key] = @() }
$endsIndex[$key] += $ev.TimeCreated
}
# 3) Assemblage + métriques
$report = foreach ($s in $starts) {
$key = "{0}|{1}" -f $s.LogonId, $s.PidHex
$end = if ($endsIndex.ContainsKey($key)) { ($endsIndex[$key] | Sort-Object | Select-Object -Last 1) } else { $null }
[pscustomobject]@{
User = $s.User
Computer = $s.Computer
ProcessName = $s.ProcessName
CommandLine = $s.CommandLine
StartTime = $s.StartTime
EndTime = $end
DurationMin = if ($end) { [math]::Round((($end - $s.StartTime).TotalMinutes),2) } else { $null }
}
}
# 4) Indicateurs synthétiques
$byApp = $report | Group-Object ProcessName | ForEach-Object {
[pscustomobject]@{
ProcessName = $*.Name
ExecCount = $*.Count
UniqueUsers = ($*.Group | Select-Object -ExpandProperty User -Unique).Count
LastExecutionUtc = ($*.Group | Measure-Object -Property StartTime -Maximum).Maximum
}
} | Sort-Object ExecCount -Descending
# 5) Exports
$ts = (Get-Date -Format "yyyyMMdd_HHmm")
$report | Export-Csv ".\RDS_App_Usage_$ts.csv" -NoTypeInformation -Encoding UTF8
$byApp | Export-Csv ".\RDS_App_Summary_$ts.csv" -NoTypeInformation -Encoding UTF8
Write-Host "Exports : RDS_App_Usage_$ts.csv, RDS_App_Summary_$ts.csv"
En pratique : activez l’audit 4688 (avec ligne de commande), laissez collecter 30 jours, exécutez les scripts d’extraction et vos tableaux de synthèse vous indiqueront clairement les applis encore utilisées, par qui, et quand. Vous pourrez alors retirer les RemoteApps obsolètes en toute confiance.