Chaque élève apporte un domaine d'expertise au parc. Ton module :
À la fin, tous les modules sont visibles dans le même dashboard — chacun voit le résultat des autres.
Voici les 16 modules disponibles — ton prof t'en attribue 2.
Modules de base (tronc commun de la classe)
| N° | Module | Slug | Question à éclairer |
|---|---|---|---|
| 1 | Réseau & IP | network |
Mon PC est-il bien configuré pour le réseau ? |
| 2 | Sécurité & patchs | security |
Mon PC est-il à jour ? Defender est-il actif ? |
| 3 | Utilisateurs & groupes | users |
Qui a les droits sur mon PC ? |
| 4 | Services & démarrage | startup |
Qu'est-ce qui démarre avec Windows ? |
| 5 | Périphériques USB | usb |
Qu'est-ce qui est branché en USB ? |
| 6 | Tâches planifiées | scheduler |
Qu'est-ce qui se déclenche tout seul ? |
| 7 | Connexions actives | connections |
Mon PC parle-t-il à des inconnus ? |
| 8 | Santé du disque | health |
Combien de temps va vivre mon disque ? |
Modules complémentaires
| N° | Module | Slug | Question à éclairer |
|---|---|---|---|
| 9 | Performance temps réel | performance |
Mon PC est-il sous-utilisé ou en goulot ? |
| 10 | Pare-feu Windows | firewall |
Quelles règles laissent passer le trafic ? |
| 11 | Partages SMB | shares |
Mon PC partage-t-il quelque chose ? |
| 12 | Batterie & alim. | power |
En quelle santé est ma batterie ? |
| 13 | GPU & écrans | display |
Quel matériel d'affichage est branché ? |
| 14 | Logs d'événements | events |
Qu'est-ce qui plante sur mon PC ? |
| 15 | Chiffrement BitLocker | encryption |
Mes données sont-elles protégées ? |
| 16 | Réseaux Wi-Fi | wifi |
Où mon portable a-t-il déjà été connecté ? |
Tous les modules doivent respecter cette enveloppe :
{
"module": {
"name": "network",
"summary": "Une phrase humaine (15 mots max) résumant l'état observé",
"stats": {
"primaryMetric": "valeur clé chiffrée",
"primaryLabel": "ce que représente la valeur"
},
"data": { /* dépend du module */ }
}
}
Le summary et la primaryMetric sont affichés directement dans la
carte du dashboard. Le data est affiché en détail sur la fiche PC.
💡 Pourquoi cette enveloppe ? Pour que le PHP du dashboard puisse afficher quelque chose sans connaître les modules à l'avance. C'est de l'extensibilité par convention — un principe qu'on retrouve dans tous les gros logiciels.
network)$adaptateurs = Get-NetAdapter |
Where-Object Status -eq 'Up' |
Select-Object Name, InterfaceDescription, LinkSpeed, MacAddress
$ips = Get-NetIPAddress -AddressFamily IPv4 |
Where-Object { $_.IPAddress -notlike '169.*' -and $_.IPAddress -ne '127.0.0.1' } |
Select-Object InterfaceAlias, IPAddress, PrefixLength
$dns = Get-DnsClientServerAddress -AddressFamily IPv4 |
Where-Object ServerAddresses |
Select-Object InterfaceAlias, ServerAddresses
primaryMetric : nombre d'adaptateurs actifsprimaryLabel : "interfaces actives"summary : ex. "3 interfaces actives, IP principale 192.168.1.42"Tableau des adaptateurs, badge pour chaque IP, liste des serveurs DNS.
security)$hotfixes = Get-HotFix | Select-Object HotFixID, Description, InstalledOn |
Sort-Object InstalledOn -Descending |
Select-Object -First 20
$defender = Get-MpComputerStatus |
Select-Object AntivirusEnabled, RealTimeProtectionEnabled,
AntivirusSignatureLastUpdated, AMServiceEnabled
primaryMetric : nombre de jours depuis le dernier patchsummary : "Defender actif, dernier patch il y a 12 jours"Get-HotFix ne montre que les patchs wusa. Pour Windows 10/11 récents,
Get-WindowsUpdate (du module PSWindowsUpdate) est plus complet, mais
demande une installation. Reste sur Get-HotFix pour le TP.users)$users = Get-LocalUser |
Select-Object Name, Enabled, LastLogon, PasswordRequired
$admins = Get-LocalGroupMember -Group 'Administrators' -ErrorAction SilentlyContinue |
Select-Object Name, PrincipalSource
$groups = Get-LocalGroup | Select-Object Name, Description
primaryMetric : nombre d'administrateurs locauxsummary : "4 utilisateurs locaux, 2 admins"C'est l'occasion de discuter du principe de moindre privilège. Un PC avec 5 administrateurs locaux est une mauvaise idée. Tu peux colorer en rouge les comptes admin dans la fiche PC.
startup)$servicesActifs = Get-Service |
Where-Object Status -eq 'Running' |
Select-Object Name, DisplayName, StartType
$startups = Get-CimInstance Win32_StartupCommand |
Select-Object Name, Command, Location, User
primaryMetric : nombre de programmes au démarragesummary : "42 services actifs, 8 programmes au démarrage"Quels programmes au démarrage sont vraiment nécessaires ? Pour ton mockup,
mets un bouton « Suspicieux » qui surligne les Command pointant ailleurs
que C:\Program Files.
usb)$usb = Get-PnpDevice -PresentOnly |
Where-Object Class -in @('USB','HIDClass','Camera','Bluetooth','Image') |
Select-Object FriendlyName, Class, Manufacturer, Status
primaryMetric : nombre de périphériques USB connectéssummary : "7 périphériques USB, dont 1 caméra et 1 clé"Différencier visuellement les classes (icône clavier, souris, stockage…). Beau défi de design.
scheduler)$taches = Get-ScheduledTask |
Where-Object State -eq 'Ready' |
Select-Object TaskName, TaskPath, Author,
@{N='LastRun'; E={(Get-ScheduledTaskInfo $_).LastRunTime}},
@{N='NextRun'; E={(Get-ScheduledTaskInfo $_).NextRunTime}}
primaryMetric : nombre de tâches prêtes à se déclenchersummary : "112 tâches planifiées, prochaine dans 2 heures"Il y a beaucoup de tâches Microsoft. Filtre TaskPath -notlike '*\Microsoft\*'
si tu veux ne voir que les tâches tierces.
connections)$connexions = Get-NetTCPConnection -State Established |
Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort,
@{N='Process'; E={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}}
primaryMetric : nombre de connexions sortantes ouvertessummary : "23 connexions actives, 8 processus différents"Regrouper par Process et compter. Top 5 des processus qui communiquent
le plus.
health)$physical = Get-PhysicalDisk |
Select-Object FriendlyName, MediaType, HealthStatus,
@{N='SizeGo'; E={[math]::Round($_.Size/1GB,1)}},
Temperature, Usage
$reliability = Get-PhysicalDisk | Get-StorageReliabilityCounter |
Select-Object Temperature, ReadErrorsTotal, WriteErrorsTotal,
PowerOnHours, Wear
primaryMetric : score de santé (0-100 calculé par toi)summary : "SSD 512 Go, état Healthy, 2 ans en service"100 - (wear%) - (errors > 100 ? 20 : 0). À toi de doser.Les modules suivants (9 à 16) suivent exactement la même structure que
les huit premiers : enveloppe module identique, fichier PHP séparé,
affichage automatique sur la fiche PC. Ils explorent simplement d'autres
facettes du système Windows.
performance)Le tronc commun te donne déjà les processus les plus gourmands. Ce module regarde la machine dans son ensemble : combien tourne-t-elle à vide, combien depuis quand, et a-t-elle assez de mémoire pour bien vivre ?
$cpu = Get-CimInstance Win32_PerfFormattedData_PerfOS_Processor |
Where-Object Name -eq '_Total' |
Select-Object @{N='CpuPct'; E={[int]$_.PercentProcessorTime}}
$os = Get-CimInstance Win32_OperatingSystem
$ram = @{
UsedGo = [math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory)/1MB, 2)
FreeGo = [math]::Round($os.FreePhysicalMemory/1MB, 2)
UsedPct = [int]((($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / $os.TotalVisibleMemorySize) * 100)
}
$uptime = (Get-Date) - $os.LastBootUpTime # objet TimeSpan
$pageFile = Get-CimInstance Win32_PageFileUsage |
Select-Object Name, AllocatedBaseSize, CurrentUsage
primaryMetric : cpuPct (entier 0-100)primaryLabel : "% CPU instantané"summary : "Charge CPU 23 %, RAM 58 %, uptime 4 j 8 h"Trois jauges côte à côte (CPU, RAM, page file), un compteur d'uptime formaté humainement, et un drapeau "sous-utilisé / équilibré / saturé" calculé sur la moyenne des dernières 24 h.
PercentProcessorTime est une photo instantanée. Pour
un vrai diagnostic, il faudrait moyenner sur plusieurs minutes. Mentionne
cette limite dans le summary.Win32_PageFileUsage peut renvoyer vide si Windows gère le fichier
d'échange automatiquement. Prévois ce cas.firewall)Différent du module 2 (Defender = antivirus) et du module 7 (connexions actuelles) : ici, on regarde les règles qui filtrent le trafic, pas ce qui passe en ce moment.
$profiles = Get-NetFirewallProfile |
Select-Object Name, Enabled, DefaultInboundAction, DefaultOutboundAction
# IMPORTANT : Get-NetFirewallRule sans filtre est TRÈS lent (~30 s).
# On filtre dès l'appel.
$rulesIn = Get-NetFirewallRule -Direction Inbound -Action Allow -Enabled True |
Select-Object -First 30 DisplayName, Profile, Owner
$counts = @{
InAllow = (Get-NetFirewallRule -Direction Inbound -Action Allow -Enabled True).Count
InBlock = (Get-NetFirewallRule -Direction Inbound -Action Block -Enabled True).Count
OutAllow = (Get-NetFirewallRule -Direction Outbound -Action Allow -Enabled True).Count
}
primaryMetric : nombre de règles entrantes "Allow" activesprimaryLabel : "règles entrantes Allow"summary : "3 profils actifs, 47 règles entrantes en allow, 1 050 en block"Pourquoi le profil Public bloque-t-il plus que Domain ? Quelles
règles ont été ajoutées manuellement (filtrer sur Owner -eq $null ou
DisplayName personnalisé) ? Un PC avec 300+ règles entrantes en allow sur
le profil Public est potentiellement exposé.
Get-NetFirewallRule sans filtre peut prendre 30 secondes. Toujours
filtrer dès l'appel (-Direction, -Enabled True, -First N).shares)# Partages publiés par mon PC (en excluant les partages système C$, ADMIN$...)
$shares = Get-SmbShare |
Where-Object Special -eq $false |
Select-Object Name, Path, Description, EncryptData
# Lecteurs réseau que MON PC a montés
$mappings = Get-SmbMapping |
Select-Object LocalPath, RemotePath, Status
# Connexions SMB sortantes actives (vers quels serveurs je parle ?)
$connections = Get-SmbConnection |
Select-Object ServerName, ShareName, UserName, Dialect, Encrypted
primaryMetric : nombre de partages locaux publics (hors système)primaryLabel : "partages publiés"summary : "2 partages locaux, 3 lecteurs mappés vers serveur-prof"Un partage local non-protégé est une fuite potentielle. Quels lecteurs
sont mappés vers où ? Bon sujet de discussion classe : pourquoi Windows
expose-t-il par défaut des partages cachés C$, ADMIN$ ? Comment les
désactiver proprement ?
Deux listes côte à côte : "Mon PC partage" (sortant) et "Mon PC se
connecte à" (entrant). Mettre un drapeau orange sur les connexions
non chiffrées (Encrypted -eq $false).
power)⚠️ Module applicable uniquement aux ordinateurs portables. Sur un PC
fixe, ton script doit détecter l'absence de batterie et remonter un
module.data.notApplicable = true. C'est une bonne occasion d'apprendre
à gérer les cas conditionnels.
$bat = Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue
if ($bat) {
$health = @{
Charge = $bat.EstimatedChargeRemaining # %
Status = $bat.BatteryStatus # 1=Discharging, 2=AC, etc.
DesignCap = $bat.DesignCapacity
FullChargeCap= $bat.FullChargeCapacity
HealthPct = if ($bat.DesignCapacity -gt 0) {
[math]::Round(($bat.FullChargeCapacity / $bat.DesignCapacity) * 100, 1)
} else { $null }
}
}
$plan = (powercfg /getactivescheme) -match 'Power Scheme GUID' |
ForEach-Object { ($_ -split ':')[1].Trim() }
primaryMetric : pourcentage de santé batterie (FullChargeCapacity / DesignCapacity)primaryLabel : "% santé batterie"summary : "Batterie 78 % de santé, charge 64 %, mode 'Équilibré'"Une jauge ronde de la santé (différente de la charge !) avec code couleur : vert > 80 %, orange 60-80 %, rouge < 60 %. Une seconde jauge plus petite pour la charge instantanée. Badge du plan d'alimentation actif.
Get-CimInstance Win32_Battery retourne $null. Tester
avec if ($bat) avant de lire les propriétés.BatteryStatus est un code numérique (1-11). Documenter une table
de correspondance dans le code PowerShell ou côté PHP.display)$gpu = Get-CimInstance Win32_VideoController |
Select-Object Name, AdapterRAM, DriverVersion, VideoModeDescription,
CurrentRefreshRate,
CurrentHorizontalResolution, CurrentVerticalResolution
# Les vrais détails écran sont dans une classe WMI à part
$monitors = Get-CimInstance WmiMonitorID -Namespace root\wmi -ErrorAction SilentlyContinue |
ForEach-Object {
[PSCustomObject]@{
Manufacturer = -join ($_.ManufacturerName | Where-Object {$_ -ne 0} | ForEach-Object {[char]$_})
Model = -join ($_.UserFriendlyName | Where-Object {$_ -ne 0} | ForEach-Object {[char]$_})
YearOfMfg = $_.YearOfManufacture
}
}
primaryMetric : nombre d'écrans branchésprimaryLabel : "écrans détectés"summary : "GPU NVIDIA T1000, 2 écrans (Dell 2021, BenQ 2019)"WmiMonitorID peut échouer sur certaines machines (surtout les
écrans connectés via USB ou DisplayLink). Toujours -ErrorAction SilentlyContinue.-join.events)# Derniers événements critiques / erreurs du journal Système (7 derniers jours)
$errors = Get-WinEvent -FilterHashtable @{
LogName = 'System'
Level = 1,2 # 1 = Critical, 2 = Error
StartTime = (Get-Date).AddDays(-7)
} -MaxEvents 30 -ErrorAction SilentlyContinue |
Select-Object TimeCreated, ProviderName, Id, LevelDisplayName,
@{N='Message'; E={
$_.Message.Substring(0, [math]::Min(120, $_.Message.Length))
}}
# Compter les démarrages des 30 derniers jours (ID 6005 = "Event log started")
$boots = (Get-WinEvent -FilterHashtable @{
LogName = 'System'; Id = 6005; StartTime = (Get-Date).AddDays(-30)
} -ErrorAction SilentlyContinue | Measure-Object).Count
primaryMetric : nombre d'erreurs critiques sur 7 joursprimaryLabel : "erreurs (7j)"summary : "4 erreurs critiques cette semaine, 12 redémarrages ce mois"Un PC qui redémarre 30 fois en un mois a un problème (matériel ? mise à jour ratée ?). Bon sujet pour parler des niveaux de log (Information / Warning / Error / Critical) et de la différence entre les journaux System, Application et Security.
Tableau chronologique des 10 derniers événements critiques, avec une icône par niveau, l'ID, et un extrait du message (tronqué). Un compteur d'erreurs par jour sur les 7 derniers jours en mini-graphe.
-FilterHashtable est beaucoup plus rapide que Where-Object
après-coup. Toujours filtrer à la source.encryption)# Get-BitLockerVolume peut nécessiter l'élévation selon les volumes
$bl = Get-BitLockerVolume -ErrorAction SilentlyContinue |
Select-Object MountPoint, VolumeStatus, ProtectionStatus,
EncryptionMethod, EncryptionPercentage,
@{N='HasKeyProtector'; E={ $_.KeyProtector.Count -gt 0 }}
# Si pas de BitLocker disponible, le tableau est vide
$summary = if ($bl) {
$chiffres = ($bl | Where-Object ProtectionStatus -eq 'On').Count
$total = $bl.Count
"$chiffres/$total volumes chiffrés"
} else {
"BitLocker non disponible ou droits insuffisants"
}
primaryMetric : pourcentage de volumes chiffrésprimaryLabel : "% volumes chiffrés"summary : "C: chiffré (XtsAes128), D: non chiffré"Pour chaque volume, un cadenas vert (chiffré) ou rouge (en clair), la
méthode de chiffrement, et la barre de progression du chiffrement en
cours (EncryptionPercentage).
-ErrorAction SilentlyContinue et gérer
le cas "non applicable" comme dans le module 12.wifi)⚠️ Important — RGPD / vie privée : on collecte la liste des réseaux mémorisés, pas les mots de passe. C'est une excellente occasion de discuter en classe de la différence entre données techniques et données personnelles, et de ce qu'on collecte parce qu'on peut vs ce qu'on a le droit de collecter.
# Profils Wi-Fi mémorisés (sans clés)
$rawProfiles = netsh wlan show profiles
$profiles = $rawProfiles | Select-String 'Profil' | ForEach-Object {
($_ -split ':')[1].Trim()
} | Where-Object { $_ }
# Réseau actuellement connecté
$current = Get-NetConnectionProfile |
Where-Object IPv4Connectivity -eq 'Internet' |
Select-Object Name, InterfaceAlias, NetworkCategory
# Pour chaque profil mémorisé, type d'authentification (sans la clé)
$details = $profiles | ForEach-Object {
$p = $_
$info = netsh wlan show profile name="$p" key=clear # 'clear' demande la clé...
# ...mais on ne la lit volontairement PAS — on prend juste l'authentif
[PSCustomObject]@{
Name = $p
Auth = ($info | Select-String 'Authentification' | Select-Object -First 1).ToString().Trim()
}
}
primaryMetric : nombre de profils Wi-Fi enregistrésprimaryLabel : "réseaux mémorisés"summary : "12 réseaux mémorisés, connecté à 'CEPES-Eleves' (WPA2)"Un portable avec 50 réseaux mémorisés (cafés, gares, hôtels, écoles…) raconte où il est passé. Discussion sécurité : les profils ouverts ou en WEP représentent des risques (man-in-the-middle). Discussion RGPD : qui a le droit de voir cette liste ? Le service IT de l'école ? Un employeur ?
Liste des réseaux mémorisés avec un badge couleur par type d'authentification (vert = WPA3/WPA2, orange = WEP, rouge = ouvert). Mise en évidence du réseau actuellement connecté.
netsh wlan show profiles est localisé : le mot
"Profil" en français devient "Profile" en anglais. À détecter ou à
documenter pour la classe.key=clear existe. À
expliquer aux élèves : "on peut, donc on doit se demander si on doit".Dans public/pc.php, ajoute juste avant le footer :
<?php if (!empty($rapport['module']['name'])):
$modName = $rapport['module']['name'];
$modFile = __DIR__ . "/modules/$modName.php";
if (is_file($modFile)) {
require $modFile;
}
?>
<?php endif; ?>
Et tu crées public/modules/network.php (et un pour chaque module).
Chaque fichier reçoit $rapport['module']['data'] et l'affiche à sa façon.
💡 Pourquoi un fichier par module ? C'est le pattern « plugin ». Chaque élève maintient son fichier, sans toucher au reste. Pas de conflit Git, pas de cassage mutuel.
Très bon prompt :
Je travaille sur le module "usb" d'un inventaire informatique en PHP/ PowerShell. Voici les commandes PowerShell qui me donnent les périphériques USB connectés : [colle le bloc]. J'aimerais 3 propositions de visualisation HTML (Bootstrap 5, dark mode) pour la fiche PC, qui mettent en évidence le nombre par classe et les périphériques inconnus. Pas de code, juste les idées + ce qu'elles racontent à l'utilisateur.
Mauvais prompt :
Fais-moi le module USB en entier.
Comprends pourquoi : avec le mauvais prompt, ton voisin pourrait livrer exactement le même rendu que toi. Avec le bon, tu fais un choix de design qui te distingue.
Une fois tout intégré, le dashboard montre pour chaque PC :
summary du module spécifique,Et un élève peut comparer son PC à celui de toute la classe. C'est ça, un inventaire de parc.
module.name, module.summary, module.stats, module.data.summary du module pour chaque PC.| Piège | Solution |
|---|---|
Get-ScheduledTask lent ou bloqué par l'antivirus |
Limiter avec -TaskPath '\' |
Get-LocalGroupMember plante avec un message bizarre |
-ErrorAction SilentlyContinue |
Get-NetFirewallRule sans filtre prend 30 s |
Toujours filtrer dès l'appel (-Direction, -Enabled True, -First N) |
Win32_Battery est null sur un PC fixe |
Tester if ($bat) avant de lire ses propriétés ; remonter notApplicable |
Get-BitLockerVolume n'existe pas sur Windows Home |
-ErrorAction SilentlyContinue et gérer l'absence |
Le parsing de netsh casse selon la langue de Windows |
Documenter pour la classe, ou détecter la locale avant |
| Tu casses la fiche PC pour les autres élèves | Module dans un fichier séparé + is_file avant require |
| Données sensibles dans le JSON (mot de passe, clé Wi-Fi, token) | Ne jamais envoyer — vérifier avant l'envoi ; principe RGPD |
Dernière étape : sécuriser l'API. Pourquoi ? Parce qu'aujourd'hui, n'importe qui sur le réseau de l'école peut envoyer des rapports au nom de n'importe quel PC. On verra le token, et pourquoi on en a besoin.