From 69e00defccadae5fd07b0a63e09e0839e5e25654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Laporte?= Date: Fri, 30 Oct 2026 17:15:22 +0100 Subject: [PATCH] =?UTF-8?q?Ajoute=20le=20d=C3=A9ploiement=20WAPT=20par=20G?= =?UTF-8?q?PO=20et=20son=20guide=20d'utilisation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Deploy-WaptAgentGpo.ps1 | 696 ++++++++++++++++++++++++++++++++++++++++ README.md | 91 +++++- 2 files changed, 786 insertions(+), 1 deletion(-) create mode 100644 Deploy-WaptAgentGpo.ps1 diff --git a/Deploy-WaptAgentGpo.ps1 b/Deploy-WaptAgentGpo.ps1 new file mode 100644 index 0000000..bbceab0 --- /dev/null +++ b/Deploy-WaptAgentGpo.ps1 @@ -0,0 +1,696 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Crée (ou met à jour) une GPO qui déploie l'agent WAPT via une tâche planifiée. + Entièrement automatisé : seule l'adresse IP du serveur WAPT est demandée. + +.COMMANDE + .\Deploy-WaptAgentGpo.ps1 -WaptServerIP 10.0.0.10 -TargetOU "OU=Postes,DC=mydomain,DC=lan" + +.DESCRIPTION + Reproduit la méthode "GPO avec une tâche planifiée" de la documentation WAPT 2.6 + (section 1.1.2.2), sans aucune question pendant l'exécution. + + Informations récupérées automatiquement : + - domaine et contrôleur de domaine (PDC) : depuis l'Active Directory ; + - nom FQDN du serveur WAPT : depuis son certificat HTTPS, sinon le DNS inverse ; + - hash SHA256 de waptagent.exe : calculé sur le fichier du dossier du script, + puis comparé à la copie publiée par le serveur WAPT quand elle est accessible ; + - version minimale (--minversion) : version de fichier de waptagent.exe ; + - OU cible : le conteneur par défaut des ordinateurs du domaine s'il s'agit d'une OU + (redirection redircmp), sinon la GPO est créée sans liaison. + + Étapes : + 1. contrôles préalables (modules, fichiers, droits d'écriture) ; + 2. serveur WAPT : joignabilité HTTPS, nom FQDN, résolution DNS ; + 3. domaine et contrôleur de domaine ; + 4. waptagent.exe : hash, version, comparaison avec le serveur ; + 5. waptdeploy.exe : fichier local, sinon téléchargé depuis le serveur (signature vérifiée) ; + 6. copie des deux exécutables dans NETLOGON ; + 7. GPO : préférences Fichiers et Tâche planifiée, extensions côté client, version ; + 8. liaison à l'OU. + + Le module GroupPolicy ne propose aucune cmdlet pour les préférences Fichiers et + Tâches planifiées : le script écrit donc directement les fichiers XML dans SYSVOL, + au même format que la console GPMC. + + Le script est rejouable. Relancez-le après chaque régénération de waptagent.exe : + il recopie le fichier dans NETLOGON et met à jour le hash dans la tâche planifiée. + + Codes retour : 0 = succès, 2 = succès avec avertissements (GPO non liée, DNS du serveur + manquant, hash non comparé au serveur...), 1 = erreur. Un journal est écrit dans le + sous-dossier "logs" du dossier du script. + +.PARAMETER WaptServerIP + Adresse IPv4 du serveur WAPT. Seul paramètre obligatoire. + +.PARAMETER SourceFolder + Dossier contenant waptagent.exe (et waptdeploy.exe). Par défaut : dossier du script. + +.PARAMETER MinVersion + Force la version minimale de l'agent si elle ne peut pas être lue dans waptagent.exe. + +.PARAMETER TargetOU + Force l'OU de liaison (DN). Par défaut : conteneur des ordinateurs s'il s'agit d'une OU. + +.PARAMETER LocalFolder + Dossier de destination sur les postes. C:\Windows\SystemTemp (défaut, conforme à + la doc) existe sur Windows 11 et sur Windows 10 à partir de la 20H2. + +.EXAMPLE + .\Deploy-WaptAgentGpo.ps1 -WaptServerIP 10.0.0.10 + +.EXAMPLE + .\Deploy-WaptAgentGpo.ps1 -WaptServerIP 10.0.0.10 -TargetOU "OU=Postes,DC=mydomain,DC=lan" + +.NOTES + Comitari - lab WAPT + Prérequis : + - exécution sur un contrôleur de domaine ou un poste du domaine avec RSAT + (modules GroupPolicy et ActiveDirectory) ; + - compte autorisé à créer des GPO (et à les lier sur l'OU cible) ; + - waptagent.exe (généré depuis la console WAPT) dans le dossier du script ; + waptdeploy.exe dans le même dossier, ou téléchargeable depuis le serveur WAPT. + Source : https://www.wapt.fr/fr/doc-2.6/wapt-deploy-agent.html#gpo-with-a-scheduled-task +#> +[CmdletBinding()] +param( + [Parameter(Mandatory = $true, Position = 0)] + [ValidateScript({ + $ip = $null + if ([System.Net.IPAddress]::TryParse($_, [ref]$ip) -and $ip.AddressFamily -eq 'InterNetwork') { $true } + else { throw "'$_' n'est pas une adresse IPv4 valide." } + })] + [string]$WaptServerIP, + + [string]$SourceFolder = $PSScriptRoot, + + [string]$MinVersion, + + [string]$TargetOU, + + [string]$GpoName = 'WAPT-Deploiement-Agent', + + [string]$TaskName = 'WAPT - waptdeploy', + + [string]$LocalFolder = 'C:\Windows\SystemTemp', + + [ValidateRange(1, 120)] + [int]$WaitMinutes = 15 +) + +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' # accélère fortement Invoke-WebRequest sous PowerShell 5.1 + +# --------------------------------------------------------------------------- +# Constantes +# --------------------------------------------------------------------------- +# Group Policy Preferences (identifiants fixes définis par Microsoft) +$CSE_CORE = '{00000000-0000-0000-0000-000000000000}' +$CSE_FILES = '{7150F9BF-48AD-4DA4-A49C-29EF4A8369BA}' +$TOOL_FILES = '{3BAE7E51-E3F4-41D0-853D-9BB9FD47605F}' +$CSE_SCHEDTASKS = '{AADCED64-746C-4633-A97C-D61349046527}' +$TOOL_SCHEDTASKS = '{CAB54552-DEEA-4691-817E-ED4A4D1AFC72}' + +$VERSION_PATTERN = '^\d+(\.\d+){1,3}$' +$script:WarningCount = 0 +$script:CertError = $null +$TOTAL_STEPS = 8 + +# Emplacements publiés par le serveur WAPT (doc 2.6) +$AGENT_URL_PATHS = @('/wapt/waptagent.exe', '/wapt/waptagent/waptagent.exe') +$DEPLOY_URL_PATHS = @('/wapt/waptagent/waptdeploy.exe', '/wapt/waptdeploy.exe') + +# --------------------------------------------------------------------------- +# Fonctions utilitaires : affichage, chemins, XML +# --------------------------------------------------------------------------- +function Write-Step([int]$Number, [string]$Text) { + Write-Host '' + Write-Host "[$Number/$TOTAL_STEPS] $Text" -ForegroundColor Cyan +} + +function Write-Ok([string]$Text) { Write-Host " OK $Text" -ForegroundColor Green } +function Write-Info([string]$Text) { Write-Host " ... $Text" } +function Write-Warn([string]$Text) { + $script:WarningCount++ + Write-Host " /!\ $Text" -ForegroundColor Yellow +} + +function New-GppUid { + '{' + [guid]::NewGuid().ToString().ToUpper() + '}' +} + +function Join-LocalPath([string]$Folder, [string]$File) { + # Chemin Windows côté poste client : simple concaténation, sans vérifier le lecteur + $Folder.TrimEnd('\') + '\' + $File +} + +function ConvertTo-XmlText([string]$Text) { + [System.Security.SecurityElement]::Escape($Text) +} + +function Write-Utf8File([string]$Path, [string]$Content) { + $full = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) + $dir = Split-Path -Parent $full + if (-not (Test-Path -LiteralPath $dir)) { + New-Item -ItemType Directory -Path $dir -Force | Out-Null + } + [System.IO.File]::WriteAllText($full, $Content, (New-Object System.Text.UTF8Encoding($false))) +} + +function New-FilesXml { + param([string]$NetlogonUnc, [string]$LocalFolder) + + $now = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + $deploySrc = ConvertTo-XmlText "$NetlogonUnc\waptdeploy.exe" + $agentSrc = ConvertTo-XmlText "$NetlogonUnc\waptagent.exe" + $deployDst = ConvertTo-XmlText (Join-LocalPath $LocalFolder 'waptdeploy.exe') + $agentDst = ConvertTo-XmlText (Join-LocalPath $LocalFolder 'waptagent.exe') + + # action R = Remplacer, U = Mettre à jour (image = icône associée dans GPMC) + @" + + + + + + + +"@ +} + +function New-ScheduledTasksXml { + param([string]$TaskName, [string]$Command, [string]$Arguments, [string]$Author) + + $now = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + $start = Get-Date -Format "yyyy-MM-dd'T'HH:mm:00" + $name = ConvertTo-XmlText $TaskName + $cmd = ConvertTo-XmlText $Command + $args_ = ConvertTo-XmlText $Arguments + $auth = ConvertTo-XmlText $Author + + # Onglet Général : compte S-1-5-18, exécuter même si l'utilisateur n'est pas connecté, + # privilèges les plus élevés. + # Onglet Déclencheurs : quotidien, répéter toutes les heures pendant 1 jour, + # arrêter si la tâche dure plus de 2 heures, activé. + # Onglet Paramètres : seule "Exécuter la tâche dès que possible si un démarrage + # planifié est manqué" est cochée. + @" + + + + + + + $auth + Deploiement de l'agent WAPT via waptdeploy.exe (GPO tache planifiee) + + + + S-1-5-18 + S4U + HighestAvailable + + + + + PT10M + PT1H + false + false + + IgnoreNew + false + false + false + true + false + true + false + PT0S + 7 + + + + + PT1H + P1D + false + + $start + PT2H + true + + 1 + + + + + + $cmd + $args_ + + + + + + +"@ +} + +function Merge-ExtensionNames { + # Fusionne les extensions existantes de la GPO avec celles à ajouter. + # Format AD : [{CSE}{Outil}{Outil}][{CSE}{Outil}]..., trié par GUID. + param([string]$Existing, [hashtable]$ToAdd) + + $map = @{} + if ($Existing) { + foreach ($block in [regex]::Matches($Existing, '\[([^\]]+)\]')) { + $guids = @([regex]::Matches($block.Groups[1].Value, '\{[0-9A-Fa-f\-]{36}\}') | + ForEach-Object { $_.Value.ToUpper() }) + if ($guids.Count -eq 0) { continue } + if (-not $map.ContainsKey($guids[0])) { $map[$guids[0]] = @() } + $map[$guids[0]] += @($guids | Select-Object -Skip 1) + } + } + foreach ($cse in $ToAdd.Keys) { + $key = $cse.ToUpper() + if (-not $map.ContainsKey($key)) { $map[$key] = @() } + $map[$key] += @($ToAdd[$cse] | ForEach-Object { $_.ToUpper() }) + } + + $result = '' + foreach ($cse in ($map.Keys | Sort-Object)) { + $tools = @($map[$cse] | Sort-Object -Unique) + $result += '[' + $cse + ($tools -join '') + ']' + } + $result +} + +# --------------------------------------------------------------------------- +# Fonctions utilitaires : réseau et fichiers WAPT +# --------------------------------------------------------------------------- +function Test-TcpPort([string]$HostName, [int]$Port, [int]$TimeoutMs = 5000) { + $tcp = New-Object System.Net.Sockets.TcpClient + try { + $tcp.ConnectAsync($HostName, $Port).Wait($TimeoutMs) -and $tcp.Connected + } catch { + $false + } finally { + $tcp.Dispose() + } +} + +function Get-ServerCertificateName([string]$Ip, [int]$Port = 443) { + # Lit le nom DNS du certificat HTTPS du serveur WAPT (FQDN saisi lors du postconf). + # Le certificat n'est pas validé ici : on lit seulement le nom qu'il contient. + $tcp = New-Object System.Net.Sockets.TcpClient + try { + if (-not $tcp.ConnectAsync($Ip, $Port).Wait(5000)) { return $null } + $callback = [System.Net.Security.RemoteCertificateValidationCallback] { $true } + $ssl = New-Object System.Net.Security.SslStream($tcp.GetStream(), $false, $callback) + try { + # TLS 1.2 explicite : sous PowerShell 5.1, la valeur par défaut (SSL3/TLS 1.0) + # est refusée par le nginx du serveur WAPT. + $ssl.AuthenticateAsClient($Ip, $null, [System.Security.Authentication.SslProtocols]::Tls12, $false) + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($ssl.RemoteCertificate) + $name = $cert.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::DnsName, $false) + if ($name -and $name -ne $Ip) { return $name.TrimEnd('.').ToLower() } + } finally { + $ssl.Dispose() + } + } catch { + $script:CertError = $_.Exception.GetBaseException().Message + } finally { + $tcp.Dispose() + } + $null +} + +function Get-ReverseDnsName([string]$Ip) { + $ptr = $null + try { + $ptr = Resolve-DnsName -Name $Ip -Type PTR -DnsOnly -ErrorAction SilentlyContinue | + Where-Object { $_.Type -eq 'PTR' } | Select-Object -First 1 + } catch { } + if ($ptr) { return $ptr.NameHost.TrimEnd('.').ToLower() } + $null +} + +function Test-ForwardDns([string]$Fqdn, [string]$Ip) { + $ips = @() + try { + $ips = @(Resolve-DnsName -Name $Fqdn -Type A -DnsOnly -ErrorAction SilentlyContinue | + Where-Object { $_.Type -eq 'A' } | ForEach-Object { $_.IPAddress }) + } catch { } + $ips -contains $Ip +} + +function Set-RelaxedTls([bool]$Enabled) { + # PowerShell 5.1 : accepte temporairement le certificat auto-signé du serveur WAPT. + # (PowerShell 7 utilise -SkipCertificateCheck, voir Invoke-WaptDownload.) + if ($PSVersionTable.PSVersion.Major -ge 6) { return } + if (-not ('WaptRelaxedTls' -as [type])) { + Add-Type -TypeDefinition @" +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +public static class WaptRelaxedTls { + private static bool Accept(object s, X509Certificate c, X509Chain ch, SslPolicyErrors e) { return true; } + public static void Enable() { ServicePointManager.ServerCertificateValidationCallback = Accept; } + public static void Disable() { ServicePointManager.ServerCertificateValidationCallback = null; } +} +"@ + } + if ($Enabled) { [WaptRelaxedTls]::Enable() } else { [WaptRelaxedTls]::Disable() } +} + +function Invoke-WaptDownload { + # Télécharge le premier fichier disponible parmi les URL proposées. + # Essai avec validation du certificat, puis sans (certificat auto-signé du postconf). + param([string]$BaseUrl, [string[]]$Paths, [string]$OutFile) + + foreach ($path in $Paths) { + $url = $BaseUrl + $path + foreach ($relaxed in @($false, $true)) { + try { + $params = @{ Uri = $url; OutFile = $OutFile; UseBasicParsing = $true; TimeoutSec = 300 } + if ($relaxed) { + if ($PSVersionTable.PSVersion.Major -ge 6) { $params.SkipCertificateCheck = $true } + else { Set-RelaxedTls $true } + } + Invoke-WebRequest @params + return [pscustomobject]@{ Url = $url; Relaxed = $relaxed } + } catch { + Write-Verbose "Échec $url (certificat ignoré : $relaxed) : $($_.Exception.Message)" + } finally { + if ($relaxed) { Set-RelaxedTls $false } + } + } + } + $null +} + +function Get-AgentVersion([string]$Path) { + $info = (Get-Item -LiteralPath $Path).VersionInfo + foreach ($candidate in @($info.FileVersion, $info.ProductVersion)) { + if (-not $candidate) { continue } + $v = ($candidate -replace ',', '.') -replace '\s', '' + if ($v -match $VERSION_PATTERN -and $v -notmatch '^0(\.0)*$') { return $v } + } + $null +} + +function Test-TranquilSignature([string]$Path) { + $sig = Get-AuthenticodeSignature -LiteralPath $Path + $subject = if ($sig.SignerCertificate) { $sig.SignerCertificate.Subject } else { '(aucun)' } + [pscustomobject]@{ + Valid = ($sig.Status -eq 'Valid' -and $subject -match 'Tranquil') + Status = $sig.Status + Signer = $subject + } +} + +# --------------------------------------------------------------------------- +# Déroulé principal +# --------------------------------------------------------------------------- +function Invoke-Deployment { + + # 1. Contrôles préalables ----------------------------------------------------------- + Write-Step 1 'Contrôles préalables' + + foreach ($module in 'GroupPolicy', 'ActiveDirectory') { + if (-not (Get-Module -ListAvailable -Name $module)) { + throw "Module $module absent. Lancez le script sur un DC ou installez les outils RSAT." + } + Import-Module $module + } + Write-Ok 'Modules GroupPolicy et ActiveDirectory chargés' + + $agentSource = Join-Path $SourceFolder 'waptagent.exe' + if (-not (Test-Path -LiteralPath $agentSource -PathType Leaf)) { + throw "waptagent.exe introuvable dans $SourceFolder. Générez l'agent depuis la console WAPT et copiez-le à côté du script." + } + Write-Ok "waptagent.exe présent dans $SourceFolder" + + $setupFiles = @(Get-ChildItem -LiteralPath $SourceFolder -Filter 'waptsetup*.exe' -ErrorAction SilentlyContinue) + if ($setupFiles) { + Write-Info "$($setupFiles[0].Name) présent : il installe la console et n'est pas utilisé par cette GPO." + } + + if ([Net.ServicePointManager]::SecurityProtocol -notmatch 'Tls12') { + [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + } + + # 2. Serveur WAPT ---------------------------------------------------------------------- + Write-Step 2 "Serveur WAPT $WaptServerIP" + + if (-not (Test-TcpPort -HostName $WaptServerIP -Port 443)) { + throw "Le serveur $WaptServerIP ne répond pas sur le port 443. Vérifiez l'IP, le service nginx et le pare-feu." + } + Write-Ok 'Port 443 joignable' + + $fqdn = Get-ServerCertificateName -Ip $WaptServerIP + $fqdnSource = 'certificat HTTPS' + if (-not $fqdn) { + $fqdn = Get-ReverseDnsName -Ip $WaptServerIP + $fqdnSource = 'DNS inverse' + } + + $downloadHost = $WaptServerIP + if ($fqdn) { + Write-Ok "Nom du serveur : $fqdn (source : $fqdnSource)" + if (Test-ForwardDns -Fqdn $fqdn -Ip $WaptServerIP) { + Write-Ok "$fqdn est résolu vers $WaptServerIP par le DNS du domaine" + $downloadHost = $fqdn + } else { + Write-Warn "$fqdn n'est pas résolu vers $WaptServerIP par le DNS du domaine." + Write-Warn "Les agents contactent le serveur par ce nom : créez l'enregistrement A avant de déployer." + } + } else { + Write-Warn 'Nom du serveur introuvable (ni certificat lisible, ni DNS inverse).' + if ($script:CertError) { Write-Warn "Lecture du certificat : $($script:CertError)" } + Write-Warn 'Vérifiez que le nom du serveur saisi lors de la génération de l''agent est résolu par le DNS du domaine.' + } + $baseUrl = "https://$downloadHost" + + # 3. Domaine et contrôleur de domaine ---------------------------------------------------- + Write-Step 3 'Domaine et contrôleur de domaine' + + $domain = Get-ADDomain + $dnsRoot = $domain.DNSRoot + $dc = $domain.PDCEmulator # un seul DC pour toutes les écritures : pas d'attente de réplication + Write-Ok "Domaine : $dnsRoot" + Write-Ok "Contrôleur utilisé : $dc (PDC)" + + # 4. waptagent.exe : hash, version, comparaison avec le serveur ---------------------------- + Write-Step 4 'waptagent.exe' + + $agentHash = (Get-FileHash -LiteralPath $agentSource -Algorithm SHA256).Hash.ToLower() + Write-Ok "SHA256 : $agentHash" + + if (-not $MinVersion) { + $MinVersion = Get-AgentVersion -Path $agentSource + if (-not $MinVersion) { + throw 'Version illisible dans waptagent.exe. Relancez avec -MinVersion (ex. -MinVersion 2.6.1.17654).' + } + Write-Ok "Version minimale (lue dans waptagent.exe) : $MinVersion" + } else { + if ($MinVersion -notmatch $VERSION_PATTERN) { throw "Version invalide : $MinVersion" } + Write-Ok "Version minimale (forcée) : $MinVersion" + } + + $serverCopy = Join-Path ([System.IO.Path]::GetTempPath()) ("waptagent_server_{0}.exe" -f [guid]::NewGuid().ToString('N')) + try { + Write-Info "Téléchargement de la copie publiée par le serveur pour comparaison..." + $dl = Invoke-WaptDownload -BaseUrl $baseUrl -Paths $AGENT_URL_PATHS -OutFile $serverCopy + if ($dl) { + if ($dl.Relaxed) { Write-Info 'Certificat du serveur non approuvé (normal avec le certificat auto-signé du postconf).' } + $serverHash = (Get-FileHash -LiteralPath $serverCopy -Algorithm SHA256).Hash.ToLower() + if ($serverHash -ne $agentHash) { + throw ("Le waptagent.exe du dossier ne correspond pas à celui publié par le serveur ($($dl.Url)).`n" + + " Local : $agentHash`n Serveur : $serverHash`n" + + "Copiez la dernière version générée de waptagent.exe à côté du script, puis relancez.") + } + Write-Ok "Identique au fichier publié par le serveur ($($dl.Url))" + } else { + Write-Warn 'Copie du serveur non téléchargeable : hash calculé sur le fichier local uniquement.' + Write-Warn 'Assurez-vous que waptagent.exe est bien la dernière version générée depuis la console.' + } + } finally { + Remove-Item -LiteralPath $serverCopy -Force -ErrorAction SilentlyContinue + } + + # 5. waptdeploy.exe ------------------------------------------------------------------------- + Write-Step 5 'waptdeploy.exe' + + $deploySource = Join-Path $SourceFolder 'waptdeploy.exe' + if (Test-Path -LiteralPath $deploySource -PathType Leaf) { + Write-Ok "Fichier local utilisé : $deploySource" + $sig = Test-TranquilSignature -Path $deploySource + if ($sig.Valid) { Write-Ok "Signature valide : $($sig.Signer)" } + else { Write-Warn "Signature non vérifiée (statut : $($sig.Status), signataire : $($sig.Signer))." } + } else { + Write-Info 'Absent du dossier : téléchargement depuis le serveur WAPT...' + $dl = Invoke-WaptDownload -BaseUrl $baseUrl -Paths $DEPLOY_URL_PATHS -OutFile $deploySource + if (-not $dl) { + throw "waptdeploy.exe absent du dossier et non téléchargeable depuis $baseUrl. Récupérez-le sur la page d'accueil du serveur WAPT." + } + $sig = Test-TranquilSignature -Path $deploySource + if (-not $sig.Valid) { + Remove-Item -LiteralPath $deploySource -Force -ErrorAction SilentlyContinue + throw "waptdeploy.exe téléchargé rejeté : signature Authenticode Tranquil IT non valide (statut : $($sig.Status), signataire : $($sig.Signer))." + } + Write-Ok "Téléchargé depuis $($dl.Url), signature valide : $($sig.Signer)" + } + + # 6. Copie dans NETLOGON ---------------------------------------------------------------------- + Write-Step 6 'Copie dans NETLOGON' + + $netlogonDc = "\\$dc\NETLOGON" # écriture sur le PDC + $netlogonUnc = "\\$dnsRoot\NETLOGON" # chemin lu par les postes (comme dans la doc) + + Copy-Item -LiteralPath $deploySource -Destination (Join-Path $netlogonDc 'waptdeploy.exe') -Force + Copy-Item -LiteralPath $agentSource -Destination (Join-Path $netlogonDc 'waptagent.exe') -Force + + $copiedHash = (Get-FileHash -LiteralPath (Join-Path $netlogonDc 'waptagent.exe') -Algorithm SHA256).Hash.ToLower() + if ($copiedHash -ne $agentHash) { throw 'La copie de waptagent.exe dans NETLOGON est corrompue (hash différent).' } + Write-Ok "waptdeploy.exe et waptagent.exe copiés dans $netlogonDc (hash vérifié)" + + # 7. GPO ---------------------------------------------------------------------------------------- + Write-Step 7 "GPO $GpoName" + + $gpo = Get-GPO -Name $GpoName -Server $dc -ErrorAction SilentlyContinue + if ($gpo) { + Write-Ok 'GPO existante : mise à jour' + } else { + $gpo = New-GPO -Name $GpoName -Server $dc -Comment 'Deploiement agent WAPT par tache planifiee (doc WAPT 2.6, 1.1.2.2)' + Write-Ok 'GPO créée' + } + + $gpoId = '{' + $gpo.Id.ToString().ToUpper() + '}' + $gpoPath = "\\$dc\SYSVOL\$dnsRoot\Policies\$gpoId" + + # Paramètres ordinateur uniquement : on désactive la partie utilisateur + $gpo.GpoStatus = 'UserSettingsDisabled' + + $command = Join-LocalPath $LocalFolder 'waptdeploy.exe' + $arguments = "--hash=$agentHash --minversion=$MinVersion --wait=$WaitMinutes --waptsetupurl=$(Join-LocalPath $LocalFolder 'waptagent.exe')" + + Write-Utf8File -Path "$gpoPath\Machine\Preferences\Files\Files.xml" ` + -Content (New-FilesXml -NetlogonUnc $netlogonUnc -LocalFolder $LocalFolder) + Write-Utf8File -Path "$gpoPath\Machine\Preferences\ScheduledTasks\ScheduledTasks.xml" ` + -Content (New-ScheduledTasksXml -TaskName $TaskName -Command $command -Arguments $arguments ` + -Author "$env:USERDOMAIN\$env:USERNAME") + Write-Ok 'Préférences Fichiers et Tâche planifiée écrites' + + $adGpo = Get-ADObject -Server $dc ` + -Identity "CN=$gpoId,CN=Policies,CN=System,$($domain.DistinguishedName)" ` + -Properties versionNumber, gPCMachineExtensionNames + + $extensions = Merge-ExtensionNames -Existing $adGpo.gPCMachineExtensionNames -ToAdd @{ + $CSE_CORE = @($TOOL_FILES, $TOOL_SCHEDTASKS) + $CSE_FILES = @($TOOL_FILES) + $CSE_SCHEDTASKS = @($TOOL_SCHEDTASKS) + } + + # versionNumber : 16 bits de poids fort = utilisateur, 16 bits de poids faible = ordinateur + $version = [int]$adGpo.versionNumber + $newVersion = (($version -shr 16) -shl 16) + (($version -band 0xFFFF) + 1) + + Set-ADObject -Server $dc -Identity $adGpo -Replace @{ + gPCMachineExtensionNames = $extensions + versionNumber = $newVersion + } + + $gptIni = Join-Path $gpoPath 'GPT.INI' + $ini = @(Get-Content -LiteralPath $gptIni) + if ($ini -match '^Version=') { + $ini = $ini -replace '^Version=\d+', "Version=$newVersion" + } else { + $ini += "Version=$newVersion" + } + Set-Content -LiteralPath $gptIni -Value $ini -Encoding ASCII + Write-Ok "Extensions déclarées, version $version -> $newVersion" + + # 8. Liaison à l'OU -------------------------------------------------------------------------------- + Write-Step 8 'Liaison' + + $ou = $TargetOU + if (-not $ou) { + if ($domain.ComputersContainer -match '^OU=') { + $ou = $domain.ComputersContainer + Write-Info "OU par défaut des ordinateurs du domaine : $ou" + } + } + + $linkedTo = $null + if (-not $ou) { + Write-Warn "Le conteneur des ordinateurs ($($domain.ComputersContainer)) n'est pas une OU : une GPO ne peut pas y être liée." + Write-Warn 'GPO créée sans liaison. Relancez avec -TargetOU "OU=...", ou liez-la dans GPMC.' + } else { + Get-ADOrganizationalUnit -Identity $ou -Server $dc | Out-Null # échoue si l'OU n'existe pas + $already = (Get-GPInheritance -Target $ou -Server $dc).GpoLinks | Where-Object { $_.GpoId -eq $gpo.Id } + if ($already) { + Write-Ok "Déjà liée à $ou" + $linkedTo = $ou + } else { + try { + New-GPLink -Guid $gpo.Id -Target $ou -LinkEnabled Yes -Server $dc | Out-Null + Write-Ok "Liée à $ou" + $linkedTo = $ou + } catch { + Write-Warn "Liaison refusée sur $ou : $($_.Exception.Message)" + Write-Warn 'Le compte peut créer des GPO mais pas les lier sur cette OU (droit gPLink). Liez-la dans GPMC.' + } + } + } + + # Récapitulatif -------------------------------------------------------------------------------------- + Write-Host '' + Write-Host '=== Récapitulatif ===' -ForegroundColor Cyan + Write-Host "Serveur WAPT : $WaptServerIP$(if ($fqdn) { " ($fqdn)" })" + Write-Host "Domaine / DC : $dnsRoot / $dc" + Write-Host "GPO : $GpoName $gpoId" + Write-Host "Liaison : $(if ($linkedTo) { $linkedTo } else { '(aucune)' })" + Write-Host "Tâche : $TaskName" + Write-Host "Commande : $command $arguments" + Write-Host '' + Write-Host 'Vérification sur un poste de l''OU (PowerShell administrateur) :' -ForegroundColor Cyan + Write-Host ' gpupdate /force' + Write-Host " Get-ScheduledTask -TaskName '$TaskName' | Format-List TaskName, State, Principal" + Write-Host " Start-ScheduledTask -TaskName '$TaskName'" +} + +# --------------------------------------------------------------------------- +# Point d'entrée (ignoré si le script est chargé en dot-sourcing pour des tests) +# --------------------------------------------------------------------------- +if ($MyInvocation.InvocationName -eq '.') { return } + +$logDir = Join-Path $SourceFolder 'logs' +$logFile = Join-Path $logDir ("Deploy-WaptAgentGpo_{0}.log" -f (Get-Date -Format 'yyyyMMdd_HHmmss')) +New-Item -ItemType Directory -Path $logDir -Force | Out-Null +Start-Transcript -LiteralPath $logFile | Out-Null + +$exitCode = 0 +try { + Invoke-Deployment + Write-Host '' + if ($script:WarningCount -gt 0) { + $exitCode = 2 + Write-Host "Terminé avec $($script:WarningCount) avertissement(s) : relisez les lignes /!\ ci-dessus." -ForegroundColor Yellow + } else { + Write-Host 'Terminé avec succès.' -ForegroundColor Green + } +} catch { + $exitCode = 1 + Write-Host '' + Write-Host "ÉCHEC : $($_.Exception.Message)" -ForegroundColor Red + Write-Host "Ligne $($_.InvocationInfo.ScriptLineNumber) : $($_.InvocationInfo.Line.Trim())" -ForegroundColor DarkGray +} finally { + Write-Host "Journal : $logFile" + Stop-Transcript | Out-Null +} +exit $exitCode diff --git a/README.md b/README.md index c55e3bb..39b3998 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,92 @@ # comi-deploy-waptagentgpo -créé une gpo de tache planifié pour pousser l'installation de l'agent wapt \ No newline at end of file +Crée ou met à jour une GPO de déploiement de l'agent WAPT par tâche planifiée. + +## Avant de lancer le script + +Sur un contrôleur de domaine ou un poste du domaine équipé des outils RSAT : + +1. Ouvrir **Windows PowerShell 5.1 avec élévation**, avec un compte disposant des droits de création/modification des GPO et d'écriture dans NETLOGON et SYSVOL. Pour la liaison, le compte doit aussi pouvoir lier une GPO à l'OU cible. +2. Placer **`waptagent.exe`, généré depuis la console WAPT**, dans le même dossier que `Deploy-WaptAgentGpo.ps1`. Un fichier `waptsetup*.exe` ne le remplace pas. +3. Placer également `waptdeploy.exe` dans ce dossier, ou laisser le script le télécharger depuis le serveur WAPT. +4. Se placer dans le dossier du script. Les modules **`GroupPolicy`** et **`ActiveDirectory`** doivent être disponibles et le serveur WAPT accessible en HTTPS sur le port 443. + +Dans les commandes suivantes, remplacer `10.0.0.10` par l'adresse IPv4 du serveur WAPT et le DN de l'OU par celui de votre domaine. + +## Possibilité 1 — Créer la GPO et la lier à une OU + +**Commande à lancer :** + +```powershell +.\Deploy-WaptAgentGpo.ps1 -WaptServerIP 10.0.0.10 -TargetOU "OU=Postes,DC=mydomain,DC=lan" +``` + +Le script crée ou met à jour la GPO **`WAPT-Deploiement-Agent`**, puis crée un lien activé sur l'OU indiquée si ce lien n'existe pas déjà. L'OU doit exister et contenir les ordinateurs à cibler, directement ou par héritage. + +Si la création du lien est refusée, la GPO reste créée ou mise à jour ; le script signale un avertissement. La liaison peut alors être effectuée dans la console **Gestion de stratégie de groupe (GPMC)** avec un compte disposant des droits nécessaires. + +## Possibilité 2 — Créer la GPO sans préciser d'OU + +**Commande à lancer :** + +```powershell +.\Deploy-WaptAgentGpo.ps1 -WaptServerIP 10.0.0.10 +``` + +**Cette commande crée la GPO sans liaison si le conteneur par défaut des ordinateurs est un conteneur tel que `CN=Computers`.** La GPO peut ensuite être liée manuellement à l'OU souhaitée dans GPMC : clic droit sur l'OU, puis **Lier un objet de stratégie de groupe existant**, et sélectionner `WAPT-Deploiement-Agent`. + +**Attention : si le conteneur par défaut des ordinateurs a été redirigé vers une OU, le script lie automatiquement la GPO à cette OU, même sans `-TargetOU`.** La version actuelle ne propose pas de paramètre pour forcer une création sans liaison. Elle ne supprime pas non plus les liens d'une GPO existante. + +Pour connaître le conteneur par défaut avant de choisir cette commande : + +```powershell +(Get-ADDomain).ComputersContainer +``` + +Vérifier la ligne **`Liaison`** du récapitulatif final : `(aucune)` indique qu'aucune liaison n'a été effectuée ou reconnue par cette exécution. Cela ne constitue pas un inventaire des liens préexistants sur d'autres OU. + +## Ce que configure le script + +- Détecte le domaine et utilise son contrôleur PDC pour les écritures. +- Recherche le nom du serveur WAPT dans son certificat HTTPS, puis dans le DNS inverse, et vérifie sa résolution DNS. +- Calcule le SHA256 et lit la version de `waptagent.exe`. Il compare le hash avec la copie du serveur lorsqu'elle est téléchargeable ; une différence bloque l'exécution. +- Utilise le `waptdeploy.exe` local ou le télécharge. Un téléchargement est rejeté si sa signature Authenticode Tranquil IT n'est pas valide ; une signature locale non vérifiée produit un avertissement. +- Copie les deux exécutables dans NETLOGON et configure leur copie sur les postes vers **`C:\Windows\SystemTemp`** par défaut. +- Écrit les préférences Fichiers et Tâche planifiée de la GPO dans SYSVOL, met à jour les extensions et la version de la GPO, et désactive sa partie utilisateur. +- Configure la tâche **`WAPT - waptdeploy`** sous **SYSTEM**, avec les privilèges les plus élevés, un déclenchement quotidien et une répétition toutes les heures pendant une journée. + +La tâche appelle `waptdeploy.exe` avec le hash de l'agent, sa version minimale, un délai `--wait` de 15 minutes par défaut et le chemin local de `waptagent.exe`. + +## Paramètres utiles + +| Paramètre | Utilisation | +| --- | --- | +| `-WaptServerIP` | Adresse IPv4 du serveur WAPT ; obligatoire. | +| `-TargetOU` | DN de l'OU à laquelle lier la GPO ; comportement automatique si absent, décrit ci-dessus. | +| `-SourceFolder` | Dossier des exécutables ; dossier du script par défaut. Les journaux y sont également créés. | +| `-MinVersion` | Version minimale à imposer ; lue dans `waptagent.exe` par défaut. À préciser si elle ne peut pas être lue. | +| `-GpoName` | Nom de la GPO ; `WAPT-Deploiement-Agent` par défaut. | +| `-TaskName` | Nom de la tâche ; `WAPT - waptdeploy` par défaut. | +| `-LocalFolder` | Dossier de destination sur les postes ; `C:\Windows\SystemTemp` par défaut. Vérifier sa présence sur les postes ciblés. | +| `-WaitMinutes` | Valeur transmise à `--wait` ; de 1 à 120, avec 15 par défaut. | + +## Journaux et vérification + +Le journal est enregistré dans **`logs\Deploy-WaptAgentGpo_yyyyMMdd_HHmmss.log`**, sous le dossier des exécutables. + +| Code retour | Signification | +| --- | --- | +| `0` | Succès sans avertissement. | +| `2` | Exécution terminée avec avertissements : GPO non liée, DNS à vérifier, comparaison avec le serveur impossible, etc. Relire le journal. | +| `1` | Erreur ; consulter le journal pour identifier les étapes déjà réalisées. | + +Après liaison et réplication de la GPO, vérifier sur un poste ciblé, dans PowerShell avec élévation : + +```powershell +gpupdate /force +Get-ScheduledTask -TaskName 'WAPT - waptdeploy' | Format-List TaskName, State, Principal +``` + +Adapter le nom de la tâche si `-TaskName` a été utilisé. + +Après chaque régénération de `waptagent.exe`, remplacer le fichier source et relancer la commande choisie pour actualiser les exécutables et le hash dans la GPO. Une GPO portant déjà le nom demandé est mise à jour.