Automatiser le déploiement de postes avec winget et PowerShell — Rendre le manuel de procédure exécutable
· Go Komura · 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 installprend 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 commeInstall-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 directives — allowPrerelease 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 dewinget configure showdevient directement lisible grâce à lui, écrivez-le systématiquementallowPrerelease… 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.
flowchart TB
CFG["kitting.config.json<br/>Définition de « comment ce poste doit être »"]
subgraph ADM["Phase administrateur (élevée. une fois par poste)"]
A1["Invoke-KsKitting.ps1"]
A2["Dossiers / registre HKLM /<br/>fonctionnalités Windows / paquets winget /<br/>applications internes (MSI, EXE)"]
A3["Distribue le JSON de configuration sous ProgramData"]
A1 --> A2
A1 --> A3
end
subgraph USR["Phase utilisateur (non élevée. à chaque connexion de l'utilisateur)"]
U1["Invoke-KsUserKitting.ps1"]
U2["Registre HKCU /<br/>lecteur réseau /<br/>imprimante partagée"]
U1 --> U2
end
CFG --> A1
A3 -->|"relit la configuration distribuée"| U1
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
defaulten 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.
-ArgumentListdeStart-Processse 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 utilisezProcessStartInfo.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--silentainsi que--accept-package-agreementset--accept-source-agreements. Les oublier bloque l’exécution sans surveillance. winget exportpermet 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
- Appeler correctement un exe externe depuis PowerShell — guillemets des arguments, codes de sortie et pièges d’encodage
- La gestion des erreurs et la conception des tentatives en PowerShell — des pièges où try/catch échoue jusqu’aux bonnes pratiques d’exit code et de nouvelle tentative
- Abandonner Write-Host — les flux de sortie PowerShell et la conception des journaux
- Les tâches du Planificateur ne s’exécutent pas, se terminent en 0x1 — isoler la cause et concevoir une exploitation fiable
- Verrouiller des postes métier en mode kiosque — choisir entre Assigned Access et Shell Launcher, et concevoir l’exploitation
- La solution réaliste après la fin du support de Windows 10 — tableau de décision ESU/LTSC/remplacement
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.
- Conseil technique et revue de conception
- Développement de systèmes métier
- Modernisation et maintenance de logiciels Windows existants
- Contact
Références
-
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
-
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
-
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
-
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
-
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. ↩
-
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 associés
Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.
Conception des paramètres et modularisation des scripts PowerShell — Du « script qui fonctionne » au « script que l'on peut transmettre »
Ce guide organise les étapes pour élever la qualité d'un script PowerShell jusqu'à pouvoir le transmettre à d'autres personnes. Il couvre...
Renforcer la sécurité de PowerShell — journalisation, AMSI, mode de langage, JEA
Ce guide rassemble la pratique pour utiliser PowerShell en toute sécurité sans l'interdire : activation de la journalisation des blocs de...
Examiner les journaux d'événements en pratique avec Get-WinEvent — La rapidité du filtrage détermine le temps d'investigation
Ce guide présente comment rendre plus efficace l'investigation des journaux d'événements Windows avec PowerShell. Il explique pourquoi fi...
Distribuer et mettre à jour des modules PowerShell en interne — PSResourceGet et le dépôt interne
Ce guide explique comment sortir de la pratique consistant à copier et réutiliser des fichiers .ps1 depuis un dossier partagé. Il couvre ...
Utiliser PowerShell avec les API REST — la pratique d'Invoke-RestMethod
Ce guide rassemble la pratique de l'appel d'API REST internes ou SaaS depuis PowerShell : transmission des en-têtes d'authentification, p...
Sujets associés
Ces pages replacent le sujet dans un contexte plus large de services et de décisions.
Thèmes techniques Windows
Portail des sujets sur le développement Windows, l'analyse des incidents et la valorisation des actifs existants.
Services liés à ce sujet
Cet article est directement lié aux services suivants.
Développement d'applications Windows
Applications métier, intégration d'équipements et outils de communication, des besoins au développement.
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.