Ajoute le déploiement WAPT par GPO et son guide d'utilisation

This commit is contained in:
slaporte committed 2026-10-30 17:15:22 +01:00
1 parent 7b10be05d1
commit 69e00defcc
2 files changed
+786 -1

No files matched your search

+696
View File
@@ -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)
@"
<?xml version="1.0" encoding="utf-8"?>
<Files clsid="{215B2E53-57CE-475c-80FE-9EEC14635851}">
<File clsid="{50BE44C8-567A-4ed1-B1D0-9234FE1F38AF}" name="waptdeploy.exe" status="waptdeploy.exe" image="1" changed="$now" uid="$(New-GppUid)">
<Properties action="R" fromPath="$deploySrc" targetPath="$deployDst" readOnly="1" archive="1" hidden="0" suppress="0"/>
</File>
<File clsid="{50BE44C8-567A-4ed1-B1D0-9234FE1F38AF}" name="waptagent.exe" status="waptagent.exe" image="2" changed="$now" uid="$(New-GppUid)">
<Properties action="U" fromPath="$agentSrc" targetPath="$agentDst" readOnly="1" archive="1" hidden="0" suppress="0"/>
</File>
</Files>
"@
}
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.
@"
<?xml version="1.0" encoding="utf-8"?>
<ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}">
<TaskV2 clsid="{D8896631-B747-47a7-84A6-C155337F3BC8}" name="$name" image="1" changed="$now" uid="$(New-GppUid)" userContext="0" removePolicy="0">
<Properties action="R" name="$name" runAs="S-1-5-18" logonType="S4U">
<Task version="1.3">
<RegistrationInfo>
<Author>$auth</Author>
<Description>Deploiement de l'agent WAPT via waptdeploy.exe (GPO tache planifiee)</Description>
</RegistrationInfo>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId>
<LogonType>S4U</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<IdleSettings>
<Duration>PT10M</Duration>
<WaitTimeout>PT1H</WaitTimeout>
<StopOnIdleEnd>false</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>false</AllowHardTerminate>
<StartWhenAvailable>true</StartWhenAvailable>
<AllowStartOnDemand>false</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Triggers>
<CalendarTrigger>
<Repetition>
<Interval>PT1H</Interval>
<Duration>P1D</Duration>
<StopAtDurationEnd>false</StopAtDurationEnd>
</Repetition>
<StartBoundary>$start</StartBoundary>
<ExecutionTimeLimit>PT2H</ExecutionTimeLimit>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
</Triggers>
<Actions Context="Author">
<Exec>
<Command>$cmd</Command>
<Arguments>$args_</Arguments>
</Exec>
</Actions>
</Task>
</Properties>
</TaskV2>
</ScheduledTasks>
"@
}
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
+90 -1
View File
@@ -1,3 +1,92 @@
# comi-deploy-waptagentgpo
créé une gpo de tache planifié pour pousser l'installation de l'agent wapt
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.