Automatiser le déploiement de postes avec winget et PowerShell — Rendre le manuel de procédure exécutable

· · winget, PowerShell, Windows, Déploiement de postes, Systèmes d'information, Automatisation, Amélioration opérationnelle, Efficacité opérationnelle

Chaque fois qu’un nouvel employé arrive, ou qu’un PC est remplacé, la personne en charge configure les postes un par un en suivant un manuel de procédure — dans les services informatiques des petites et moyennes entreprises, c’est encore aujourd’hui la scène habituelle. Le problème ne se limite pas au temps. Le travail manuel n’est pas reproductible, si bien que des ennuis du type « seul ce poste a des réglages différents » finissent par se manifester plus tard. Le manuel de procédure vieillit sans être mis à jour, et quand la personne responsable change, les détails se perdent.

Windows intègre en standard le gestionnaire de paquets winget, et l’installation d’une application s’écrit en une seule ligne. En allant plus loin avec WinGet Configuration, on peut exprimer applications et réglages dans un seul fichier YAML déclaratif (une méthode qui, au lieu d’aligner des étapes, décrit « l’état final souhaité »). Et les domaines que winget ne couvre pas (imprimantes, lecteurs réseau, réglages standards internes du registre, etc.) peuvent être complétés avec PowerShell.

Cet article présente comment remplacer un manuel de procédure de déploiement par un « fichier exécutable », avec un niveau de détail réellement exploitable en exploitation.

1. La conclusion, d’abord

  • Laissez l’installation des applications à winget. winget install prend en charge l’installation silencieuse en standard.1
  • Vous pouvez extraire la configuration d’un PC existant avec winget export. Mais cela se limite aux paquets gérés par winget, et les réglages ne sont pas inclus.2
  • Pour une approche déclarative, utilisez WinGet Configuration (winget configure). Basée sur PowerShell DSC, elle permet d’écrire applications et réglages dans un seul fichier YAML. Elle nécessite Windows 10 1809 ou ultérieur, et winget 1.6 ou ultérieur.3
  • Complétez avec PowerShell ce que winget ne couvre pas : imprimantes, lecteurs partagés, registre, fonctionnalités Windows, comptes locaux, etc.
  • Pour le piloter depuis PowerShell, il existe le module Microsoft.WinGet.Client. Il offre des applets de commande comme Install-WinGetPackage.4
  • Le contexte système (par exemple le compte SYSTEM, une exécution sous un compte distinct de l’utilisateur connecté) demande une attention particulière. Microsoft le mentionne encore comme un élément de développement à venir, la vérification avec le compte d’exécution réel est donc indispensable.3
  • Assurez toujours l’idempotence (le fait d’obtenir le même résultat quel que soit le nombre d’exécutions). Le déploiement échoue en cours de route, il doit donc pouvoir être relancé autant de fois que nécessaire.
  • Ne forcez pas les applications internes dans winget : les exécuter silencieusement via PowerShell est l’option réaliste.

2. Les bases de winget — écrire une installation sans surveillance

Voyons d’abord la forme standard pour installer de façon fiable, sans interaction.1

# Installer en spécifiant l'ID exactement (-e = correspondance exacte, --id = spécification de l'ID)
#   --silent                     : n'afficher aucune interface
#   --accept-package-agreements  : accepter les conditions d'utilisation du paquet
#   --accept-source-agreements   : accepter les conditions d'utilisation de la source
#   --scope machine              : installer pour tous les utilisateurs (paquets compatibles uniquement)
# Le backtick de continuation de ligne doit être placé en fin de ligne. Un commentaire après lui annule la continuation
winget install --id Google.Chrome -e --silent `
    --accept-package-agreements --accept-source-agreements --scope machine

# Rechercher un ID
winget search "Visual Studio Code"
winget show --id Microsoft.VisualStudioCode

Oublier --accept-* fait que l’exécution sans surveillance se bloque en attendant une acceptation. C’est le premier écueil rencontré dans l’automatisation du déploiement.

--scope machine n’est pas utilisable pour tous les paquets ; certaines applications ne peuvent être installées qu’au niveau utilisateur. Dans ce cas, configurez une exécution dans le contexte utilisateur lors de la première connexion.

La compatibilité peut se vérifier avant l’installation. winget show accepte --scope, ce qui permet de vérifier au préalable si un installeur au niveau machine est disponible.5

# Vérifier si un installeur au niveau machine existe
winget show --id Google.Chrome -e --scope machine

# À titre de comparaison, regarder aussi le niveau utilisateur
winget show --id Google.Chrome -e --scope user

S’il n’existe aucun installeur pour la portée spécifiée, un message l’indique. Vérifier ainsi l’ensemble des paquets cibles avant d’écrire "scope": "machine" dans le fichier de configuration du déploiement permet d’éviter de découvrir l’échec seulement en cours d’exécution sans surveillance.

3. Extraire la configuration du PC actuel — export / import

Si vous disposez déjà d’un « PC standard » préparé, vous pouvez transformer sa configuration en fichier.2

# Depuis le PC standard, écrire la liste des paquets installés dans un fichier JSON
winget export --output D:\kitting\apps.json --include-versions

# Restaurer sur le nouveau PC
winget import --import-file D:\kitting\apps.json `
    --accept-package-agreements --accept-source-agreements --ignore-unavailable

Le JSON produit a la structure suivante (les valeurs sont des exemples).2

{
  "CreationDate": "2026-07-25T10:40:00.000-00:00",
  "Sources": [
    {
      "Packages": [
        { "PackageIdentifier": "Google.Chrome", "Version": "126.0.6478.127" },
        { "PackageIdentifier": "Microsoft.VisualStudioCode", "Version": "1.101.2" },
        { "PackageIdentifier": "7zip.7zip", "Version": "24.09" }
      ],
      "SourceDetails": {
        "Argument": "https://cdn.winget.microsoft.com/cache",
        "Identifier": "Microsoft.Winget.Source_8wekyb3d8bbwe",
        "Name": "winget",
        "Type": "Microsoft.PreIndexed.Package"
      }
    }
  ],
  "WinGetVersion": "1.9.25180"
}

Comme on le voit, le contenu est une liste d’identifiants de paquets et de versions (sans --include-versions, Version n’apparaît pas, et import installe la dernière version).2 Autrement dit, tout ce que fait winget import est « installer le paquet portant cet identifiant » : ni les réglages des applications, ni les applications installées autrement que par winget ne figurent la moindre trace ici. Ouvrir réellement le JSON permet de constater concrètement cette limite.

Il est important de bien comprendre cette limite.

Ce qui est possible Ce qui ne l’est pas
Reproduire la liste des paquets gérés par winget Le transfert des réglages internes aux applications
Fixer les versions (--include-versions) Les applications installées autrement que par winget, les applications internes
Ignorer les paquets absents (--ignore-unavailable) L’activation de licences, l’état de connexion

Autrement dit, winget import est un point de départ, et non l’ensemble du déploiement. La question centrale est de savoir comment combler le reste.

4. Écrire de façon déclarative — WinGet Configuration

winget configure est un mécanisme qui déclare en YAML l’état final souhaité, puis l’applique via PowerShell DSC.3 Comparé à un script procédural, il présente trois avantages.

  • Ne rien faire si l’état souhaité est déjà atteint (idempotence)
  • Exprimer dans un seul fichier à la fois l’installation des applications et les réglages de Windows et des applications
  • En cas d’échec en cours de route, il suffit de relancer le même fichier
# kitting.winget ── déclare l'état souhaité du poste standard
properties:
  configurationVersion: 0.2.0
  resources:
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: chrome
      directives:
        description: Installer Google Chrome
        allowPrerelease: true
      settings:
        id: Google.Chrome
        source: winget

    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: vscode
      directives:
        description: Installer Visual Studio Code
      settings:
        id: Microsoft.VisualStudioCode
        source: winget

    - resource: Microsoft.Windows.Developer/DeveloperMode
      id: devmode
      directives:
        description: Activer le mode développeur (postes de développement uniquement)
        allowPrerelease: true
      settings:
        Ensure: Present
# Vérifier le contenu avant application (affiche ce qui sera exécuté)
winget configure show --file D:\kitting\kitting.winget

# Appliquer
winget configure --file D:\kitting\kitting.winget --accept-configuration-agreements

Les prérequis sont Windows 10 version 1809 (build 17763) ou ultérieur, ou Windows 11, ainsi que winget 1.6.2631 ou ultérieur.3 Dans un environnement où subsistent des postes plus anciens, la configuration via PowerShell du chapitre suivant est plus fiable.

4.1. Comment lire directivesallowPrerelease ne concerne pas l’application

directives est ce qui prête toujours à confusion quand on assemble du YAML à partir d’exemples divers. Ce que l’on y écrit, ce sont des instructions relatives à la gestion de la ressource, et non la spécification de l’application à installer (cela relève de settings).

  • description … le texte descriptif affiché à l’exécution. Comme la sortie de winget configure show devient directement lisible grâce à lui, écrivez-le systématiquement
  • allowPrerelease … une instruction qui autorise l’utilisation d’une version préliminaire du module PowerShell fournissant la ressource DSC. Cela ne signifie pas « installer une version préliminaire (bêta) de l’application ».

Cette distinction a des conséquences concrètes. allowPrerelease se raisonne au niveau du module, donc une divergence de valeur entre des éléments utilisant le même resource: provient le plus souvent d’un assemblage incohérent d’exemples disparates. Dans l’exemple ci-dessus, sur les deux entrées Microsoft.WinGet.DSC/WinGetPackage, seule celle de chrome le porte, mais il n’y a aucune raison de le différencier selon l’application : harmonisez-le pour un même type de ressource. Le seul critère de décision est « une version stable du module fournissant cette ressource est-elle publiée » : retirez-le si une version stable existe, et ne l’ajoutez que lorsque le module n’a encore qu’une préversion.

4.2. La répartition des rôles entre WinGet Configuration et un script PowerShell maison

« Si WinGet Configuration est idempotent, pourquoi écrire du code idempotent maison au chapitre suivant ? » est une question légitime. La réponse tient à ce que le périmètre couvert diffère : ce n’est pas une relation de substitution. Voici un récapitulatif par exigence.

Exigence WinGet Configuration PowerShell maison (chapitre 5) Choix en pratique
Installation d’applications publiées sur winget ◎ Ressource standard WinGetPackage ○ Faisable mais idempotence à gérer soi-même Configuration
Réglages Windows (mode développeur, etc.) ○ Si une ressource DSC correspondante existe Configuration si la ressource existe
Valeur de registre arbitraire (réglage standard interne) △ Selon la ressource disponible ◎ Peut tout écrire PowerShell
Application interne sur dossier partagé (MSI/EXE) △ Hors du périmètre des ressources standards PowerShell
Lecteur réseau, imprimante partagée × Traitement par utilisateur, à la connexion PowerShell (phase non élevée)
Variations par service/modèle de machine △ Séparer les fichiers YAML ◎ Il suffit de substituer le JSON de configuration PowerShell
OS cible ancien (moins de 1809, winget moins de 1.6) × Ne remplit pas les prérequis PowerShell
Renvoyer via le code de sortie si un redémarrage est requis pour l’outil de distribution △ Difficile à contrôler ◎ Décidable soi-même PowerShell

En conclusion, « ce qui entre dans le cadre de winget va vers Configuration, ce qui n’y entre pas va vers PowerShell ». Cependant, si vous utilisez les deux, ne placez la liste des applications que d’un seul côté. Écrire les paquets dans le YAML de Configuration, puis à nouveau dans wingetPackages du kitting.config.json du chapitre 5, fait qu’en corrigeant l’un, l’autre reste obsolète. Si vous adoptez une configuration combinant les deux, il est plus pratique de retirer le traitement d’installation d’applications du côté PowerShell, et de ne conserver dans le JSON que les éléments que Configuration ne peut pas traiter, comme les valeurs de registre ou les imprimantes partagées. À l’inverse, vouloir tout couvrir uniquement avec Configuration en cherchant sans fin des ressources correspondantes est un travail qui, souvent, ne vaut pas l’investissement.

5. Compléter avec PowerShell — ce que winget ne fait pas

Dans un déploiement réel, la plus grande part de l’effort ne porte en fait pas sur l’installation des applications. C’est là que PowerShell entre en jeu. Le point clé est d’écrire systématiquement sous la forme « ne rien faire si c’est déjà fait ».

Ce chapitre étant long, présentons d’abord la vue d’ensemble. Il n’intervient que trois fichiers : la structure consiste à avoir un seul JSON qui détermine « quoi installer », lu par deux scripts s’exécutant avec des privilèges différents.

Phase utilisateur (non élevée. à chaque connexion de l'utilisateur)Phase administrateur (élevée. une fois par poste)relit la configuration distribuéeInvoke-KsUserKitting.ps1Registre HKCU /lecteur réseau /imprimante partagéeInvoke-KsKitting.ps1Dossiers / registre HKLM /fonctionnalités Windows / paquets winget /applications internes (MSI, EXE)Distribue le JSON de configuration sous ProgramDatakitting.config.jsonDéfinition de « comment ce poste doit être »

Figure 1 : les trois composants du déploiement. La même définition est lue par deux phases aux privilèges différents

Le principe de répartition tient en une seule règle. Les réglages qui s’appliquent à toute la machine relèvent de la phase administrateur ; les réglages créés par utilisateur relèvent de la phase utilisateur. Mélanger les deux provoque toujours, comme on le verra plus loin, l’accident du type « le lecteur qui aurait dû être affecté n’apparaît pas ».

Le code qui suit étant long, montrons d’abord où se trouve quoi. Le script administrateur est découpé par des commentaires --- N. ---, ne lisez que les parties nécessaires.

Section Contenu Public concerné
Début (prétraitement) Lecture du JSON de configuration, distribution vers C:\ProgramData, démarrage du journal via Start-Transcript Tout le monde
--- 1. Dossiers standards --- Création de dossiers. L’exemple le plus simple d’écriture idempotente Ceux qui veulent comprendre l’écriture idempotente
--- 2. Réglages de registre --- Écriture dans HKLM. Le point clé est de comparer non seulement la valeur mais aussi le type Ceux qui veulent distribuer des réglages standards internes
--- 3. Fonctionnalités Windows --- Activation des fonctionnalités et détection du besoin de redémarrage Ceux qui ont besoin, par exemple, de .NET Framework 3.5
--- 4. Paquets winget --- Détection de l’installation déjà faite, et écriture ne faisant pas confiance au code de sortie Ceux qui veulent voir seulement la partie winget
--- 5. Applications internes --- Exécution silencieuse de MSI/EXE depuis un dossier partagé. Comparaison de version et gestion du code de sortie Ceux qui veulent distribuer des applications internes
Fin (traitement de sortie) Distinction des retours 3010 / 1641 / 1 / 0 Ceux qui relient à un outil de distribution (Intune, etc.)

Ce script lit kitting.config.json. Un exemple complet du fichier de configuration est inclus sous le nom kitting.config.json dans le code d’exemple de cet article (le zip en fin d’article), mais présentons d’abord uniquement sa structure.

{
  "folders": [ "C:\\Work", "C:\\KsTools" ],
  "registry": [
    { "key": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Edge",
      "name": "HomepageLocation", "value": "https://intra.example.co.jp/", "type": "String" }
  ],
  "userRegistry": [
    { "key": "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced",
      "name": "HideFileExt", "value": 0, "type": "DWord" }
  ],
  "windowsFeatures": [ "NetFx3" ],
  "wingetPackages": [ { "id": "Google.Chrome", "scope": "machine" } ],
  "internalApps": [
    { "displayName": "KsApp Client du système métier",
      "installer": "\\\\fileserver\\deploy\\KsApp\\KsAppSetup.msi",
      "arguments": "/quiet /norestart INSTALLDIR=\"C:\\Program Files\\KsApp\"",
      "version": "3.2.0" }
  ],
  "drives":   [ { "letter": "S", "path": "\\\\fileserver\\share" } ],
  "printers": [ { "name": "\\\\printserver\\MFP-1F", "connection": "\\\\printserver\\MFP-1F" } ]
}

Le tableau suivant montre le rôle de chaque clé et la phase qui la lit. L’intention de conception tient tout entière dans ce tableau.

Clé Contenu Phase de lecture Remarque
folders Chemins des dossiers standards à créer Administrateur (élevée) Ne fait rien s’ils existent déjà
registry Réglages standards internes écrits dans HKLM Administrateur (élevée) De type stratégie (page d’accueil Edge, etc.). S’applique à tous les utilisateurs
userRegistry Réglages par utilisateur écrits dans HKCU Utilisateur (non élevée) Affichage des extensions, etc. Sans effet si écrit dans HKLM
windowsFeatures Fonctionnalités optionnelles de Windows à activer Administrateur (élevée) Peut nécessiter un redémarrage
wingetPackages id et scope des paquets à installer avec winget Administrateur (élevée) Vérifier au préalable le scope avec winget show --scope (chapitre 2)
internalApps Applications internes sur dossier partagé (MSI/EXE) Administrateur (élevée) displayName doit correspondre exactement au nom affiché dans « Programmes et fonctionnalités »
drives Affectation de lecteurs réseau Utilisateur (non élevée) Par session de connexion. Invisible pour l’utilisateur si créé côté élevé
printers Connexion à une imprimante partagée Utilisateur (non élevée) Idem. Une connexion par utilisateur est créée

Le fait que la colonne des phases se répartisse en deux catégories constitue en soi la conception de ce fichier de configuration. Lorsque vous ajoutez un nouvel élément, décidez d’abord s’il s’agit d’un réglage machine ou d’un réglage utilisateur, avant de choisir dans quelle clé le placer.

registry et userRegistry sont séparés parce que leur cible d’application diffère. Un réglage comme « afficher les extensions » de l’Explorateur (HideFileExt) est consulté non pas via une stratégie HKLM, mais via le HKCU propre à chaque utilisateur. L’écrire dans HKLM en phase administrateur ne change pas l’affichage. Ce type de réglage par utilisateur est placé dans userRegistry et appliqué dans la phase non élevée décrite plus loin.

Le displayName de internalApps doit correspondre exactement au nom affiché dans « Programmes et fonctionnalités ». Si version est spécifié, la vérification va jusqu’à confirmer qu’une version égale ou supérieure est installée (en son absence, la vérification porte uniquement sur la correspondance du nom).

L’installation des applications elle-même peut aussi se faire via winget import ou WinGet Configuration du chapitre précédent, mais ce script inclut également l’installation de wingetPackages. En regroupant tout dans un seul fichier de configuration, la définition de « ce qu’il faut installer sur ce poste » tient en un seul endroit, et l’exécution se fait en une seule fois. À l’inverse, laisser un élément écrit dans le fichier de configuration sans que le script ne le lise enregistre comme « déploiement réussi » un poste où il n’est pas installé.

#Requires -RunAsAdministrator
[CmdletBinding()]
param(
    [string] $ConfigPath = "$PSScriptRoot\kitting.config.json"
)

$ErrorActionPreference = 'Stop'
# Le Get-Content du paragraphe 5.1 lit avec la page de code ANSI en l'absence de BOM.
# Lire un JSON en UTF-8 dans un environnement localisé le corromprait, on le précise donc explicitement
$config = Get-Content $ConfigPath -Raw -Encoding utf8 | ConvertFrom-Json
$rebootRequired  = $false
$rebootInitiated = $false
$log    = "C:\ProgramData\KsKitting\kitting_$(Get-Date -f yyyyMMdd_HHmmss).log"
$null   = New-Item -Path (Split-Path $log) -ItemType Directory -Force

# La phase par utilisateur décrite plus loin s'exécute dans un processus séparé, il faut donc
# distribuer la configuration vers un emplacement lisible par tous les utilisateurs.
# Si le paquet distribué est placé à cet emplacement et exécuté tel quel, la source et la
# destination de la copie deviennent le même fichier. Copy-Item lève une erreur en cas de copie
# vers soi-même, ce qui, sous $ErrorActionPreference = 'Stop', arrêterait tout le script avant
# même que le déploiement ne commence
$sharedConfig = 'C:\ProgramData\KsKitting\kitting.config.json'
$sourceFull   = (Resolve-Path $ConfigPath).ProviderPath
$sharedFull   = if (Test-Path $sharedConfig) { (Resolve-Path $sharedConfig).ProviderPath } else { '' }
if (-not [string]::Equals($sourceFull, $sharedFull, 'OrdinalIgnoreCase')) {
    Copy-Item $ConfigPath $sharedConfig -Force
}
Start-Transcript -Path $log -Append

try {
    # --- 1. Dossiers standards ---------------------------------------------
    foreach ($dir in $config.folders) {
        if (-not (Test-Path $dir)) {
            $null = New-Item -Path $dir -ItemType Directory
            Write-Verbose "Créé : $dir"
        }
    }

    # --- 2. Réglages de registre standards internes ------------------------
    foreach ($reg in $config.registry) {
        if (-not (Test-Path $reg.key)) { $null = New-Item -Path $reg.key -Force }
        $item   = Get-Item -Path $reg.key
        $exists = $item.GetValueNames() -contains $reg.name

        # Comparer non seulement la valeur mais aussi le « type ». La comparaison PowerShell
        # considère "1" en REG_SZ et 1 en DWORD comme égaux, ce qui ferait juger « appliqué »
        # à tort un réglage qui, en réalité, n'a aucun effet
        $sameValue = $exists -and $item.GetValue($reg.name) -eq $reg.value
        $sameKind  = $exists -and $item.GetValueKind($reg.name).ToString() -eq $reg.type

        if (-not ($sameValue -and $sameKind)) {
            Set-ItemProperty -Path $reg.key -Name $reg.name -Value $reg.value -Type $reg.type
            Write-Verbose "Défini : $($reg.key)\$($reg.name) = $($reg.value) ($($reg.type))"
        }
    }

    # --- 3. Fonctionnalités Windows -----------------------------------------
    foreach ($feature in $config.windowsFeatures) {
        $state = Get-WindowsOptionalFeature -Online -FeatureName $feature
        if ($state.State -ne 'Enabled') {
            # Quand -NoRestart supprime le redémarrage, le besoin apparaît dans RestartNeeded
            # de la valeur de retour. Sans le récupérer, on se termine avec 0 alors qu'un
            # redémarrage est nécessaire
            $result = Enable-WindowsOptionalFeature -Online -FeatureName $feature -NoRestart
            if ($result.RestartNeeded) { $rebootRequired = $true }
        }
    }

    # --- 4. Paquets winget ---------------------------------------------------
    # Le code de sortie de winget peut être différent de 0 même « déjà installé »,
    # et inversement peut être 0 alors que le paquet n'est en fait pas installé.
    # Le succès se juge en interrogeant l'état. Sans --scope, on capte l'installation
    # au niveau utilisateur de l'administrateur lui-même et on juge à tort « installé en machine »
    function Test-KsWingetPackage {
        param([string] $Id, [string] $Scope)
        $arguments = @('list', '--id', $Id, '--exact', '--accept-source-agreements')
        if ($Scope) { $arguments += @('--scope', $Scope) }
        $null = winget @arguments 2>&1
        return ($LASTEXITCODE -eq 0)
    }

    foreach ($package in $config.wingetPackages) {
        if (Test-KsWingetPackage -Id $package.id -Scope $package.scope) { continue }

        # Le backtick de continuation de ligne doit être placé en fin de ligne. Un commentaire après lui annule la continuation
        winget install --id $package.id -e --silent `
            --accept-package-agreements --accept-source-agreements `
            --scope $package.scope
        $wingetExit = $LASTEXITCODE

        # Se contenter ici d'un avertissement avant de continuer ferait que l'outil de distribution
        # enregistre comme « déploiement réussi » un poste où une application obligatoire n'est pas installée
        if (-not (Test-KsWingetPackage -Id $package.id -Scope $package.scope)) {
            throw "L'installation de $($package.id) a échoué (winget ExitCode=$wingetExit)"
        }
    }

    # --- 5. Applications internes (exécution silencieuse d'un installeur sur dossier partagé) ---
    # La liste des applications installées est ouverte en spécifiant explicitement les deux vues
    # 32 bits et 64 bits. Si un PowerShell 32 bits s'exécute sur un Windows 64 bits (cela peut
    # arriver selon la configuration Intune), la redirection WOW64 fait pointer
    # HKLM:\SOFTWARE\... vers la vue 32 bits, ce qui jugerait à tort une application 64 bits
    # comme « non installée » et la réinstallerait à chaque fois
    $installedApps = foreach ($view in 'Registry64', 'Registry32') {
        $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', $view)
        try {
            $uninstall = $baseKey.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall')
            if (-not $uninstall) { continue }
            try {
                foreach ($name in $uninstall.GetSubKeyNames()) {
                    $appKey = $uninstall.OpenSubKey($name)
                    if (-not $appKey) { continue }
                    try {
                        $displayName = $appKey.GetValue('DisplayName')
                        if ($displayName) {
                            [pscustomobject]@{
                                DisplayName    = [string] $displayName
                                DisplayVersion = [string] $appKey.GetValue('DisplayVersion')
                            }
                        }
                    }
                    finally { $appKey.Dispose() }
                }
            }
            finally { $uninstall.Dispose() }
        }
        finally { $baseKey.Dispose() }
    }

    foreach ($app in $config.internalApps) {
        $installed = $installedApps | Where-Object DisplayName -eq $app.displayName

        # Ne pas juger « installé » sur la seule correspondance de DisplayName. Sur un poste où
        # une ancienne version reste installée, la nouvelle version ne s'installerait pas et une
        # nouvelle exécution ne convergerait plus vers l'état du fichier de configuration.
        # Si le fichier de configuration précise version, vérifier même qu'une version égale ou supérieure est installée
        $upToDate = if ($app.version) {
            $wanted = [version] $app.version
            [bool]($installed | Where-Object {
                $cur = $_.DisplayVersion -as [version]   # Une notation qui n'est pas un format de version est exclue
                $cur -and $cur -ge $wanted
            })
        } else {
            [bool] $installed
        }
        if ($upToDate) { continue }

        # -ArgumentList de Start-Process concatène le tableau avec des espaces pour former une
        # seule ligne de commande, donc les valeurs contenant des espaces doivent être mises entre
        # guillemets côté fichier de configuration
        # Exemple : '/quiet /norestart INSTALLDIR="C:\Program Files\KsApp"'
        # .msi n'étant pas un exécutable, le passer tel quel à Start-Process échoue avec
        # « n'est pas une application valide ». On le lance via msiexec
        if ([System.IO.Path]::GetExtension($app.installer) -eq '.msi') {
            $msiArgs = @('/i', "`"$($app.installer)`"") + $app.arguments
            $proc = Start-Process -FilePath 'msiexec.exe' -ArgumentList $msiArgs -Wait -PassThru -NoNewWindow
        }
        else {
            $proc = Start-Process -FilePath $app.installer -ArgumentList $app.arguments -Wait -PassThru -NoNewWindow
        }
        # Le code de sortie de Windows Installer n'a pas « seulement 0 » comme succès.
        # 3010 et 1641 sont tous deux des succès, seule la gestion du redémarrage diffère
        switch ($proc.ExitCode) {
            0    { }                                   # Succès
            3010 { $rebootRequired = $true }           # Succès. Redémarrage requis (ERROR_SUCCESS_REBOOT_REQUIRED)
            1641 { $rebootInitiated = $true }          # Succès. L'installeur a lancé le redémarrage
            default {
                throw "L'installation de $($app.displayName) a échoué (ExitCode=$($proc.ExitCode))"
            }
        }
        # Si un redémarrage a déjà démarré, les installations suivantes seraient interrompues même lancées
        if ($rebootInitiated) { break }
    }

    if ($rebootInitiated) {
        # 1641 signifie « succès, mais redémarrage déjà lancé ». Si l'on renvoyait 0, l'outil de
        # distribution considérerait que « c'est terminé mais un redémarrage a eu lieu de son
        # propre chef » ; on transmet donc le code tel quel
        Write-Host 'L''installeur a lancé un redémarrage. Relancez après le redémarrage' -ForegroundColor Yellow
        exit 1641
    }

    if ($rebootRequired) {
        # Renvoyer 3010 tel quel permet à Intune ou à l'outil de distribution d'interpréter
        # « succès, redémarrage requis » et de planifier et signaler le redémarrage. Renvoyer 0
        # ici ferait oublier le redémarrage
        Write-Host 'Déploiement terminé (redémarrage requis)' -ForegroundColor Yellow
        exit 3010
    }

    Write-Host 'Déploiement terminé' -ForegroundColor Green
    exit 0
}
catch {
    Write-Warning "Échec : $($_.Exception.Message)"
    Write-Warning $_.InvocationInfo.PositionMessage
    exit 1
}
finally {
    Stop-Transcript
}

Notez que l’affectation de lecteurs réseau a été volontairement exclue de ce script administrateur. L’affectation d’une lettre de lecteur étant un réglage par session de connexion, même créée dans une session élevée en administrateur, elle reste invisible depuis l’Explorateur habituel de l’utilisateur sous UAC. Exécutée avec des privilèges système depuis Intune par exemple, elle serait de toute façon affectée à la session SYSTEM, sans rapport avec l’utilisateur. La bonne approche est d’exécuter les réglages propres à l’utilisateur, en non élevé, lors de la connexion de cet utilisateur.

# Réglages par utilisateur (exécutés en non élevé, lors de la connexion de l'utilisateur. Méthode d'enregistrement plus loin)

# S'exécutant dans un processus distinct du script administrateur, $config n'est pas hérité.
# On relit la configuration distribuée vers ProgramData pendant la phase administrateur
$configPath = 'C:\ProgramData\KsKitting\kitting.config.json'
if (-not (Test-Path $configPath)) {
    Write-Warning "Fichier de configuration introuvable : $configPath"
    exit 1
}
$config = Get-Content $configPath -Raw -Encoding utf8 | ConvertFrom-Json

# Réglages de registre par utilisateur (HideFileExt, etc. Sans effet si écrit dans HKLM)
foreach ($reg in $config.userRegistry) {
    if (-not (Test-Path $reg.key)) { $null = New-Item -Path $reg.key -Force }

    $item   = Get-Item -Path $reg.key
    $exists = $item.GetValueNames() -contains $reg.name
    $same   = $exists -and
              $item.GetValue($reg.name) -eq $reg.value -and
              $item.GetValueKind($reg.name).ToString() -eq $reg.type

    if (-not $same) {
        Set-ItemProperty -Path $reg.key -Name $reg.name -Value $reg.value -Type $reg.type
    }
}

# Lecteur réseau
foreach ($drive in $config.drives) {
    $local    = "$($drive.letter):"
    $existing = Get-SmbMapping -LocalPath $local -ErrorAction SilentlyContinue
    if ($existing) {
        # Juger uniquement sur « l'affectation existe-t-elle » ferait rapporter « succès » avec
        # une ancienne affectation restée en place, même après un changement de réglage dû par
        # exemple à un déplacement du partage. On compare donc aussi la destination
        if ($existing.RemotePath.TrimEnd('\') -eq $drive.path.TrimEnd('\')) { continue }
        Remove-SmbMapping -LocalPath $local -Force
    }
    New-SmbMapping -LocalPath $local -RemotePath $drive.path -Persistent $true
}

# La connexion à une imprimante partagée est elle aussi par utilisateur. Exécutée en administrateur
# ou en SYSTEM, la connexion ne serait créée que pour ce compte, invisible pour l'utilisateur
foreach ($printer in $config.printers) {
    if (-not (Get-Printer -Name $printer.name -ErrorAction SilentlyContinue)) {
        Add-Printer -ConnectionName $printer.connection
    }
}

La connexion à une imprimante partagée (Add-Printer -ConnectionName) reçoit le même traitement. Il s’agit d’une opération qui crée une connexion propre à chaque utilisateur ; même exécutée depuis le script administrateur ou en SYSTEM via Intune, elle reste invisible pour l’employé qui se connectera ensuite. Si vous souhaitez qu’elle soit commune à tous les postes, utilisez un mécanisme de déploiement des imprimantes au niveau machine (distribution par stratégie depuis un serveur d’impression, par exemple), ou exécutez-la dans cette phase non élevée.

Pour la même raison, le placement de fichiers sous le profil utilisateur, l’écriture dans HKCU et la création de raccourcis destinés à l’utilisateur sont eux aussi regroupés dans cette phase non élevée. Ce dispositif à deux étages — « les réglages de toute la machine en administrateur, une fois ; les réglages propres à l’utilisateur, en non élevé, à chaque connexion » — constitue la forme de base d’un script de déploiement. Pour les points de vigilance sur les chemins UNC et l’affectation de lecteurs, voir aussi « Les pièges des partages réseau et des chemins UNC ».

Comment déclencher la phase utilisateur. La clé de ce dispositif à deux étages est le mécanisme qui fait tourner ce script non élevé « lors de la connexion de l’utilisateur, avec les privilèges de cet utilisateur lui-même ». Si ce maillon manque, seule la phase administrateur s’exécute, et l’on obtient un poste où « ni le lecteur ni l’imprimante ne sont configurés ». Il existe trois méthodes.

Méthode Moment d’exécution Environnement adapté Point de vigilance
Clé Run de HKLM À chaque connexion de tout utilisateur Sans jonction à un domaine. On veut tout faire tenir dans un seul script depuis Intune ou un outil de distribution S’exécute à chaque fois, donc l’idempotence est un prérequis. Il faut spécifier de ne pas afficher de fenêtre
Planificateur de tâches (déclencheur à la connexion) À chaque connexion de tout utilisateur On veut conserver un historique des résultats d’exécution, ou différer l’exécution Mettre « exécuter avec les privilèges les plus élevés » sur désactivé (l’activer élève la session et annule l’intérêt)
Script de connexion (stratégie de groupe) À chaque connexion de tout utilisateur Déjà jointe à un domaine S’intègre à une exploitation GPO existante, mais peu pratique sur un poste isolé

Méthode 1 : enregistrer dans la clé Run de HKLM (à exécuter à la fin de la phase administrateur)

# Placer le script de la phase utilisateur à un emplacement lisible par tous les utilisateurs
$userScript = 'C:\ProgramData\KsKitting\Invoke-KsUserKitting.ps1'
Copy-Item "$PSScriptRoot\Invoke-KsUserKitting.ps1" $userScript -Force

# Le Run de HKLM s'exécute avec les privilèges de l'utilisateur connecté (non élevé).
# Même en essayant d'écrire dans HKCU, tant que l'on s'exécute en administrateur/SYSTEM, ce qui
# serait écrit serait « le HKCU de l'administrateur lui-même », pas celui de l'employé qui
# utilisera le poste ensuite
$runKey  = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
$command = 'powershell.exe -NoProfile -ExecutionPolicy Bypass ' +
           "-WindowStyle Hidden -File `"$userScript`""
Set-ItemProperty -Path $runKey -Name 'KsUserKitting' -Value $command

Méthode 2 : enregistrer dans le Planificateur de tâches (pour conserver un historique)

Au cas où vous liriez directement cette section sans être passé par la méthode 1, reprenons depuis le placement du script.

# Placer à un emplacement lisible par tous les utilisateurs (identique à la méthode 1)
$userScript = 'C:\ProgramData\KsKitting\Invoke-KsUserKitting.ps1'
New-Item -ItemType Directory -Path (Split-Path $userScript) -Force | Out-Null
Copy-Item -Path '.\Invoke-KsUserKitting.ps1' -Destination $userScript -Force

$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
    -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$userScript`""

$trigger = New-ScheduledTaskTrigger -AtLogOn

# Spécifier BUILTIN\Users (S-1-5-32-545) fait s'exécuter la tâche avec les privilèges de la
# personne connectée. RunLevel Limited signifie « ne pas élever ». Mettre Highest ici ferait
# tourner la tâche dans une session élevée, rendant l'affectation de lecteur invisible pour l'utilisateur
$principal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-545' -RunLevel Limited

Register-ScheduledTask -TaskName 'KsUserKitting' `
    -Action $action -Trigger $trigger -Principal $principal -Force

Méthode 3 : script de connexion par stratégie de groupe

L’emplacement du réglage est Configuration utilisateur > Stratégies > Paramètres Windows > Scripts (ouverture/fermeture de session) > Ouverture de session (sur un gpedit.msc de poste isolé, il n’y a pas de niveau « Stratégies » : Configuration utilisateur > Paramètres Windows > Scripts (ouverture/fermeture de session)). Ajoutez le script dans l’onglet « Scripts PowerShell » de la boîte de dialogue. Écrire directement un .ps1 dans l’onglet « Scripts » le fait traiter comme un exécutable et ne produit pas le comportement voulu. Pour une stratégie locale, le script est physiquement placé dans %SystemRoot%\System32\GroupPolicy\User\Scripts\Logon.

Quelle que soit la méthode, le point commun est que l’exécution a lieu à chaque connexion. C’est aussi pour cette raison que le script de la phase utilisateur est écrit de façon idempotente (vérifier l’état actuel avant de modifier). Pour ne l’exécuter « qu’une seule fois », écrivez un marqueur d’achèvement sous HKCU et vérifiez-le en tête de script.

Voici les points clés de conception.

  • Séparez les réglages nécessitant une élévation des réglages par utilisateur. Comme indiqué ci-dessus, les mélanger provoque l’accident « le lecteur qui aurait dû être affecté n’apparaît pas »
  • Externalisez la configuration dans un JSON. Cela permet d’exprimer les variations par service ou par modèle de machine sans modifier le script
  • Vérifiez l’état actuel avant chaque traitement. Cela garantit une exécution sûre quel que soit le nombre de fois
  • Conservez une trace avec Start-Transcript. Vous pourrez retracer ultérieurement « ce qui a été fait sur ce poste » (« Les flux de sortie PowerShell et la conception des journaux »)
  • Renvoyez un code de sortie. Cela permet à Intune ou à l’outil de distribution de juger du succès. Notez que sous Windows Installer, 0 n’est pas le seul code de succès. 3010 (ERROR_SUCCESS_REBOOT_REQUIRED, redémarrage requis) et 1641 (ERROR_SUCCESS_REBOOT_INITIATED, redémarrage déjà lancé) sont tous deux des succès.6 Les faire tomber dans default en les traitant comme des échecs ferait apparaître en rouge, dans l’outil de distribution, une installation pourtant réussie. Le point clé consiste à les traiter comme des succès, puis à les renvoyer tels quels à l’appelant en fin de script. Renvoyer 0 priverait l’outil de distribution de tout moyen de connaître le besoin de redémarrage (« La gestion des erreurs et la conception des tentatives en PowerShell »)
  • Faites attention aux guillemets dans les arguments de l’installeur. -ArgumentList de Start-Process se contente de concaténer le tableau avec des espaces, la séparation des arguments n’est donc pas préservée. Pour un chemin contenant des espaces, mettez-le entre guillemets côté fichier de configuration, ou utilisez ProcessStartInfo.ArgumentList (PowerShell 7) (« Appeler correctement un exe externe depuis PowerShell »)

6. Utiliser winget depuis un module PowerShell

Utiliser un module PowerShell est plus robuste que d’analyser la sortie de la ligne de commande sous forme de chaîne. Microsoft.WinGet.Client propose des applets de commande comme Find-WinGetPackage / Install-WinGetPackage / Get-WinGetPackage / Update-WinGetPackage.4

Install-Module -Name Microsoft.WinGet.Client -Scope AllUsers -Force

# Vérifier si c'est déjà installé avant de le faire (idempotent)
foreach ($id in 'Google.Chrome', 'Microsoft.VisualStudioCode', '7zip.7zip') {
    if (Get-WinGetPackage -Id $id -ErrorAction SilentlyContinue) {
        Write-Verbose "Déjà installé : $id"
        continue
    }
    Install-WinGetPackage -Id $id -Mode Silent -Scope System
}

Comme le résultat est renvoyé sous forme d’objet, l’avantage est de pouvoir écrire directement la détermination du succès ou le rapprochement de listes.

7. Points de vigilance pour l’exécution sans surveillance et le contexte système

Vouloir automatiser entièrement le déploiement fait immanquablement se heurter à la question de « sous quel compte exécuter ».

  • Certaines parties de winget supposent une exécution dans un contexte utilisateur. L’exécution dans le contexte système est encore, à ce stade, mentionnée par Microsoft comme une fonctionnalité à venir3
  • Séparez les traitements nécessitant une élévation des traitements propres à l’utilisateur. Une configuration où les réglages de toute la machine s’exécutent avec des privilèges administrateur, et où les réglages sous le profil utilisateur s’exécutent à la première connexion, est plus facile à gérer
  • Vérifiez toujours avec le compte d’exécution réellement utilisé. « Ça a marché avec mon propre compte administrateur, mais pas une fois distribué » est l’échec le plus fréquent dans ce domaine (« Les tâches du Planificateur ne s’exécutent pas »)

8. Les bonnes pratiques en pratique (tableau de décision)

Objectif Moyen Remarque
Installer un produit du commerce ou open source winget install / configure --silent --accept-* est indispensable1
Extraire la configuration d’un poste standard existant winget export Les réglages ne sont pas inclus. À utiliser comme point de départ2
Applications + réglages Windows de façon déclarative winget configure (YAML) Win10 1809 ou ultérieur + winget 1.6 ou ultérieur3
Registre, fonctionnalités Windows, applications internes PowerShell (administrateur) Vérifier l’état avant de modifier (idempotent)
Lecteur partagé, imprimante partagée, réglages propres à l’utilisateur PowerShell (à la connexion, non élevé) Invisible pour l’utilisateur si créé en élevé ou en SYSTEM
Applications internes propriétaires PowerShell + installeur silencieux Construire un dépôt dédié est excessif à petite échelle
Vouloir piloter depuis PowerShell Microsoft.WinGet.Client Plus besoin d’analyser la sortie sous forme de chaîne4
Exécution sans surveillance Vérifier avec le compte d’exécution Le contexte système comporte des contraintes3
Trace d’exécution Start-Transcript + code de sortie Conserver « ce qui a été fait sur ce poste »
Redémarrage requis Renvoyer le code de sortie 3010 / 1641 Les deux sont des succès. Renvoyer 0 empêche l’outil de distribution de reconnaître le redémarrage6

9. Résumé

  • Un manuel de procédure de déploiement peut être remplacé par un fichier exécutable. La répartition réaliste consiste à confier l’installation des applications à winget, et le reste des réglages à PowerShell.
  • Pour winget install, ajoutez toujours --silent ainsi que --accept-package-agreements et --accept-source-agreements. Les oublier bloque l’exécution sans surveillance.
  • winget export permet d’extraire la configuration d’un poste standard existant, mais n’inclut ni les réglages, ni les applications hors du périmètre de winget.
  • Utiliser WinGet Configuration (winget configure) permet de regrouper applications et réglages dans un seul fichier déclaratif, pour une configuration robuste face aux relances.
  • Pour le traitement côté PowerShell, écrivez toujours « vérifier l’état actuel avant de modifier » afin d’assurer l’idempotence.
  • Séparez les phases d’exécution : les réglages de toute la machine avec des privilèges administrateur, et les réglages propres à l’utilisateur comme les lecteurs réseau ou les imprimantes partagées en non élevé à la connexion. Une connexion créée dans une session élevée ou en SYSTEM reste invisible pour l’utilisateur.
  • Pour l’exécution sans surveillance, la vérification du compte d’exécution est primordiale. Concevez en partant du principe que l’exécution de winget dans le contexte système comporte des contraintes.

Téléchargement du code d’exemple

Le code traité dans cet article est distribué sous une forme directement exécutable. Il contient des exemples complets de la phase administrateur, de la phase utilisateur et du fichier de configuration.

Télécharger le code d’exemple (zip)

Les exemples de cet article dépendant de Windows et du tenant, ils n’ont pas fait l’objet d’une vérification d’exécution. L’analyse syntaxique et l’analyse statique via PSScriptAnalyzer ont été réalisées sur tous les fichiers, mais vérifiez impérativement leur fonctionnement sur votre propre machine de test.

# Analyse syntaxique + analyse statique (exécutable même hors Windows)
./Invoke-SampleTests.ps1

Les valeurs de configuration (chemins, noms de serveurs, ID de tenant, etc.) sont des exemples. Ne les exécutez pas telles quelles en environnement de production : adaptez-les à votre propre environnement.

Articles connexes

Domaines de conseil associés

KomuraSoft LLC (合同会社小村ソフト) prend en charge l’automatisation du déploiement de PC et des environnements standards internes, la transformation en fichiers exécutables d’opérations dépendantes d’une seule personne via un manuel de procédure, ainsi que l’accompagnement à la conception de scripts de distribution.

Références

  1. Microsoft Learn, install command (winget). Sur la spécification de la cible via –id / -e, l’installation sans surveillance via –silent, l’acceptation des conditions d’utilisation via –accept-package-agreements / –accept-source-agreements, et la spécification de la portée d’installation (user / machine) via –scope. Ainsi que, d’après Use WinGet to install and manage applications, sur la liste des commandes.  2 3

  2. Microsoft Learn, export command (winget). Sur la possibilité d’écrire la liste des paquets installés dans un fichier JSON, l’enregistrement des versions via –include-versions, la restauration via la commande import et le comportement de –ignore-unavailable, le fait que l’export se limite aux paquets gérés par winget, et sur la hiérarchie du JSON produit (Sources / Packages / PackageIdentifier / Version, Version étant optionnel). La structure du JSON est également définie dans packages.schema.2.0.json : le niveau supérieur comporte WinGetVersion, CreationDate et Sources ; chaque élément de Sources possède SourceDetails (Name / Identifier / Argument / Type) et Packages ; chaque élément de Packages requiert PackageIdentifier et porte Version, etc. en option.  2 3 4 5

  3. Microsoft Learn, WinGet Configuration. Sur le fait que WinGet Configuration est un mécanisme déclarant l’état souhaité en YAML et l’appliquant via PowerShell DSC, qu’il peut être utilisé pour une configuration sans surveillance, qu’il nécessite Windows 10 version 1809 (build 17763) ou ultérieur ou Windows 11, ainsi que WinGet v1.6.2631 ou ultérieur, sur la gestion de l’UAC en cas d’exécution depuis un shell administrateur, et sur le fait que l’exécution dans le contexte système est mentionnée comme un développement futur. Ainsi que, d’après la commande configure, sur show / –accept-configuration-agreements.  2 3 4 5 6 7

  4. GitHub, microsoft/winget-cli — module PowerShell Microsoft.WinGet.Client. Sur la possibilité d’installer le module Microsoft.WinGet.Client depuis PowerShell Gallery, et sur le fait que des applets de commande comme Find-WinGetPackage / Get-WinGetPackage / Install-WinGetPackage / Update-WinGetPackage / Uninstall-WinGetPackage sont fournies et permettent de manipuler les résultats sous forme d’objets.  2 3

  5. Microsoft Learn, show command (winget). Sur le fait qu’il s’agit d’une commande affichant les détails (métadonnées et informations d’installeur) d’une application spécifiée, que l’option –scope permet de choisir la portée d’installation (user / machine), et que les informations d’installeur affichées se basent sur les arguments spécifiés et le jugement de WinGet. 

  6. Microsoft Learn, Windows Installer error codes. Sur le fait qu’ERROR_SUCCESS_REBOOT_REQUIRED (3010) signifie « un redémarrage est nécessaire pour que les modifications prennent effet ; l’installation elle-même a réussi », et qu’ERROR_SUCCESS_REBOOT_INITIATED (1641) signifie « l’installeur a lancé le redémarrage ; ce code indique un succès ».  2

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Est-ce que winget import permet d'automatiser tout le déploiement d'un poste ?
Cela permet d'automatiser jusqu'à l'installation des applications, mais cela ne suffit pas à lui seul. Ce que winget import reproduit est une liste de paquets ; les réglages internes aux applications, l'ajout d'imprimantes, l'affectation de lecteurs réseau, les paramètres d'alimentation, ou les réglages standards internes appliqués via le registre en sont exclus. De plus, les applications installées par un autre moyen que winget, ainsi que les applications métier internes, ne figurent pas dans l'export. En pratique, l'approche réaliste consiste en un dispositif à deux étages : laisser l'installation des applications à winget, et compléter le reste des réglages avec un script PowerShell.
Quelle est la différence entre winget et WinGet Configuration (winget configure) ?
Les commandes install/import de winget sont des instructions procédurales du type « installer ceci, dans cet ordre », alors que WinGet Configuration consiste à écrire dans un fichier YAML une déclaration du type « voici l'état final souhaité ». En interne, elle utilise PowerShell DSC, ce qui permet d'exprimer dans un seul fichier non seulement l'installation des applications, mais aussi les réglages Windows et la configuration des applications. Comme elle ne fait rien si l'état souhaité est déjà atteint, il suffit de relancer le même fichier en cas d'échec en cours de route, ce qui la rend robuste face aux reprises de déploiement. Elle nécessite Windows 10 1809 ou ultérieur et winget 1.6 ou ultérieur.
Est-il prudent d'exécuter winget avec des droits SYSTEM depuis le Planificateur de tâches ou Intune ?
Il faut être prudent. Certaines parties de winget supposent une exécution dans un contexte utilisateur, et l'exécution dans le contexte système est encore, à ce stade, un élément que Microsoft mentionne comme un développement futur. En pratique, on contourne cela en utilisant --scope machine pour installer pour tous les utilisateurs, en passant par le module PowerShell (Microsoft.WinGet.Client), ou en l'exécutant dans le contexte utilisateur lors de la première connexion. Dans tous les cas, vérifiez toujours avec le compte d'exécution réellement utilisé.
Un script de déploiement doit-il pouvoir être exécuté n'importe quel nombre de fois sans risque ?
Oui. Considérez l'idempotence (obtenir le même résultat quel que soit le nombre d'exécutions) comme un impératif. Le déploiement échoue couramment en cours de route, et il doit être possible de recommencer depuis le début à chaque fois. Si vous écrivez le script de façon à vérifier l'existence avec Test-Path avant de créer un dossier, à vérifier la valeur actuelle avant un réglage de registre, et à vérifier si une application est déjà installée avant de l'installer, vous pourrez reprendre là où cela a échoué. WinGet Configuration intègre cette logique dès le départ.
Peut-on distribuer des applications métier internes via winget ?
C'est possible en mettant en place un dépôt privé interne (une source REST API), mais cela implique de construire et maintenir un serveur pour cela. Pour quelques applications seulement, exécuter silencieusement un installeur depuis un dossier partagé via un script PowerShell est plus simple. Pour les petites structures, la répartition la plus réaliste consiste à laisser winget gérer les produits du commerce et open source, et à installer les applications internes avec PowerShell.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog