Procédure de déploiement ZopeEdit 1.1.2 via GPO#

Référence : IA.DOCS-GPO-DEPLOY-ZopeEdit-v1.0 Auteur : christophe Tourment Date : 28 mai 2026 Cible : Windows 11 Pro Méthode : GPO Startup Script (PowerShell)


1. Contexte et objectif#

1.1 Objectif#

Déployer automatiquement le package ZopeEdit-1.1.2.msi (généré via Scalable Smart Package) sur les postes Windows 11 Pro du domaine votre.commune.be via une GPO de type Startup Script.

1.2 Pourquoi un Startup Script et pas un Logon Script ?#

Type de script

Contexte d’exécution

Droits

Installation MSI machine

Startup Script

NT AUTHORITY\SYSTEM

Administrateur local

✅ Oui

Logon Script

Utilisateur connecté

Limités

❌ Non (sauf user admin)

L’installation d’un MSI machine-wide requiert les droits SYSTEM, donc uniquement un Startup Script est viable.

1.3 Pourquoi un script PowerShell et pas l’option “Software Installation” native de la GPO ?#

L’option Computer Configuration → Software Settings → Software Installation déploie nativement les MSI, mais :

  • pas de gestion fine des versions ni des codes de retour MSI

  • pas de log centralisé exploitable par Zabbix

  • diagnostic difficile en cas d’échec

  • pas de logique d’idempotence personnalisée

Le script PowerShell offre un contrôle complet et une journalisation détaillée.


2. Prérequis#

2.1 Infrastructure#

  • ✅ Contrôleur de domaine ad.votre.commune.be opérationnel

  • ✅ Partage SYSVOL accessible en lecture pour les machines du domaine

  • ✅ GPO {Unique ID} créée et liée à l’OU contenant les postes Windows 11 cibles

2.2 Postes clients#

  • Windows 11 Pro joint au domaine votre.commune.be

  • PowerShell 5.1 (présent par défaut sur Windows 11)

  • ExecutionPolicy autorisant l’exécution de scripts (voir §4)

2.3 Fichiers#

  • ZopeEdit-1.1.2.msi (package MSI à déployer)

  • Deploy-ZopeEdit.ps1 (script de déploiement, fourni en §3)


3. Script de déploiement PowerShell#

3.1 Emplacement#

Le script et le MSI doivent être copiés ensemble dans le dossier Startup de la GPO :

\\votre.commune.be\SYSVOL\votre.commune.be\Policies\{Unique ID}\Machine\Scripts\Startup\
├── Deploy-ZopeEdit.ps1
└── ZopeEdit-1.1.2.msi

3.2 Code du script#

Fichier : Deploy-ZopeEdit.ps1

<#
.SYNOPSIS
    Déploiement du package ZopeEdit-1.1.2.msi via GPO Startup Script.

.DESCRIPTION
    Script de déploiement idempotent qui :
    - Vérifie si le MSI est déjà installé (via DisplayName + DisplayVersion)
    - Installe ou met à jour le package si nécessaire
    - Journalise les opérations dans %ProgramData%\IMIO\Logs
    - Doit s'exécuter en contexte SYSTEM (startup script GPO)

.NOTES
    Auteur      : Christophe Tourment / IMIO
    Version     : 1.0
    Cible       : Windows 11 Pro - PowerShell 5.1
#>

#Requires -Version 5.0

[CmdletBinding()]
param()

# ============================================================
# Configuration
# ============================================================
$ErrorActionPreference = 'Stop'

$MsiFileName     = 'ZopeEdit-1.1.2.msi'
$ProductName     = 'ZopeEdit'
$TargetVersion   = '1.1.2'
$LogDir          = Join-Path $env:ProgramData 'IMIO\Logs'
$LogFile         = Join-Path $LogDir 'ZopeEdit-Deploy.log'
$MsiLogFile      = Join-Path $LogDir "ZopeEdit-MsiInstall-$(Get-Date -Format 'yyyyMMdd-HHmmss').log"
$MarkerFile      = Join-Path $env:ProgramData 'IMIO\ZopeEdit.installed'

$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$MsiPath   = Join-Path $ScriptDir $MsiFileName

# ============================================================
# Fonctions
# ============================================================
function Write-Log {
    param(
        [Parameter(Mandatory)][string]$Message,
        [ValidateSet('INFO','WARN','ERROR','OK')][string]$Level = 'INFO'
    )
    $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    $line = "[$timestamp] [$Level] $Message"
    try {
        Add-Content -Path $LogFile -Value $line -Encoding UTF8 -ErrorAction Stop
    } catch {}
    Write-Verbose $line
}

function Get-InstalledProduct {
    param([Parameter(Mandatory)][string]$Name)

    $uninstallKeys = @(
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
        'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
    )

    Get-ItemProperty -Path $uninstallKeys -ErrorAction SilentlyContinue |
        Where-Object { $_.DisplayName -like "*$Name*" } |
        Select-Object DisplayName, DisplayVersion, PSChildName, UninstallString
}

function Compare-Version {
    param(
        [Parameter(Mandatory)][string]$Installed,
        [Parameter(Mandatory)][string]$Target
    )
    try {
        $vInstalled = [version]$Installed
        $vTarget    = [version]$Target
        return $vInstalled.CompareTo($vTarget)
    } catch {
        return [string]::Compare($Installed, $Target)
    }
}

# ============================================================
# Initialisation
# ============================================================
try {
    if (-not (Test-Path $LogDir)) {
        New-Item -Path $LogDir -ItemType Directory -Force | Out-Null
    }
} catch {}

Write-Log "===== Demarrage deploiement $ProductName v$TargetVersion =====" -Level INFO
Write-Log "Execute en tant que : $([Security.Principal.WindowsIdentity]::GetCurrent().Name)"
Write-Log "Machine             : $env:COMPUTERNAME"
Write-Log "Chemin du script    : $ScriptDir"
Write-Log "Chemin du MSI       : $MsiPath"

# ============================================================
# Vérifications préalables
# ============================================================
if (-not (Test-Path -LiteralPath $MsiPath)) {
    Write-Log "MSI introuvable : $MsiPath" -Level ERROR
    exit 1
}

# ============================================================
# Vérification de l'état d'installation
# ============================================================
$installed = Get-InstalledProduct -Name $ProductName

if ($installed) {
    foreach ($product in $installed) {
        Write-Log "Produit detecte : $($product.DisplayName) v$($product.DisplayVersion)" -Level INFO
    }

    $current = $installed | Select-Object -First 1

    if ($current.DisplayVersion) {
        $cmp = Compare-Version -Installed $current.DisplayVersion -Target $TargetVersion

        if ($cmp -ge 0) {
            Write-Log "Version installee ($($current.DisplayVersion)) >= version cible ($TargetVersion). Rien a faire." -Level OK
            exit 0
        } else {
            Write-Log "Version installee ($($current.DisplayVersion)) < version cible ($TargetVersion). Mise a jour requise." -Level INFO
        }
    }
} else {
    Write-Log "Aucune installation existante detectee. Installation initiale." -Level INFO
}

# ============================================================
# Installation du MSI
# ============================================================
Write-Log "Lancement de msiexec..." -Level INFO

$msiArgs = @(
    '/i'
    "`"$MsiPath`""
    '/qn'
    '/norestart'
    'REBOOT=ReallySuppress'
    "/L*V `"$MsiLogFile`""
)

Write-Log "Commande : msiexec.exe $($msiArgs -join ' ')"

try {
    $process = Start-Process -FilePath 'msiexec.exe' `
                             -ArgumentList $msiArgs `
                             -Wait -PassThru -NoNewWindow

    $exitCode = $process.ExitCode
    Write-Log "msiexec termine avec code : $exitCode"

    switch ($exitCode) {
        0 {
            Write-Log "Installation reussie." -Level OK
            try {
                "$ProductName v$TargetVersion installe le $(Get-Date -Format 'o')" |
                    Set-Content -Path $MarkerFile -Encoding UTF8
            } catch {}
            exit 0
        }
        1605 {
            Write-Log "Code 1605 : produit non trouve." -Level WARN
            exit $exitCode
        }
        1618 {
            Write-Log "Code 1618 : une autre installation MSI est en cours. Reessai au prochain demarrage." -Level WARN
            exit $exitCode
        }
        1641 {
            Write-Log "Code 1641 : installation reussie, reboot initie." -Level OK
            exit 0
        }
        3010 {
            Write-Log "Code 3010 : installation reussie, reboot requis." -Level OK
            exit 0
        }
        default {
            Write-Log "Echec installation. Consultez le log MSI : $MsiLogFile" -Level ERROR
            exit $exitCode
        }
    }
}
catch {
    Write-Log "Exception lors de l'execution de msiexec : $($_.Exception.Message)" -Level ERROR
    exit 1
}

4. Configuration de la GPO#

4.1 Ouvrir la console GPMC#

Sur un contrôleur de domaine ou un poste avec RSAT :

Démarrer → Group Policy Management

4.2 Éditer la GPO cible#

  1. Naviguer jusqu’à la GPO {Unique ID}

  2. Clic droit → Edit

4.3 Activer l’ExecutionPolicy PowerShell#

Chemin dans l’éditeur GPO :

Computer Configuration
└── Policies
    └── Administrative Templates
        └── Windows Components
            └── Windows PowerShell
                └── Turn on Script Execution

Configuration :

  • Enabled

  • Execution Policy : Allow local scripts and remote signed scripts

Note : sur les Startup Scripts, le script s’exécute depuis SYSVOL qui est considéré comme local. La policy RemoteSigned peut malgré tout bloquer selon le contexte ; Allow local scripts and remote signed scripts est le compromis sûr.

4.4 Ajouter le script PowerShell de démarrage#

Chemin dans l’éditeur GPO :

Computer Configuration
└── Policies
    └── Windows Settings
        └── Scripts (Startup/Shutdown)
            └── Startup

Étapes :

  1. Double-cliquer sur Startup

  2. Sélectionner l’onglet PowerShell Scripts (⚠️ pas l’onglet Scripts classique)

  3. Cliquer sur Show Files… → cela ouvre le dossier SYSVOL ...\Machine\Scripts\Startup\

  4. Copier Deploy-ZopeEdit.ps1 et ZopeEdit-1.1.2.msi dans ce dossier

  5. Fermer l’Explorateur

  6. Cliquer sur Add…

  7. Browse… → sélectionner Deploy-ZopeEdit.ps1

  8. Laisser le champ Script Parameters vide

  9. OK

4.5 Ordre d’exécution (optionnel)#

Si plusieurs scripts PowerShell coexistent, configurer l’option :

Run Windows PowerShell scripts first : Enabled

(Onglet PowerShell Scripts → bouton Properties en bas).

4.6 Forcer la mise à jour de la GPO#

Sur un poste client de test :

gpupdate /force

Puis redémarrer le poste — les Startup Scripts ne s’exécutent qu’au démarrage de la machine, pas lors d’un simple gpupdate.


5. Vérification du déploiement#

5.1 Vérifier l’application de la GPO sur le poste client#

gpresult /scope computer /h C:\Temp\gpo.html
Start-Process C:\Temp\gpo.html

Chercher la GPO {Unique ID} dans la section Computer ConfigurationApplied GPOs.

5.2 Vérifier l’installation du logiciel#

Get-ItemProperty `
    'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', `
    'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' |
    Where-Object DisplayName -like '*Zope*' |
    Select-Object DisplayName, DisplayVersion, Publisher, InstallDate

5.3 Consulter les logs#

Fichier

Contenu

C:\ProgramData\IMIO\Logs\ZopeEdit-Deploy.log

Journal du script PowerShell

C:\ProgramData\IMIO\Logs\ZopeEdit-MsiInstall-<timestamp>.log

Log verbeux msiexec

C:\ProgramData\IMIO\ZopeEdit.installed

Marker file (présent = installé)

5.4 Vérifier le marker file#

Get-Content C:\ProgramData\IMIO\ZopeEdit.installed

Sortie attendue :

ZopeEdit v1.1.2 installe le 2026-05-28T08:15:32.0000000+02:00

6. Diagnostic et résolution de problèmes#

6.1 Codes de retour MSI#

Code

Signification

Action

0

Installation réussie

Aucune

1603

Erreur fatale d’installation

Consulter le log MSI verbeux

1605

Produit non trouvé (pour désinstall)

Normal lors de la première installation

1618

Autre installation MSI en cours

Réessai automatique au prochain démarrage

1619

MSI introuvable ou inaccessible

Vérifier le chemin SYSVOL

1638

Version déjà installée (autre instance)

Vérifier le ProductCode du MSI

1641

Installation réussie, reboot initié

Aucune

3010

Installation réussie, reboot requis

Reboot manuel ou planifié

6.2 Le script ne s’exécute pas#

Vérifier dans l’ordre :

  1. GPO bien liée à l’OU contenant le poste :

    gpresult /r /scope computer
    
  2. Fichiers présents dans SYSVOL :

    Test-Path '\\votre.commune.be\SYSVOL\votre.commune.be\Policies\{Unique ID}\Machine\Scripts\Startup\Deploy-ZopeEdit.ps1'
    Test-Path '\\votre.commune.be\SYSVOL\votre.commune.be\Policies\{Unique ID}\Machine\Scripts\Startup\ZopeEdit-1.1.2.msi'
    
  3. Réplication SYSVOL complète entre tous les DC (DFSR opérationnel)

  4. ExecutionPolicy correctement appliquée :

    Get-ExecutionPolicy -List
    
  5. Journal Événements Windows :

    Event Viewer → Applications and Services Logs → Microsoft → Windows → GroupPolicy → Operational
    

6.3 Le MSI s’installe mais échoue#

  1. Consulter le log MSI verbeux dans C:\ProgramData\IMIO\Logs\

  2. Rechercher les chaînes Return value 3 (point d’échec) et MainEngineThread is returning (code final)

  3. Tester l’installation manuelle en local en tant qu’Administrateur :

    msiexec.exe /i "C:\Temp\ZopeEdit-1.1.2.msi" /qn /L*V "C:\Temp\manual-install.log"
    

6.4 Cas particulier : DisplayName ne contient pas “ZopeEdit”#

Adapter la variable $ProductName dans le script en fonction du DisplayName réel détecté :

Get-ItemProperty `
    'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', `
    'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' |
    Where-Object DisplayName |
    Select-Object DisplayName, DisplayVersion |
    Sort-Object DisplayName

7. Désinstallation / rollback#

7.1 Désinstallation manuelle sur un poste#

# Récupérer le ProductCode
$product = Get-ItemProperty `
    'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', `
    'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' |
    Where-Object DisplayName -like '*ZopeEdit*' |
    Select-Object -First 1

if ($product) {
    $productCode = $product.PSChildName
    Start-Process msiexec.exe -ArgumentList "/x $productCode /qn /norestart" -Wait
    Remove-Item 'C:\ProgramData\IMIO\ZopeEdit.installed' -ErrorAction SilentlyContinue
}

7.2 Désactivation du déploiement#

Pour stopper le déploiement sur de nouveaux postes :

  1. Éditer la GPO {Unique ID}

  2. Onglet PowerShell Scripts → sélectionner Deploy-ZopeEdit.ps1Remove

  3. gpupdate /force sur les DC


8. Annexes#

8.1 Arborescence finale dans SYSVOL#

\\votre.commune.be\SYSVOL\votre.commune.be\Policies\{Unique ID}\
└── Machine\
    └── Scripts\
        └── Startup\
            ├── Deploy-ZopeEdit.ps1
            └── ZopeEdit-1.1.2.msi

8.2 Arborescence finale sur le poste client#

C:\ProgramData\IMIO\
├── ZopeEdit.installed
└── Logs\
    ├── ZopeEdit-Deploy.log
    └── ZopeEdit-MsiInstall-20260528-081530.log

8.3 Références#

  • Documentation MSI Exit Codes : https://learn.microsoft.com/windows/win32/msi/error-codes

  • GPO PowerShell Startup Scripts : https://learn.microsoft.com/windows-server/administration/windows-commands/gpresult

  • Scalable Smart Package : documentation interne IMIO


Fin du document – DOC-IMIO-DEPLOY-ZopeEdit-v1.1.2