#Requires -Version 5.1 # ===================================================================================================== # gentle-audit.ps1 - audit de securite en LECTURE SEULE pour Windows 10/11 (PowerShell 5.1 et 7+) # Source : https://gentledesigns.studio/securite-numerique/scripts (jumeau macOS/Linux : gentle-audit.sh) # (c) 2026 gentledesigns.studio - Tous droits reserves. Lecture et execution libres ; reutilisation sur demande. # # Ce que fait ce script, en clair : # - il LIT des reglages de Windows (mises a jour, antivirus, pare-feu, comptes, chiffrement...) ; # - il ne modifie RIEN : aucun reglage, aucun service, aucun fichier systeme ; # - il n'utilise PAS le reseau : rien n'est telecharge, rien n'est envoye ; # - il n'a pas besoin des droits administrateur (sans eux, certains points restent "inconnus") ; # - sa seule ecriture : un rapport JSON dans le dossier courant, # gentle-audit-windows-AAAAMMJJ-HHMMSS.json, que tu peux lire avant d'en faire quoi que ce soit. # # Il n'utilise JAMAIS : manage-bde -protectors (afficherait ta cle de recuperation BitLocker), # netsh wlan ... key=clear (afficherait tes mots de passe Wi-Fi). Aucun secret dans le rapport. # Independant de la langue de Windows : objets PowerShell et SID, jamais de texte traduit analyse. # # Lancement conseille (telecharger -> lire -> lancer), depuis le dossier ou tu veux le rapport : # powershell -NoProfile -ExecutionPolicy Bypass -File .\gentle-audit.ps1 # (-ExecutionPolicy Bypass ne vaut que pour CE lancement : ta politique d'execution ne change pas.) # # Codes de sortie (identiques dans gentle-audit.sh) : # 0 = rapport ecrit # 1 = erreur inattendue (rien n'a ete modifie) # 2 = systeme non pris en charge (ce script est pour Windows ; macOS/Linux : gentle-audit.sh) # 3 = rapport impossible a ecrire (dossier courant en lecture seule, ou fichier deja present) # # Fichier en ASCII strict (PowerShell 5.1 lit un .ps1 sans BOM en ANSI) : les accents des messages # sont ecrits en sequences \u00e9 et decodes a l'execution par la fonction U. # ===================================================================================================== $ErrorActionPreference = 'Stop' function U([string]$s) { return [regex]::Unescape($s) } $script:GaChecks = New-Object System.Collections.Generic.List[object] $NoRights = U 'non v\u00e9rifi\u00e9 sans droits administrateur' function Add-GaCheck([string]$id, [string]$title, [string]$result, [string]$detail) { $script:GaChecks.Add([pscustomobject]@{ id = $id; title = $title; result = $result; detail = $detail }) } # Chaque check est isole : une commande absente ou refusee donne "unknown", jamais un arret. function Invoke-GaCheck([string]$id, [string]$title, [scriptblock]$body) { try { $r = & $body Add-GaCheck $id $title $r[0] $r[1] } catch { Add-GaCheck $id $title 'unknown' ((U 'lecture impossible (commande indisponible ou refus\u00e9e) : ') + $_.Exception.GetType().FullName) } } function Format-GaJson([string]$s) { $sb = New-Object System.Text.StringBuilder [void]$sb.Append('"') foreach ($ch in $s.ToCharArray()) { $code = [int]$ch if ($code -eq 34) { [void]$sb.Append('\"') } elseif ($code -eq 92) { [void]$sb.Append('\\') } elseif ($code -eq 10) { [void]$sb.Append('\n') } elseif ($code -eq 13) { [void]$sb.Append('\r') } elseif ($code -eq 9) { [void]$sb.Append('\t') } elseif ($code -lt 32) { [void]$sb.Append(('\u{0:x4}' -f $code)) } else { [void]$sb.Append($ch) } } [void]$sb.Append('"') return $sb.ToString() } function Get-GaSid($principal) { # PowerShell 7 renvoie parfois des objets deserialises (compatibilite Windows PowerShell). if ($null -eq $principal -or $null -eq $principal.SID) { return '' } if ($principal.SID.Value) { return [string]$principal.SID.Value } return [string]$principal.SID } function Get-GaRegValue([string]$path, [string]$name) { $item = Get-ItemProperty -LiteralPath $path -ErrorAction SilentlyContinue if ($null -eq $item) { return $null } $prop = $item.PSObject.Properties[$name] if ($null -eq $prop) { return $null } return $prop.Value } # ---------------------------------------------------------------------------------- 0. Systeme if ($PSVersionTable.PSEdition -eq 'Core' -and -not $IsWindows) { Write-Host (U 'Ce script est pour Windows. Sur macOS ou Linux, utilise gentle-audit.sh.') exit 2 } try { $Banner = @( (U 'gentle-audit : audit de s\u00e9curit\u00e9 en LECTURE SEULE (gentledesigns.studio)'), (U 'Ce script ne modifie rien : aucun r\u00e9glage, aucun service, aucun fichier syst\u00e8me.'), (U "Il n'utilise pas le r\u00e9seau : rien n'est t\u00e9l\u00e9charg\u00e9, rien n'est envoy\u00e9."), (U "Il n'a pas besoin des droits administrateur : sans eux, certains points restent \u00ab inconnus \u00bb."), (U 'Seule \u00e9criture : un rapport JSON dans le dossier courant.'), (U "Ce rapport s'importe sur la page Scripts du site : il est lu dans ton navigateur, jamais envoy\u00e9."), (U 'Codes de sortie : 0 rapport \u00e9crit, 1 erreur inattendue, 2 syst\u00e8me non pris en charge, 3 rapport impossible \u00e0 \u00e9crire.') ) Write-Host '' foreach ($line in $Banner) { Write-Host $line } Write-Host '' $cwd = Get-Location if ($cwd.Provider.Name -ne 'FileSystem') { Write-Host (U "Le dossier courant n'est pas un dossier de fichiers : place-toi dans un dossier (cd) puis relance.") exit 3 } $IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.SecurityIdentifier]'S-1-5-32-544') $Today = (Get-Date).Date $cv = Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $Build = [int]$cv.CurrentBuild $Edition = [string]$cv.EditionID $DisplayVersion = [string](Get-GaRegValue 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' 'DisplayVersion') $Ubr = Get-GaRegValue 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' 'UBR' # ProductName dit encore "Windows 10" sur Windows 11 (mesure 16/10/2026) : seul le build fait foi. $WinName = if ($Build -ge 22000) { 'Windows 11' } else { 'Windows 10' } $OsVersion = "$WinName $DisplayVersion (build $Build.$Ubr, $Edition)" $mp = $null try { $mp = Get-MpComputerStatus -ErrorAction Stop } catch { $mp = $null } $DefenderOn = ($null -ne $mp -and $mp.AMServiceEnabled -eq $true -and $mp.AntivirusEnabled -eq $true) $AdminSids = $null try { $AdminSids = @(Get-LocalGroupMember -SID 'S-1-5-32-544' -ErrorAction Stop | ForEach-Object { Get-GaSid $_ }) } catch { $AdminSids = $null } $MySid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value # ------------------------------------------------------------------------------ 1. Checks Invoke-GaCheck 'L0-WIN-1' (U 'Mises \u00e0 jour Windows install\u00e9es r\u00e9cemment') { $last = $null try { $last = (New-Object -ComObject Microsoft.Update.AutoUpdate).Results.LastInstallationSuccessDate } catch { $last = $null } $src = 'Windows Update' if ($null -eq $last -or ([datetime]$last).Year -lt 2000) { $hf = Get-HotFix | Where-Object { $_.InstalledOn } | Sort-Object InstalledOn -Descending | Select-Object -First 1 if ($null -eq $hf) { return @('unknown', ((U 'date de la derni\u00e8re mise \u00e0 jour illisible'))) } $last = $hf.InstalledOn; $src = (U 'correctifs install\u00e9s') } $age = [int][math]::Floor(($Today - ([datetime]$last).Date).TotalDays) $d = (U 'derni\u00e8re installation r\u00e9ussie il y a ') + $age + (U ' jour(s) (source : ') + $src + ')' if ($age -le 35) { return @('ok', ($d)) } return @('ko', ($d + (U ' : plus d''un mois sans correctif, v\u00e9rifie que les mises \u00e0 jour ne sont pas en pause'))) } Invoke-GaCheck 'L0-WIN-2' (U 'Version de Windows encore support\u00e9e') { # Table au 16/10/2026, source : Microsoft Learn, "Windows 11 - release information" (MAJ 13/10/2026). # Cle = build (independant de la langue). home = Famille/Pro/Pro Education/Pro for Workstations. $table = @{ 22631 = @{ v = '23H2'; home = '2025-11-11'; ent = '2026-11-10' } 26100 = @{ v = '24H2'; home = '2026-10-13'; ent = '2027-10-12' } 26200 = @{ v = '25H2'; home = '2027-10-12'; ent = '2028-10-10' } 26300 = @{ v = '26H2'; home = '2028-10-10'; ent = '2029-10-09' } 28000 = @{ v = '26H1'; home = '2028-03-14'; ent = '2029-03-13' } } $isLtsc = $Edition -match 'S$|SN$' $isEnt = $Edition -match '^(Enterprise|Education|IoTEnterprise|ServerRdsh)' if ($Build -lt 22000) { if ($isLtsc) { return @('unknown', ((U 'Windows 10 LTSC : calendrier de support propre, v\u00e9rifie la page Microsoft de ton \u00e9dition'))) } return @('ko', ((U 'Windows 10 ne re\u00e7oit plus de correctifs grand public depuis le 14/10/2025 (hors programme ESU) : passe \u00e0 Windows 11 si ton PC le permet'))) } if ($isLtsc) { if ($Build -eq 26100) { $end = if ($Edition -like 'IoT*') { '2034-10-10' } else { '2029-10-09' } if ($Today -le [datetime]$end) { return @('ok', ((U 'Windows 11 LTSC 2024 : correctifs jusqu''au ') + $end)) } return @('ko', ((U 'Windows 11 LTSC 2024 : fin des correctifs le ') + $end)) } return @('unknown', ((U '\u00e9dition LTSC : calendrier de support propre, v\u00e9rifie la page Microsoft de ton \u00e9dition'))) } $row = $table[$Build] if ($null -eq $row) { $max = ($table.Keys | Measure-Object -Maximum).Maximum if ($Build -gt $max) { return @('ok', ((U 'version plus r\u00e9cente que la table du script (build ') + $Build + ')')) } return @('ko', ((U 'cette version de Windows 11 ne re\u00e7oit plus de correctifs (build ') + $Build + (U ') : installe la derni\u00e8re version depuis Windows Update'))) } $end = if ($isEnt) { $row.ent } else { $row.home } $endDate = [datetime]::ParseExact($end, 'yyyy-MM-dd', [Globalization.CultureInfo]::InvariantCulture) if ($Today -gt $endDate) { return @('ko', ('Windows 11 ' + $row.v + (U ' : fin des correctifs le ') + $end + (U ' pour ton \u00e9dition, installe la derni\u00e8re version depuis Windows Update'))) } $d = 'Windows 11 ' + $row.v + (U ' : correctifs jusqu''au ') + $end if (($endDate - $Today).TotalDays -le 90) { $d += (U ' (bient\u00f4t : pr\u00e9vois la mise \u00e0 niveau)') } return @('ok', ($d)) } Invoke-GaCheck 'L0-WIN-3' (U 'Antivirus actif (Microsoft Defender ou \u00e9quivalent)') { if ($DefenderOn) { if ($mp.RealTimeProtectionEnabled -ne $true) { return @('ko', ((U 'Defender est install\u00e9 mais la protection en temps r\u00e9el est d\u00e9sactiv\u00e9e'))) } $age = [int]$mp.AntivirusSignatureAge if ($age -gt 7) { return @('ko', ((U 'protection en temps r\u00e9el active, mais signatures vieilles de ') + $age + ' jour(s)')) } return @('ok', ((U 'Defender actif, protection en temps r\u00e9el active, signatures de moins de ') + ($age + 1) + ' jour(s)')) } $others = @(Get-CimInstance -Namespace 'root/SecurityCenter2' -ClassName AntiVirusProduct -ErrorAction Stop | Where-Object { $_.displayName -notlike 'Windows Defender*' -and $_.displayName -notlike 'Microsoft Defender*' }) if ($others.Count -gt 0) { $names = ($others | ForEach-Object { [string]$_.displayName } | Select-Object -Unique) -join ', ' return @('unknown', ((U 'antivirus tiers d\u00e9tect\u00e9 (') + $names + (U ') : son \u00e9tat ne se lit pas de fa\u00e7on fiable, v\u00e9rifie-le dans S\u00e9curit\u00e9 Windows'))) } return @('ko', ((U 'aucun antivirus actif d\u00e9tect\u00e9'))) } Invoke-GaCheck 'L1-WIN-4' (U 'Comptes de ce PC (Invit\u00e9 et Administrateur int\u00e9gr\u00e9 d\u00e9sactiv\u00e9s)') { $users = @(Get-LocalUser -ErrorAction Stop) $byRid = @{} foreach ($u in $users) { $sid = Get-GaSid $u; if ($sid) { $byRid[$sid.Split('-')[-1]] = $u } } $enabled = @($users | Where-Object { $_.Enabled -eq $true }).Count $admins = if ($null -ne $AdminSids) { [string]$AdminSids.Count } else { '?' } $d = [string]$enabled + (U ' compte(s) local(aux) actif(s), ') + $admins + (U ' membre(s) du groupe Administrateurs') $problems = @() if ($byRid.ContainsKey('501') -and $byRid['501'].Enabled -eq $true) { $problems += (U 'compte Invit\u00e9 actif') } if ($byRid.ContainsKey('500') -and $byRid['500'].Enabled -eq $true) { $problems += (U 'compte Administrateur int\u00e9gr\u00e9 actif') } if ($problems.Count -gt 0) { return @('ko', (($problems -join ', ') + ' ; ' + $d)) } return @('ok', ($d + (U ' ; passe en revue ceux que tu ne reconnais pas'))) } Invoke-GaCheck 'L2-WIN-1' (U 'Disque syst\u00e8me chiffr\u00e9 (BitLocker / chiffrement de l''appareil)') { if (-not $IsAdmin) { return @('unknown', ($NoRights)) } $v = Get-BitLockerVolume -MountPoint $env:SystemDrive -ErrorAction Stop $ps = [string]$v.ProtectionStatus; $vs = [string]$v.VolumeStatus if ($ps -eq 'On') { return @('ok', ((U 'disque syst\u00e8me chiffr\u00e9 et prot\u00e9g\u00e9'))) } if ($vs -eq 'FullyEncrypted') { return @('ko', ((U 'disque chiffr\u00e9 mais protection suspendue : reprends-la dans les param\u00e8tres BitLocker'))) } return @('ko', ((U 'disque syst\u00e8me non chiffr\u00e9 (\u00e9tat : ') + $vs + ')')) } Invoke-GaCheck 'L2-WIN-2' (U 'D\u00e9marrage s\u00e9curis\u00e9 (Secure Boot) et puce TPM actifs') { if (-not $IsAdmin) { return @('unknown', ($NoRights)) } $sb = $false try { $sb = [bool](Confirm-SecureBootUEFI -ErrorAction Stop) } catch { return @('unknown', ((U 'Secure Boot illisible (PC non UEFI ?)'))) } $tpm = Get-Tpm -ErrorAction Stop if ($null -eq $tpm.TpmPresent) { return @('unknown', ((U '\u00e9tat de la puce TPM illisible'))) } $tpmOk = ($tpm.TpmPresent -eq $true -and $tpm.TpmReady -eq $true) if ($sb -and $tpmOk) { return @('ok', ((U 'Secure Boot actif, puce TPM pr\u00e9sente et pr\u00eate'))) } $p = @(); if (-not $sb) { $p += 'Secure Boot inactif' }; if (-not $tpmOk) { $p += (U 'TPM absente ou pas pr\u00eate') } return @('ko', (($p -join ', '))) } Invoke-GaCheck 'L2-WIN-3' (U 'Pare-feu Windows actif sur tous les profils') { $profiles = @(Get-NetFirewallProfile -ErrorAction Stop) if ($profiles.Count -eq 0) { return @('unknown', ((U 'profils du pare-feu illisibles'))) } $off = @($profiles | Where-Object { [string]$_.Enabled -ne 'True' } | ForEach-Object { [string]$_.Name }) if ($off.Count -eq 0) { return @('ok', ((U 'pare-feu actif sur les profils Domaine, Priv\u00e9 et Public'))) } return @('ko', ((U 'pare-feu d\u00e9sactiv\u00e9 sur : ') + ($off -join ', '))) } Invoke-GaCheck 'L2-WIN-4' (U 'Protection contre les falsifications de Defender') { if (-not $DefenderOn) { return @('unknown', ((U 'Defender n''est pas l''antivirus actif : r\u00e9glage sans objet ou illisible'))) } if ($mp.IsTamperProtected -eq $true) { return @('ok', ((U 'protection contre les falsifications active'))) } return @('ko', ((U 'protection contre les falsifications d\u00e9sactiv\u00e9e : un malware peut couper Defender'))) } Invoke-GaCheck 'L2-WIN-5' (U 'Contr\u00f4le de compte d''utilisateur (UAC) actif') { $key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' $lua = Get-GaRegValue $key 'EnableLUA' $cpba = Get-GaRegValue $key 'ConsentPromptBehaviorAdmin' $note = '' if ($null -ne $AdminSids) { if ($AdminSids -contains $MySid) { $note = (U ' ; ton compte est administrateur (option avanc\u00e9e : un compte standard au quotidien)') } else { $note = (U ' ; tu utilises un compte standard') } } if ($null -eq $lua) { return @('unknown', ((U 'r\u00e9glage UAC illisible'))) } if ([int]$lua -eq 0) { return @('ko', ((U 'UAC d\u00e9sactiv\u00e9 : les programmes obtiennent les droits administrateur sans te demander') + $note)) } if ($null -ne $cpba -and [int]$cpba -eq 0) { return @('ko', ((U 'UAC r\u00e9gl\u00e9 sur \u00ab ne jamais m''avertir \u00bb : \u00e9l\u00e9vation silencieuse des droits') + $note)) } return @('ok', ((U 'UAC actif et demande confirmation') + $note)) } Invoke-GaCheck 'L2-WIN-6' 'Smart App Control' { # Propriete SmartAppControlState mesuree presente le 16/10/2026 (Windows 11 25H2) : valeurs On / Off / Eval. if ($null -eq $mp) { return @('unknown', ((U '\u00e9tat de Smart App Control illisible'))) } $p = $mp.PSObject.Properties['SmartAppControlState'] if ($null -eq $p -or [string]::IsNullOrEmpty([string]$p.Value)) { return @('unknown', ((U 'Smart App Control absent de cette version de Windows'))) } switch ([string]$p.Value) { 'On' { return @('ok', ((U 'Smart App Control actif'))) } 'Off' { return @('ko', ((U 'Smart App Control d\u00e9sactiv\u00e9 (r\u00e9activable dans S\u00e9curit\u00e9 Windows si ton PC le permet)'))) } 'Eval' { return @('unknown', ((U 'Smart App Control en mode \u00e9valuation : Windows d\u00e9cidera seul de l''activer'))) } default { return @('unknown', ((U '\u00e9tat inattendu : ') + [string]$p.Value)) } } } $dg = $null try { $dg = Get-CimInstance -Namespace 'root\Microsoft\Windows\DeviceGuard' -ClassName Win32_DeviceGuard -ErrorAction Stop } catch { $dg = $null } Invoke-GaCheck 'L2-WIN-7' (U 'Int\u00e9grit\u00e9 de la m\u00e9moire (isolation du noyau)') { if ($null -eq $dg) { return @('unknown', ((U 'informations d''isolation du noyau illisibles'))) } if (@($dg.SecurityServicesRunning) -contains 2) { return @('ok', ((U 'int\u00e9grit\u00e9 de la m\u00e9moire active'))) } return @('ko', ((U 'int\u00e9grit\u00e9 de la m\u00e9moire inactive (S\u00e9curit\u00e9 Windows > S\u00e9curit\u00e9 de l''appareil > Isolation du noyau)'))) } Invoke-GaCheck 'L3-WIN-1' (U 'Profil r\u00e9seau (Public hors de chez toi)') { $profs = @(Get-NetConnectionProfile -ErrorAction Stop) if ($profs.Count -eq 0) { return @('unknown', ((U 'aucune connexion r\u00e9seau active'))) } $priv = @($profs | Where-Object { [string]$_.NetworkCategory -eq 'Private' }).Count if ($priv -eq 0) { return @('ok', ([string]$profs.Count + (U ' connexion(s) active(s), aucune en Priv\u00e9'))) } return @('unknown', ([string]$priv + (U ' connexion(s) en Priv\u00e9 : c''est normal chez toi, et c''est le bon r\u00e9glage sur ton propre r\u00e9seau. Hors de chez toi (h\u00f4tel, caf\u00e9, coworking), passe-la en Public.'))) } Invoke-GaCheck 'L3-WIN-2' (U 'Bureau \u00e0 distance d\u00e9sactiv\u00e9') { $v = Get-GaRegValue 'HKLM:\System\CurrentControlSet\Control\Terminal Server' 'fDenyTSConnections' if ($null -eq $v) { return @('unknown', ((U 'r\u00e9glage du Bureau \u00e0 distance illisible'))) } if ([int]$v -eq 1) { return @('ok', ((U 'Bureau \u00e0 distance d\u00e9sactiv\u00e9'))) } return @('ko', ((U 'Bureau \u00e0 distance activ\u00e9 : d\u00e9sactive-le si tu ne t''en sers pas'))) } Invoke-GaCheck 'L3-WIN-3' (U 'Ancien protocole SMBv1 d\u00e9sactiv\u00e9') { if (-not $IsAdmin) { return @('unknown', ($NoRights)) } $f = Get-WindowsOptionalFeature -Online -FeatureName SMB1Protocol -ErrorAction Stop if ($null -eq $f) { return @('ok', ((U 'SMBv1 absent de ce syst\u00e8me'))) } if ([string]$f.State -like 'Disabled*') { return @('ok', ((U 'SMBv1 d\u00e9sactiv\u00e9'))) } return @('ko', ((U 'SMBv1 activ\u00e9 : protocole ancien exploit\u00e9 par les vers de type WannaCry'))) } Invoke-GaCheck 'L4-WIN-2' (U 'Identifiant de publicit\u00e9 d\u00e9sactiv\u00e9') { $adv = Get-GaRegValue 'HKCU:\Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo' 'Enabled' $tel = Get-GaRegValue 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection' 'AllowTelemetry' $t = if ($null -eq $tel) { '' } elseif ([int]$tel -ge 3) { (U ' ; donn\u00e9es de diagnostic facultatives envoy\u00e9es') } else { (U ' ; donn\u00e9es de diagnostic limit\u00e9es au n\u00e9cessaire (attention : cela d\u00e9sactive Smart App Control)') } if ($null -eq $adv) { return @('unknown', ((U 'r\u00e9glage de l''identifiant de publicit\u00e9 absent') + $t)) } if ([int]$adv -eq 0) { return @('ok', ((U 'identifiant de publicit\u00e9 d\u00e9sactiv\u00e9') + $t)) } return @('ko', ((U 'identifiant de publicit\u00e9 activ\u00e9 : les applis peuvent te profiler') + $t)) } Invoke-GaCheck 'L5-WIN-1' (U 'Protection LSA (identifiants de session)') { $v = Get-GaRegValue 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' 'RunAsPPL' if ($null -eq $v) { return @('unknown', ((U 'valeur absente : Windows peut l''activer par d\u00e9faut sans l''\u00e9crire, v\u00e9rifie dans S\u00e9curit\u00e9 Windows > Isolation du noyau'))) } if ([int]$v -ge 1) { return @('ok', ((U 'protection LSA activ\u00e9e'))) } return @('ko', ((U 'protection LSA d\u00e9sactiv\u00e9e'))) } $pref = $null try { $pref = Get-MpPreference -ErrorAction Stop } catch { $pref = $null } Invoke-GaCheck 'L5-WIN-2' (U 'R\u00e8gles de r\u00e9duction de la surface d''attaque (ASR)') { if (-not $DefenderOn -or $null -eq $pref) { return @('unknown', ((U 'Defender n''est pas l''antivirus actif ou ses pr\u00e9f\u00e9rences sont illisibles'))) } $ids = @($pref.AttackSurfaceReductionRules_Ids | Where-Object { $_ }) $np = [int]$pref.EnableNetworkProtection $npTxt = if ($np -eq 1) { (U 'protection r\u00e9seau active') } elseif ($np -eq 2) { (U 'protection r\u00e9seau en audit') } else { (U 'protection r\u00e9seau inactive') } if ($ids.Count -gt 0) { return @('ok', ([string]$ids.Count + (U ' r\u00e8gle(s) ASR configur\u00e9e(s), ') + $npTxt)) } return @('ko', ((U 'aucune r\u00e8gle ASR configur\u00e9e, ') + $npTxt + (U ' (option avanc\u00e9e : commence en mode audit)'))) } Invoke-GaCheck 'L5-WIN-3' (U 'Acc\u00e8s contr\u00f4l\u00e9 aux dossiers (anti-ran\u00e7ongiciel)') { if (-not $DefenderOn -or $null -eq $pref) { return @('unknown', ((U 'Defender n''est pas l''antivirus actif ou ses pr\u00e9f\u00e9rences sont illisibles'))) } $v = [int]$pref.EnableControlledFolderAccess if ($v -eq 1 -or $v -eq 3) { return @('ok', ((U 'acc\u00e8s contr\u00f4l\u00e9 aux dossiers actif'))) } if ($v -eq 2 -or $v -eq 4) { return @('ko', ((U 'acc\u00e8s contr\u00f4l\u00e9 aux dossiers en mode audit seulement (il observe, il ne bloque pas)'))) } return @('ko', ((U 'acc\u00e8s contr\u00f4l\u00e9 aux dossiers d\u00e9sactiv\u00e9'))) } Invoke-GaCheck 'L5-WIN-4' (U 'S\u00e9curit\u00e9 par virtualisation et Credential Guard') { if ($null -eq $dg) { return @('unknown', ((U 'informations de virtualisation illisibles'))) } $run = @($dg.SecurityServicesRunning) if ($run -contains 1) { return @('ok', ((U 'Credential Guard actif'))) } $vbsOn = ([int]$dg.VirtualizationBasedSecurityStatus -eq 2) # Credential Guard n'existe pas en Famille / Pro : y renvoyer 'ko' serait un faux rouge sur # la grande majorite des PC grand public. Seules les editions Entreprise / Education peuvent # l'activer, donc elles seules mentent quand il est absent. # Test recalcule ici : le $isEnt du check de version de Windows vit dans UN AUTRE # scriptblock, il n'est pas visible d'ici. $Edition, lui, est au scope englobant. $cgCapable = $Edition -match '^(Enterprise|Education|IoTEnterprise|ServerRdsh)' if (-not $cgCapable) { if ($vbsOn) { return @('ok', ((U 'virtualisation de s\u00e9curit\u00e9 active ; Credential Guard n''existe pas sur cette \u00e9dition de Windows'))) } return @('unknown', ((U 'Credential Guard n''existe pas sur cette \u00e9dition de Windows (r\u00e9serv\u00e9 \u00e0 Entreprise et \u00c9ducation)'))) } $vbs = if ($vbsOn) { (U 'virtualisation de s\u00e9curit\u00e9 active') } else { (U 'virtualisation de s\u00e9curit\u00e9 inactive') } return @('ko', ((U 'Credential Guard inactif (') + $vbs + ')')) } Invoke-GaCheck 'L5-WIN-5' (U 'Exclusions de Defender') { if (-not $IsAdmin) { return @('unknown', ($NoRights)) } if (-not $DefenderOn -or $null -eq $pref) { return @('unknown', ((U 'Defender n''est pas l''antivirus actif ou ses pr\u00e9f\u00e9rences sont illisibles'))) } $all = @($pref.ExclusionPath) + @($pref.ExclusionProcess) + @($pref.ExclusionExtension) | Where-Object { $_ } $n = @($all).Count if ($n -eq 0) { return @('ok', ((U 'aucune exclusion'))) } return @('unknown', ([string]$n + (U ' exclusion(s) : chacune doit \u00eatre connue et voulue (S\u00e9curit\u00e9 Windows > Exclusions)'))) } # ------------------------------------------------------------------------------ 2. Rapport $stamp = (Get-Date).ToString('yyyyMMdd-HHmmss', [Globalization.CultureInfo]::InvariantCulture) $generatedAt = (Get-Date).ToString('yyyy-MM-ddTHH:mm:sszzz', [Globalization.CultureInfo]::InvariantCulture) $fileName = "gentle-audit-windows-$stamp.json" $path = Join-Path $cwd.ProviderPath $fileName $parts = New-Object System.Collections.Generic.List[string] foreach ($c in $script:GaChecks) { $parts.Add(' { "id": ' + (Format-GaJson $c.id) + ', "result": ' + (Format-GaJson $c.result) + ', "detail": ' + (Format-GaJson $c.detail) + ' }') } $nl = "`n" $json = '{' + $nl + ' "schema": 1,' + $nl + ' "os": "windows",' + $nl + ' "osVersion": ' + (Format-GaJson $OsVersion) + ',' + $nl + ' "generatedAt": ' + (Format-GaJson $generatedAt) + ',' + $nl + ' "checks": [' + $nl + ($parts -join (',' + $nl)) + $nl + ' ]' + $nl + '}' + $nl # Resume a l'ecran $nOk = 0; $nKo = 0; $nUnk = 0 Write-Host (U 'R\u00e9sultat ') -NoNewline; Write-Host "($OsVersion)" Write-Host '' foreach ($c in $script:GaChecks) { switch ($c.result) { 'ok' { $label = '[ok] '; $color = 'Green'; $nOk++ } 'ko' { $label = '[ko] '; $color = 'Red'; $nKo++ } default { $label = '[inconnu]'; $color = 'Yellow'; $nUnk++ } } Write-Host $label -ForegroundColor $color -NoNewline Write-Host (' {0,-9} {1}' -f $c.id, $c.title) Write-Host (' ' + $c.detail) } Write-Host '' Write-Host ((U 'Bilan : ') + "$nOk ok, $nKo ko, $nUnk " + (U 'inconnu(s) sur ') + $script:GaChecks.Count + (U ' points v\u00e9rifi\u00e9s.')) Write-Host (U "inconnu = le script n'a pas pu conclure (droits, outil absent, r\u00e9glage illisible) : v\u00e9rifie ce point \u00e0 la main avec le guide.") if (Test-Path -LiteralPath $path) { Write-Host ((U 'Rapport non \u00e9crit : le fichier existe d\u00e9j\u00e0 (') + $path + ')') exit 3 } try { [System.IO.File]::WriteAllText($path, $json, (New-Object System.Text.UTF8Encoding($false))) } catch { Write-Host ((U 'Rapport non \u00e9crit : dossier courant inaccessible en \u00e9criture (') + $cwd.ProviderPath + ')') exit 3 } Write-Host ((U 'Rapport \u00e9crit : ') + $path) exit 0 } catch { Write-Host ((U 'Erreur inattendue, rien n''a \u00e9t\u00e9 modifi\u00e9 : ') + $_.Exception.GetType().FullName) exit 1 }