Windows Installer (MSI) et le registre¶
Ce que vous allez apprendre¶
- Comment Windows Installer (msiexec.exe) utilise le registre pour gerer l'etat complet de chaque installation
- Les trois emplacements principaux ou MSI stocke ses donnees : Installer, Uninstall, HKCR\Installer
- Le systeme de GUIDs comprimes et l'algorithme de transformation
- L'arborescence des produits, composants et fonctionnalites dans le registre
- La structure complete de la cle Uninstall et son exploitation par "Programmes et fonctionnalites"
- Le mecanisme de patches et de mises a niveau (major upgrade, minor update, small update)
- Les actions personnalisees MSI et leurs interactions avec le registre
- Le systeme d'auto-reparation et de resilience MSI
- Les strategies de securite et permissions liees a Windows Installer
- Les techniques de diagnostic et de depannage avancees
- La transition de MSI vers MSIX et ses implications sur le registre
Architecture MSI et le registre¶
Ouvrez une invite PowerShell en tant qu'administrateur et executez :
# Count all MSI-installed products registered on this machine
$products = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" |
Where-Object { (Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue).WindowsInstaller -eq 1 }
Write-Host "MSI products installed: $($products.Count)"
Chaque produit MSI laisse une empreinte considerable dans le registre. Si une application Win32 classique se contente d'ecrire quelques cles, un package MSI inscrit des dizaines de valeurs reparties dans trois arborescences distinctes. Comprendre cette architecture est indispensable pour tout administrateur qui deploie, maintient ou depanne des applications en entreprise.
Analogie
Pensez a Windows Installer comme au service du cadastre d'une ville. Chaque batiment (produit MSI) possede un acte de propriete (ProductCode), un plan d'architecte (PackageCode) et un numero de parcelle (UpgradeCode). Le cadastre (le registre) conserve la trace de chaque piece du batiment (composant), de chaque etage (fonctionnalite) et de chaque renovation (patch). Quand un batiment est demoli (desinstallation), le cadastre efface methodiquement toutes les references.
Les trois emplacements principaux¶
Windows Installer repartit ses donnees dans trois zones du registre :
| Emplacement | Contenu principal | Acces |
|---|---|---|
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer | Donnees internes du moteur MSI (UserData, produits, patches) | Systeme |
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | Informations affichees dans "Programmes et fonctionnalites" | Lecture publique |
HKCR\Installer | Alias vers HKLM\SOFTWARE\Classes\Installer — produits, composants, fonctionnalites | Systeme |
HKCR\Installer est un alias
HKCR\Installer n'est pas une ruche independante. C'est une vue fusionnee de HKLM\SOFTWARE\Classes\Installer et HKCU\SOFTWARE\Classes\Installer. Pour les installations per-machine, les donnees se trouvent sous HKLM. Pour les installations per-user, elles se trouvent sous HKCU.
Les trois GUIDs MSI¶
Chaque package MSI utilise trois identifiants globalement uniques :
| GUID | Role | Change quand ? | Exemple |
|---|---|---|---|
| ProductCode | Identifie un produit specifique | A chaque major upgrade | {AC76BA86-7AD7-1033-7B44-AC0F074E4100} |
| UpgradeCode | Lie toutes les versions d'un meme produit | Jamais (stable entre les versions) | {A19B2B52-EA8C-40A0-9E3D-7B1F38494234} |
| PackageCode | Identifie un fichier .msi precis | A chaque compilation du .msi | {F2E466A7-B10C-4214-9D2C-98B5AC12D0E0} |
# Retrieve the three GUIDs for a specific MSI product using WMI
$product = Get-CimInstance Win32_Product | Where-Object { $_.Name -like "*Visual C++ 2022*" } | Select-Object -First 1
if ($product) {
Write-Host "ProductCode: $($product.IdentifyingNumber)"
Write-Host "PackageCode: $($product.PackageCode)"
# UpgradeCode requires querying the MSI database or the registry
$compressed = ($product.IdentifyingNumber -replace '[{}]','')
Write-Host "Name: $($product.Name)"
}
ProductCode: {3A5B2C9E-7F41-4D8A-B6E0-1C9D5E8F2A3B}
PackageCode: {F2E466A7-B10C-4214-9D2C-98B5AC12D0E0}
Name: Microsoft Visual C++ 2022 X64 Minimum Runtime - 14.38.33130
Le format GUID comprime¶
MSI ne stocke pas les GUIDs dans leur format standard {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}. Il utilise un format comprime ou les caracteres sont reordonnes par groupes. Ce format supprime les tirets et les accolades, puis inverse chaque groupe de caracteres.
L'algorithme de compression fonctionne ainsi :
- Supprimer les accolades
{et} - Supprimer les tirets
- - Decouper le resultat en groupes de tailles : 8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2
- Inverser chaque groupe individuellement
- Concatener les groupes
Exemple concret avec {AC76BA86-7AD7-1033-7B44-AC0F074E4100} :
Etape 1 : AC76BA86-7AD7-1033-7B44-AC0F074E4100
Etape 2 : AC76BA867AD710337B44AC0F074E4100
Etape 3 : AC76BA86 | 7AD7 | 1033 | 7B | 44 | AC | 0F | 07 | 4E | 41 | 00
Etape 4 : 68AB67CA | 7DA7 | 3301 | B7 | 44 | CA | F0 | 70 | E4 | 14 | 00
Etape 5 : 68AB67CA7DA73301B744CAF070E41400
Le GUID comprime resultant est 68AB67CA7DA73301B744CAF070E41400.
# Convert a standard GUID to MSI compressed format
function ConvertTo-CompressedGuid {
param([string]$Guid)
$clean = $Guid -replace '[{}\-]', ''
$groups = @(8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2)
$result = ""
$pos = 0
foreach ($len in $groups) {
$chunk = $clean.Substring($pos, $len)
$reversed = -join ($chunk.ToCharArray() | Sort-Object { -([array]::IndexOf($chunk.ToCharArray(), $_)) })
# Proper reversal
$chars = $chunk.ToCharArray()
[Array]::Reverse($chars)
$result += -join $chars
$pos += $len
}
return $result
}
# Convert an MSI compressed GUID back to standard format
function ConvertFrom-CompressedGuid {
param([string]$Compressed)
$groups = @(8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2)
$parts = @()
$pos = 0
foreach ($len in $groups) {
$chunk = $Compressed.Substring($pos, $len)
$chars = $chunk.ToCharArray()
[Array]::Reverse($chars)
$parts += -join $chars
$pos += $len
}
return "{$($parts[0])-$($parts[1])-$($parts[2])-$($parts[3])$($parts[4])-$($parts[5])$($parts[6])$($parts[7])$($parts[8])$($parts[9])$($parts[10])}"
}
# Example usage
$standard = "{AC76BA86-7AD7-1033-7B44-AC0F074E4100}"
$compressed = ConvertTo-CompressedGuid $standard
Write-Host "Standard: $standard"
Write-Host "Compressed: $compressed"
Write-Host "Back: $(ConvertFrom-CompressedGuid $compressed)"
Standard: {AC76BA86-7AD7-1033-7B44-AC0F074E4100}
Compressed: 68AB67CA7DA73301B744CAF070E41400
Back: {AC76BA86-7AD7-1033-7B44-AC0F074E4100}
Flux d'installation MSI et ecritures registre¶
flowchart TD
A["msiexec /i produit.msi"] --> B["Service Windows Installer\n(msiserver)"]
B --> C{"Installation\nper-machine ou\nper-user ?"}
C -->|"Per-machine"| D["Ecritures dans HKLM"]
C -->|"Per-user"| E["Ecritures dans HKCU"]
D --> F["HKCR\\Installer\\Products\\GUID comprime\n(ProductName, PackageCode, etc.)"]
D --> G["HKCR\\Installer\\Components\\GUID comprime\n(chemins des keypaths)"]
D --> H["HKCR\\Installer\\Features\\GUID comprime\n(liste des composants par feature)"]
D --> I["HKLM\\...\\Uninstall\\ProductCode\n(DisplayName, UninstallString, etc.)"]
D --> J["HKLM\\...\\Installer\\UserData\\S-1-5-18\\Products\n(donnees d'installation detaillees)"]
F --> K["Installation terminee"]
G --> K
H --> K
I --> K
J --> K
E --> L["HKCU\\SOFTWARE\\Classes\\Installer\\Products"]
E --> M["HKCU\\...\\Uninstall\\ProductCode"]
L --> K
M --> K En resume
- MSI repartit ses donnees dans trois arborescences : Installer (donnees internes), Uninstall (affichage utilisateur) et HKCR\Installer (produits/composants)
- Chaque produit est identifie par trois GUIDs : ProductCode, UpgradeCode et PackageCode, stockes en format comprime (inversion par groupes)
- Les installations per-machine ecrivent dans HKLM, les per-user dans HKCU
Produits et composants dans le registre¶
L'arborescence Products¶
Chaque produit installe par MSI possede une entree sous :
Cette cle contient les proprietes de publication du produit telles que MSI les a enregistrees :
| Valeur | Type | Description |
|---|---|---|
ProductName | REG_SZ | Nom du produit tel que defini dans la table Property du MSI |
PackageCode | REG_SZ | GUID comprime du fichier .msi utilise pour l'installation |
Language | REG_DWORD | Code de langue LCID (ex: 1033 pour anglais US, 1036 pour francais) |
Version | REG_DWORD | Version encodee : major * 16777216 + minor * 65536 + build |
Assignment | REG_DWORD | 0 = per-user, 1 = per-machine |
AdvertiseFlags | REG_DWORD | Drapeaux de publication (installation a la demande) |
InstanceType | REG_DWORD | 0 = normal, 1 = instance multiple autorisee |
AuthorizedLUAApp | REG_DWORD | 1 = application autorisee pour les utilisateurs non-admin |
Clients | REG_MULTI_SZ | Liste des produits dependants |
DeploymentFlags | REG_DWORD | Options de deploiement GPO |
# List all registered MSI products from the Products subtree
Get-ChildItem "HKLM:\SOFTWARE\Classes\Installer\Products" | ForEach-Object {
$props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
if ($props.ProductName) {
[PSCustomObject]@{
CompressedGuid = $_.PSChildName
ProductName = $props.ProductName
Language = $props.Language
Assignment = if ($props.Assignment -eq 1) { "Per-machine" } else { "Per-user" }
}
}
} | Format-Table -AutoSize
CompressedGuid ProductName Language Assignment
-------------- ----------- -------- ----------
68AB67CA7DA73301B744CAF070E41400 Adobe Acrobat Reader DC 1033 Per-machine
1E5D742E02F66B740BB72A9A32C5D83C Microsoft Visual C++ 2022 Redistribut. 1033 Per-machine
...
Decoder la version¶
La valeur Version est un entier 32 bits qui encode trois numeros :
# Decode an MSI product version from the registry
function ConvertFrom-MsiVersion {
param([int]$EncodedVersion)
$major = [math]::Floor($EncodedVersion / 16777216) # bits 24-31
$minor = [math]::Floor(($EncodedVersion % 16777216) / 65536) # bits 16-23
$build = $EncodedVersion % 65536 # bits 0-15
return "$major.$minor.$build"
}
# Example: version value 0x0E000000 = 14.0.0
$regVersion = (Get-ItemProperty "HKLM:\SOFTWARE\Classes\Installer\Products\68AB67CA7DA73301B744CAF070E41400" -ErrorAction SilentlyContinue).Version
if ($regVersion) {
Write-Host "Encoded: $regVersion -> Decoded: $(ConvertFrom-MsiVersion $regVersion)"
}
L'arborescence Components¶
Les composants sont les unites atomiques d'une installation MSI. Chaque composant est identifie par un GUID comprime et pointe vers un keypath — l'element (fichier, cle de registre ou dossier) dont la presence confirme que le composant est correctement installe.
Sous chaque composant, on trouve des valeurs nommees par le GUID comprime du produit qui a installe ce composant. La donnee de chaque valeur est le keypath du composant dans le contexte de ce produit.
| Type de keypath | Format de la valeur | Exemple |
|---|---|---|
| Fichier | Chemin complet du fichier | C:\Program Files\App\main.exe |
| Cle de registre | Prefixe 00:, 01:, 02: suivi du chemin | 02:\SOFTWARE\App\Config\Setting |
| Dossier | Chemin du dossier avec \ final | C:\Program Files\App\Data\ |
| Source de donnees ODBC | Prefixe ODBC suivi du DSN | Specifique au composant |
| Assembly | Nom qualifie de l'assembly .NET | MyLib, Version=1.0, Culture=neutral, PublicKeyToken=... |
Le prefixe pour les keypaths de type registre indique la racine :
| Prefixe | Racine |
|---|---|
00: | HKCR |
01: | HKCU |
02: | HKLM |
03: | HKU |
# Explore components for a specific product (by compressed GUID)
$productGuid = "68AB67CA7DA73301B744CAF070E41400"
Get-ChildItem "HKLM:\SOFTWARE\Classes\Installer\Components" | ForEach-Object {
$val = (Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue).$productGuid
if ($val) {
[PSCustomObject]@{
ComponentGuid = $_.PSChildName
Keypath = $val
}
}
} | Select-Object -First 10 | Format-Table -AutoSize
ComponentGuid Keypath
------------- -------
0A1B2C3D4E5F67890A1B2C3D4E5F6789 C:\Program Files\Adobe\Acrobat DC\Acrobat\Acrobat.exe
1B2C3D4E5F67890A1B2C3D4E5F67890A 02:\SOFTWARE\Adobe\Acrobat Reader\DC\InstallPath
2C3D4E5F67890A1B2C3D4E5F67890A1B C:\Program Files\Adobe\Acrobat DC\Acrobat\AcroRd32.dll
3D4E5F67890A1B2C3D4E5F67890A1B2C C:\Program Files\Adobe\Acrobat DC\Resource\
...
L'arborescence Features¶
Les fonctionnalites (features) regroupent un ou plusieurs composants en unites installables par l'utilisateur (ex: "Documentation", "Outils supplementaires").
Chaque valeur sous cette cle porte le nom d'une fonctionnalite. Sa donnee contient la relation parent-enfant et la liste des composants associes, separes par des caracteres null.
# List features of a product
$productGuid = "68AB67CA7DA73301B744CAF070E41400"
$path = "HKLM:\SOFTWARE\Classes\Installer\Features\$productGuid"
if (Test-Path $path) {
$props = Get-ItemProperty $path
$props.PSObject.Properties | Where-Object { $_.Name -notlike "PS*" } | ForEach-Object {
[PSCustomObject]@{
Feature = $_.Name
Value = if ($_.Value) { $_.Value.Substring(0, [Math]::Min(60, $_.Value.Length)) + "..." } else { "(empty)" }
}
} | Format-Table -AutoSize
}
Feature Value
------- -----
AcrobatReader ...
Accessibility AcrobatReader...
BrowserIntegration AcrobatReader...
L'arborescence Patches¶
Les patches (fichiers .msp) appliques a un produit sont enregistres sous :
Chaque entree contient le chemin vers le fichier .msp cache localement et des metadonnees sur le patch.
UserData : les donnees detaillees¶
L'arborescence UserData contient les informations d'installation detaillees, indexees par SID utilisateur :
Pour les installations per-machine, le SID utilise est S-1-5-18 (compte SYSTEM).
| Sous-cle | Contenu |
|---|---|
InstallProperties | Chemin d'installation, source du .msi, date, taille |
Features | Liste des fonctionnalites installees |
Patches | Patches appliques a ce produit |
Usage | Statistiques d'utilisation des fonctionnalites |
# Explore UserData for a specific product installed per-machine
$sid = "S-1-5-18"
$basePath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\$sid\Products"
if (Test-Path $basePath) {
Get-ChildItem $basePath | Select-Object -First 5 | ForEach-Object {
$installProps = Get-ItemProperty "$($_.PSPath)\InstallProperties" -ErrorAction SilentlyContinue
if ($installProps.DisplayName) {
[PSCustomObject]@{
CompressedGuid = $_.PSChildName
DisplayName = $installProps.DisplayName
InstallDate = $installProps.InstallDate
InstallLocation = $installProps.InstallLocation
InstallSource = $installProps.InstallSource
}
}
} | Format-Table -AutoSize
}
CompressedGuid DisplayName InstallDate InstallLocation InstallSource
-------------- ----------- ----------- --------------- -------------
68AB67CA7DA73301B744CAF070E41400 Adobe Acrobat Reader DC 20240201 C:\Program Files\Adobe\Acrobat DC C:\Windows\Installer\
1E5D742E02F66B740BB72A9A32C5D83C Microsoft Visual C++ 2022 X64 Minimum 20240115 C:\Program Files\Microsoft Visu.. C:\ProgramData\Pack..
...
En resume
- Les produits sont enregistres sous HKCR\Installer\Products avec leur GUID comprime, la version encodee et les drapeaux de publication
- Les composants (unites atomiques d'installation) pointent vers un keypath (fichier, cle de registre ou dossier) qui confirme leur presence
- L'arborescence UserData sous Installer\UserData{SID}\Products contient les details d'installation indexes par SID
L'arborescence Uninstall¶
L'arborescence Uninstall est la plus visible : c'est elle que "Programmes et fonctionnalites" (et "Applications installees" dans les parametres Windows 11) interroge pour afficher la liste des logiciels.
Les trois emplacements¶
Les applications s'inscrivent dans l'un de ces trois chemins selon leur architecture et leur mode d'installation :
| Emplacement | Cas d'utilisation |
|---|---|
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | Applications per-machine 64 bits |
HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall | Applications per-machine 32 bits sur un systeme 64 bits |
HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | Applications per-user |
WOW6432Node et la redirection
Sur un systeme 64 bits, une application 32 bits qui ecrit dans HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall est automatiquement redirigee vers HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall par le WoW64 Registry Reflector. L'application ne voit pas la difference, mais l'administrateur doit savoir qu'il faut verifier les deux emplacements.
Structure d'une entree Uninstall¶
Chaque sous-cle porte le nom du {ProductCode} pour les produits MSI, ou un identifiant libre pour les installeurs non-MSI (ex: Google Chrome, 7-Zip).
| Valeur | Type | Description | Obligatoire |
|---|---|---|---|
DisplayName | REG_SZ | Nom affiche dans la liste | Oui |
DisplayVersion | REG_SZ | Version affichee (ex: 17.3.1) | Non |
Publisher | REG_SZ | Nom de l'editeur | Non |
InstallDate | REG_SZ | Date d'installation au format YYYYMMDD | Non |
InstallLocation | REG_SZ | Dossier d'installation | Non |
InstallSource | REG_SZ | Chemin vers la source du .msi ou de l'installeur | Non |
UninstallString | REG_SZ | Commande de desinstallation (avec interface) | Oui |
QuietUninstallString | REG_SZ | Commande de desinstallation silencieuse | Non |
ModifyPath | REG_SZ | Commande pour modifier l'installation | Non |
EstimatedSize | REG_DWORD | Taille estimee en Ko | Non |
NoModify | REG_DWORD | 1 = masquer le bouton "Modifier" | Non |
NoRepair | REG_DWORD | 1 = masquer le bouton "Reparer" | Non |
NoRemove | REG_DWORD | 1 = masquer le bouton "Desinstaller" | Non |
SystemComponent | REG_DWORD | 1 = masquer completement de la liste | Non |
WindowsInstaller | REG_DWORD | 1 = indique un produit MSI (desinstallation via msiexec) | Non |
DisplayIcon | REG_SZ | Chemin vers l'icone affichee (format fichier.exe,index) | Non |
URLInfoAbout | REG_SZ | URL du site web du produit | Non |
URLUpdateInfo | REG_SZ | URL de la page de mise a jour | Non |
HelpLink | REG_SZ | URL du support technique | Non |
HelpTelephone | REG_SZ | Numero de telephone du support | Non |
Contact | REG_SZ | Nom du contact de support | Non |
Comments | REG_SZ | Commentaires supplementaires | Non |
VersionMajor | REG_DWORD | Numero de version majeure | Non |
VersionMinor | REG_DWORD | Numero de version mineure | Non |
Language | REG_DWORD | Code de langue LCID | Non |
SystemComponent et WindowsInstaller
Quand SystemComponent vaut 1, le produit n'apparait dans aucune interface graphique de gestion des programmes. Les composants redistribuables (Visual C++ Runtime, .NET Runtime) utilisent souvent cette valeur pour cacher les sous-composants tout en affichant un produit parent. Quand WindowsInstaller vaut 1, Windows sait qu'il doit utiliser msiexec /x {ProductCode} pour desinstaller, meme si UninstallString est absente.
Script d'enumeration complete¶
Ce script interroge les trois emplacements et produit une liste unifiee de tous les logiciels installes :
# Enumerate all installed software from all three Uninstall registry locations
$uninstallPaths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
)
$allSoftware = foreach ($path in $uninstallPaths) {
if (Test-Path $path) {
Get-ChildItem $path | ForEach-Object {
$props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
if ($props.DisplayName -and $props.SystemComponent -ne 1) {
[PSCustomObject]@{
Name = $props.DisplayName
Version = $props.DisplayVersion
Publisher = $props.Publisher
InstallDate = $props.InstallDate
Size_MB = if ($props.EstimatedSize) { [math]::Round($props.EstimatedSize / 1024, 1) } else { $null }
Architecture = if ($path -match "WOW6432Node") { "x86" } else { "x64" }
Scope = if ($path -match "HKCU") { "Per-user" } else { "Per-machine" }
IsMSI = $props.WindowsInstaller -eq 1
ProductCode = $_.PSChildName
}
}
}
}
}
$allSoftware | Sort-Object Name | Format-Table Name, Version, Publisher, Architecture, Scope, IsMSI -AutoSize
Write-Host "`nTotal: $($allSoftware.Count) applications"
Name Version Publisher Architecture Scope IsMSI
---- ------- --------- ------------ ----- -----
7-Zip 23.01 (x64) 23.01 Igor Pavlov x64 Per-machine False
Adobe Acrobat Reader DC 23.008.20533 Adobe Inc. x64 Per-machine True
Git 2.43.0 The Git Development Comm x64 Per-machine False
Microsoft Visual C++ 2022 X64 Minimum Runtime 14.38.33130 Microsoft Corporation x64 Per-machine True
...
Total: 87 applications
Pourquoi pas Get-WmiObject Win32_Product ?
La classe WMI Win32_Product est tentante mais dangereuse en production. A chaque enumeration, elle declenche une verification de coherence de chaque produit MSI, ce qui equivaut a executer msiexec /f en arriere-plan. Cela ralentit considerablement la requete et peut modifier l'etat du systeme en declenchant des reparations inattendues. Preferez toujours la lecture directe du registre.
En resume
- L'arborescence Uninstall existe en trois exemplaires : 64 bits, 32 bits (WOW6432Node) et per-user (HKCU)
- DisplayName et UninstallString sont les valeurs essentielles ; SystemComponent = 1 masque un produit de l'interface graphique
- Interrogez toujours les cles Uninstall directement au lieu de Win32_Product qui declenche des verifications de coherence couteuses
Patches et upgrades¶
Enregistrement des patches¶
Quand un fichier .msp (Microsoft Installer Patch) est applique, MSI l'enregistre a deux endroits :
Niveau global :
Niveau UserData :
Chaque entree de patch contient :
| Valeur | Type | Description |
|---|---|---|
LocalPackage | REG_SZ | Chemin vers le .msp cache dans C:\Windows\Installer |
State | REG_DWORD | Etat du patch : 1 = applique, 2 = obsolete |
Uninstallable | REG_DWORD | 1 = le patch peut etre desinstalle individuellement |
DisplayName | REG_SZ | Nom affiche du patch |
MoreInfoURL | REG_SZ | URL avec des informations supplementaires |
Installed | REG_SZ | Date d'installation du patch |
La relation entre un produit et ses patches est enregistree sous :
Cette cle contient une valeur Patches de type REG_MULTI_SZ qui liste les GUIDs comprimes de tous les patches appliques, dans l'ordre d'application.
Types de mises a jour et impact registre¶
MSI distingue trois types de mises a jour, chacun avec un impact different sur le registre :
| Type | ProductCode change ? | PackageCode change ? | Version change ? | Impact registre |
|---|---|---|---|---|
| Small update | Non | Oui | Non | Seul le PackageCode est mis a jour dans Products |
| Minor update | Non | Oui | Oui | PackageCode et Version mis a jour ; patch ajoute a la liste |
| Major upgrade | Oui | Oui | Oui | Ancien produit completement supprime, nouveau produit inscrit |
flowchart LR
A["Produit v1.0\nProductCode: A\nUpgradeCode: X"] -->|"Small update"| B["Produit v1.0\nProductCode: A\nPackageCode modifie"]
A -->|"Minor update"| C["Produit v1.1\nProductCode: A\nVersion modifiee"]
A -->|"Major upgrade"| D["Produit v2.0\nProductCode: B\nUpgradeCode: X"]
D -.->|"Supprime"| E["Ancien ProductCode: A\n(efface du registre)"] Major upgrade en detail¶
Lors d'une major upgrade, MSI utilise l'UpgradeCode pour localiser les versions precedentes. Le processus suit cet ordre :
- MSI lit la table
Upgradedu nouveau .msi - Il recherche dans
HKCR\Installer\UpgradeCodes\{GUID comprime de l'UpgradeCode}les produits existants - L'action
FindRelatedProductsidentifie les versions a supprimer - L'action
RemoveExistingProductsdesinstalle les anciennes versions - Le nouveau produit s'installe et s'enregistre avec un nouveau
ProductCode
# Find all products sharing the same UpgradeCode
$upgradeCodePath = "HKLM:\SOFTWARE\Classes\Installer\UpgradeCodes"
if (Test-Path $upgradeCodePath) {
Get-ChildItem $upgradeCodePath | ForEach-Object {
$products = (Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue).PSObject.Properties |
Where-Object { $_.Name -notlike "PS*" }
if ($products.Count -gt 1) {
[PSCustomObject]@{
UpgradeCode = $_.PSChildName
ProductCount = $products.Count
ProductGuids = ($products.Name -join ", ")
}
}
} | Select-Object -First 5 | Format-Table -AutoSize
}
UpgradeCode ProductCount ProductGuids
----------- ------------ ------------
2DA521E6F3C04B740BB72A9A32C5D83C 3 1E5D742E02F66B74..., 3F8A6B2C01D94E5A..., 9C7D4E5F02A83B61...
5FB832A4C7E61D900EE83C2B4A6F1D5E 2 68AB67CA7DA73301..., 7B9C8D0E1F2A3B4C...
...
Patches obsoletes (superseded)¶
Quand un nouveau patch remplace un ancien, MSI marque l'ancien comme obsolete (superseded) en changeant sa valeur State a 2. Le fichier .msp cache reste dans C:\Windows\Installer mais n'est plus actif. Cela explique pourquoi le dossier C:\Windows\Installer grossit au fil du temps.
Ne supprimez jamais C:\Windows\Installer
Le dossier C:\Windows\Installer contient les fichiers .msi et .msp caches necessaires a la desinstallation, reparation et mise a jour des produits. Supprimer son contenu rend la gestion des logiciels MSI impossible et peut corrompre l'etat du systeme.
En resume
- Les patches (.msp) sont enregistres sous HKCR\Installer\Patches avec un etat (1 = applique, 2 = obsolete)
- MSI distingue trois types de mises a jour : small update (PackageCode), minor update (version), major upgrade (nouveau ProductCode)
- Une major upgrade supprime completement l'ancien produit du registre puis inscrit le nouveau via l'UpgradeCode
Custom Actions et le registre¶
Les tables MSI liees au registre¶
Un fichier .msi est une base de donnees relationnelle qui contient plusieurs tables pour gerer les ecritures de registre :
| Table MSI | Fonction |
|---|---|
Registry | Ecrit des valeurs dans le registre lors de l'installation |
RemoveRegistry | Supprime des cles ou valeurs lors de l'installation |
RegLocator | Recherche des valeurs dans le registre (conditions d'installation) |
AppSearch | Utilise RegLocator pour definir des proprietes |
Upgrade | Recherche des produits existants via le registre |
La table Registry¶
La table Registry est la methode standard pour ecrire dans le registre via MSI. Chaque ligne definit une operation d'ecriture :
| Colonne | Type | Description |
|---|---|---|
Registry | Identifiant | Cle primaire unique de la ligne |
Root | Entier | Racine du registre cible |
Key | Texte | Chemin de la cle de registre (sans la racine) |
Name | Texte | Nom de la valeur (null = valeur par defaut) |
Value | Texte | Donnee a ecrire (supporte les formatters MSI) |
Component_ | Identifiant | Composant associe (la valeur n'est ecrite que si le composant est installe) |
Les valeurs de la colonne Root :
| Valeur | Racine | Commentaire |
|---|---|---|
0 | HKCR | HKEY_CLASSES_ROOT |
1 | HKCU | HKEY_CURRENT_USER |
2 | HKLM | HKEY_LOCAL_MACHINE |
3 | HKU | HKEY_USERS |
-1 | Variable | HKCU pour per-user, HKLM pour per-machine |
Root = -1 : le choix intelligent
La valeur -1 pour Root est une bonne pratique. Elle laisse MSI decider de la racine en fonction du mode d'installation (ALLUSERS). Si ALLUSERS=1, l'ecriture va dans HKLM. Sinon, elle va dans HKCU. Cela rend le meme package compatible avec les deux modes.
La table RemoveRegistry¶
La table RemoveRegistry fonctionne de maniere symetrique : elle supprime des cles ou valeurs lors de l'installation (pas de la desinstallation). Sa structure est identique a la table Registry sans la colonne Value.
Sequencement et rollback¶
Les actions d'ecriture de registre s'executent dans l'InstallExecuteSequence. L'ordre typique est :
flowchart TD
A["InstallValidate"] --> B["InstallInitialize"]
B --> C["ProcessComponents\n(enregistre les composants)"]
C --> D["WriteRegistryValues\n(ecrit les valeurs de la table Registry)"]
D --> E["RemoveRegistryValues\n(supprime les valeurs de RemoveRegistry)"]
E --> F["RegisterProduct\n(inscrit le produit dans HKCR\\Installer)"]
F --> G["PublishProduct\n(publie dans Uninstall)"]
G --> H["InstallFinalize\n(valide la transaction)"]
H -.->|"Echec"| I["Rollback\n(restaure l'etat initial)"] Le mecanisme de rollback est une des forces de MSI. Avant chaque ecriture de registre, MSI sauvegarde la valeur existante dans un script de rollback (fichier .rbs dans un dossier temporaire). Si l'installation echoue a n'importe quelle etape, MSI execute ce script en sens inverse pour restaurer le registre a son etat exact d'avant l'installation.
# Watch MSI registry writes in real-time with Process Monitor filters
# (requires Sysinternals Process Monitor)
# Recommended filters:
# Process Name = msiexec.exe
# Operation begins with "Reg"
# Result = SUCCESS
# Export the results to analyze the exact sequence of registry operations
Custom Actions de type 51 et registre
Les Custom Actions de type 51 (set property) ne touchent pas directement le registre. En revanche, les Custom Actions de types 1 (DLL), 2 (EXE), 5-6 (JScript/VBScript) et 34 (directory) peuvent ecrire dans le registre sans passer par le systeme de rollback MSI. Si une telle action echoue, les modifications de registre qu'elle a effectuees ne seront pas annulees. C'est pourquoi les Custom Actions qui modifient le systeme doivent toujours etre accompagnees d'une action de rollback correspondante.
En resume
- La table Registry du fichier .msi definit les ecritures de registre standards avec rollback automatique en cas d'echec
- Root = -1 dans la table Registry laisse MSI choisir entre HKCU et HKLM selon le mode d'installation (ALLUSERS)
- Les Custom Actions de type DLL/EXE ecrivent dans le registre sans passer par le rollback MSI : elles necessitent une action de rollback dediee
Reparation et resilience¶
Le mecanisme d'auto-reparation¶
L'une des fonctionnalites les plus distinctives de Windows Installer est sa capacite a detecter et reparer automatiquement les installations corrompues. Ce mecanisme s'appelle la resilience MSI.
Le processus se declenche dans deux situations :
- Lancement d'un raccourci publie : les raccourcis MSI (advertised shortcuts) contiennent une reference au ProductCode. Quand l'utilisateur clique, Windows verifie d'abord l'integrite de l'installation
- Acces a un composant COM publie : quand une application tente d'instancier un objet COM enregistre par MSI, le systeme verifie que le composant est intact
Verification par keypath¶
A chaque declenchement, MSI verifie la presence du keypath de chaque composant de la fonctionnalite sollicitee :
flowchart TD
A["Utilisateur clique\nsur un raccourci MSI"] --> B["Windows Installer\nverifie le ProductCode"]
B --> C["Pour chaque composant\nde la feature activee"]
C --> D{"Le keypath\nexiste ?"}
D -->|"Oui"| E["Composant OK\n(passe au suivant)"]
D -->|"Non"| F["Composant manquant !\nReparation declenchee"]
F --> G["MSI reinstalle\nle composant"]
G --> H["Ecritures registre\nrestaurees"]
E --> I{"Tous les composants\nverifies ?"}
I -->|"Non"| C
I -->|"Oui"| J["Lancement de\nl'application"]
H --> I Si un fichier keypath est manquant ou si une cle de registre keypath est absente, MSI declenche une reparation silencieuse qui reinstalle les composants defaillants.
Cles de controle de la reparation¶
Plusieurs cles de registre influencent le comportement de la reparation :
Strategies Windows Installer :
| Valeur | Type | Description |
|---|---|---|
DisableRollback | REG_DWORD | 1 = desactive le rollback (acceleration de l'installation au detriment de la securite) |
MaxPatchCacheSize | REG_DWORD | Taille maximale du cache de patches en pourcentage du disque (defaut: 10) |
AlwaysInstallElevated | REG_DWORD | 1 = installe avec les privileges eleves (voir section securite) |
DisableMSI | REG_DWORD | Controle l'autorisation d'installation (voir section securite) |
DisableBrowse | REG_DWORD | 1 = desactive le bouton "Parcourir" dans les boites de dialogue MSI |
Logging | REG_SZ | Active la journalisation automatique (ex: voicewarmupx pour un log complet) |
Debug | REG_DWORD | 7 = active le mode debug du moteur MSI |
EnableUserControl | REG_DWORD | 1 = autorise les utilisateurs a modifier les proprietes publiques |
LimitSystemRestoreCheckpointing | REG_DWORD | 1 = limite les points de restauration crees par MSI |
TransformsSecure | REG_DWORD | 1 = les transformations (.mst) sont stockees dans un emplacement securise |
SafeForScripting | REG_DWORD | 1 = autorise les controles ActiveX MSI dans les pages web (obsolete) |
Reparation manuelle¶
La commande msiexec /f permet de declencher une reparation manuelle avec des drapeaux granulaires :
rem Full repair: reinstall files, registry, shortcuts, and re-validate
msiexec /fa {AC76BA86-7AD7-1033-7B44-AC0F074E4100}
Les drapeaux de reparation :
| Drapeau | Signification |
|---|---|
/fp | Reinstalle les fichiers manquants |
/fo | Reinstalle les fichiers si la version est identique ou plus ancienne |
/fe | Reinstalle les fichiers si la version est identique ou plus recente |
/fd | Reinstalle les fichiers si la version est differente |
/fa | Force la reinstallation de tous les fichiers |
/fu | Reecrit les cles de registre per-user |
/fm | Reecrit les cles de registre per-machine |
/fs | Reinstalle les raccourcis |
/fc | Reinstalle si le fichier est manquant ou si le checksum ne correspond pas |
/fv | Reinstalle depuis le package source (re-cache le .msi local) |
# Repair only registry entries for a product (per-user and per-machine)
$productCode = "{AC76BA86-7AD7-1033-7B44-AC0F074E4100}"
Start-Process "msiexec.exe" -ArgumentList "/fum $productCode /qn" -Wait -NoNewWindow
Write-Host "Registry repair completed for $productCode"
Desactiver l'auto-reparation pour un produit specifique¶
Dans certains cas, l'auto-reparation provoque des boucles de reparation indesirables. Pour desactiver l'auto-reparation d'un produit specifique, vous pouvez supprimer ses raccourcis publies ou desactiver la fonctionnalite globalement :
# Disable MSI auto-repair globally (not recommended in production)
# This setting prevents the repair check on advertised shortcuts
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Installer" `
-Name "DisableAutoPatch" -Value 1 -Type DWord
# Alternatively, convert advertised shortcuts to standard shortcuts
# This removes the repair trigger for a specific product
$shortcutPath = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\MyApp.lnk"
if (Test-Path $shortcutPath) {
$shell = New-Object -ComObject WScript.Shell
$shortcut = $shell.CreateShortcut($shortcutPath)
# Re-save the shortcut as a standard (non-advertised) shortcut
$shortcut.TargetPath = "C:\Program Files\MyApp\MyApp.exe"
$shortcut.Save()
Write-Host "Converted advertised shortcut to standard shortcut"
}
En resume
- L'auto-reparation MSI verifie le keypath de chaque composant au lancement d'un raccourci publie et reinstalle silencieusement les elements manquants
- Les strategies sous HKLM...\Policies\Microsoft\Windows\Installer controlent le rollback, le cache de patches et la journalisation
- Les boucles de reparation indesirables se resolvent en convertissant les raccourcis publies en raccourcis standards
MSI et les permissions¶
Installation per-machine vs per-user¶
Le mode d'installation determine les emplacements du registre et les permissions requises :
| Aspect | Per-machine (ALLUSERS=1) | Per-user (ALLUSERS="") |
|---|---|---|
| Registre produit | HKLM\SOFTWARE\Classes\Installer\Products | HKCU\SOFTWARE\Classes\Installer\Products |
| Registre Uninstall | HKLM\...\Uninstall | HKCU\...\Uninstall |
| UserData SID | S-1-5-18 (SYSTEM) | SID de l'utilisateur |
| Privileges requis | Administrateur | Aucun |
| Visible pour tous les utilisateurs | Oui | Non (seulement l'utilisateur qui a installe) |
AlwaysInstallElevated : le risque de securite majeur¶
La strategie AlwaysInstallElevated est l'un des parametres les plus dangereux de Windows Installer. Quand elle est activee simultanement dans HKLM et HKCU, tout utilisateur peut installer un package MSI avec les privileges SYSTEM.
HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer
AlwaysInstallElevated = 1
HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer
AlwaysInstallElevated = 1
Elevation de privilege critique
Un attaquant peut creer un .msi malveillant contenant une Custom Action qui execute du code arbitraire. Si AlwaysInstallElevated est active dans les deux ruches, ce code s'executera avec les privileges NT AUTHORITY\SYSTEM. C'est un vecteur d'attaque bien connu et recherche activement par les outils de pentest comme PowerUp, Metasploit et winPEAS.
# Check if AlwaysInstallElevated is enabled (security audit)
$hklmValue = Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Installer" `
-Name "AlwaysInstallElevated" -ErrorAction SilentlyContinue
$hkcuValue = Get-ItemProperty "HKCU:\SOFTWARE\Policies\Microsoft\Windows\Installer" `
-Name "AlwaysInstallElevated" -ErrorAction SilentlyContinue
$hklmEnabled = $hklmValue.AlwaysInstallElevated -eq 1
$hkcuEnabled = $hkcuValue.AlwaysInstallElevated -eq 1
if ($hklmEnabled -and $hkcuEnabled) {
Write-Host "[CRITICAL] AlwaysInstallElevated is enabled in BOTH HKLM and HKCU!" -ForegroundColor Red
Write-Host " Any user can install MSI packages with SYSTEM privileges." -ForegroundColor Red
} elseif ($hklmEnabled -or $hkcuEnabled) {
Write-Host "[WARNING] AlwaysInstallElevated is partially enabled." -ForegroundColor Yellow
Write-Host " HKLM: $hklmEnabled | HKCU: $hkcuEnabled" -ForegroundColor Yellow
} else {
Write-Host "[OK] AlwaysInstallElevated is not enabled." -ForegroundColor Green
}
Table complete des strategies MSI¶
| Valeur | Type | Description | Impact securite |
|---|---|---|---|
AlwaysInstallElevated | REG_DWORD | 1 = elevation automatique | Critique — permet l'execution de code en tant que SYSTEM |
DisableMSI | REG_DWORD | 0 = jamais desactive, 1 = desactive pour non-admin, 2 = toujours desactive | Moyen — bloque les installations non autorisees |
EnableUserControl | REG_DWORD | 1 = utilisateurs peuvent modifier les proprietes publiques | Faible — facilite les installations personnalisees |
AllowLockdownBrowse | REG_DWORD | 1 = autorise la navigation dans les sources en mode restreint | Faible |
AllowLockdownMedia | REG_DWORD | 1 = autorise l'utilisation de sources media en mode restreint | Faible |
AllowLockdownPatch | REG_DWORD | 1 = autorise l'application de patches en mode restreint | Moyen |
DisablePatch | REG_DWORD | 1 = interdit l'application de patches | Moyen — empeche les mises a jour |
DisableRollback | REG_DWORD | 1 = desactive le rollback | Moyen — empeche la restauration en cas d'echec |
TransformsSecure | REG_DWORD | 1 = force le stockage securise des transforms | Faible |
DisableAutoPatch | REG_DWORD | 1 = desactive l'application automatique de patches | Faible |
Le service Windows Installer (msiserver)¶
Le service msiserver est le moteur de Windows Installer. Il s'execute sous le compte LocalSystem et gere toutes les operations d'installation, reparation et desinstallation.
| Valeur | Donnee | Description |
|---|---|---|
Start | 3 (Manuel) | Demarre a la demande (quand msiexec est invoque) |
Type | 16 (Own Process) | S'execute dans son propre processus |
ImagePath | %systemroot%\system32\msiexec.exe /V | Chemin de l'executable avec le flag service |
ObjectName | LocalSystem | Compte de service |
Pourquoi msiexec s'execute-t-il en tant que SYSTEM ?
Meme quand un utilisateur standard lance une installation, le service msiserver effectue les operations privilegiees (ecriture dans HKLM, installation de fichiers dans Program Files) sous le compte SYSTEM. L'interface utilisateur et les dialogues s'executent dans le contexte de l'utilisateur, mais toutes les modifications systeme passent par le service. C'est cette separation qui permet a MSI de gerer les installations per-machine sans donner les droits admin a l'utilisateur (dans certains cas).
En resume
- AlwaysInstallElevated est un risque critique quand il est active dans HKLM et HKCU simultanement (execution de code en tant que SYSTEM)
- DisableMSI controle qui peut installer des packages MSI ; le service msiserver s'execute sous LocalSystem pour les operations privilegiees
- Les strategies sous HKLM...\Policies\Microsoft\Windows\Installer couvrent la securite, le rollback, le patching et la journalisation
Diagnostique et depannage¶
Journalisation verbose¶
La premiere etape de tout depannage MSI est d'activer la journalisation verbose :
rem Install with verbose logging
msiexec /i "MonProduit.msi" /l*v "C:\Temp\msi-install.log"
rem Uninstall with verbose logging
msiexec /x {AC76BA86-7AD7-1033-7B44-AC0F074E4100} /l*v "C:\Temp\msi-uninstall.log"
La fenetre de progression MSI s'affiche. Le fichier journal est cree au chemin specifie.
Les drapeaux de journalisation :
| Drapeau | Information journalisee |
|---|---|
/li | Messages de statut |
/lw | Avertissements non fatals |
/le | Messages d'erreur |
/la | Demarrage des actions |
/lr | Enregistrements specifiques aux actions |
/lu | Requetes utilisateur |
/lc | Parametres initiaux de l'interface |
/lm | Messages d'insuffisance memoire ou de sortie fatale |
/lo | Messages de depassement d'espace disque |
/lp | Proprietes du terminal |
/lv | Sortie verbose |
/lx | Informations de debug supplementaires |
/l+ | Ajouter au fichier existant |
/l! | Vider chaque ligne dans le log (pas de mise en tampon) |
/l* | Journaliser toutes les informations sauf verbose |
/l*v | Journaliser tout (le plus complet) |
Pour activer la journalisation permanente (tous les MSI seront logues) :
# Enable global MSI logging via registry
# Value contains the logging flags to use
New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Installer" `
-Name "Logging" -Value "voicewarmupx" -PropertyType String -Force
# Logs will be created in %TEMP% as MSI*.LOG
Write-Host "MSI global logging enabled. Logs will appear in: $env:TEMP"
Impact sur les performances
La journalisation globale ralentit toutes les installations MSI. Ne l'activez que temporairement pour le diagnostic.
Produits orphelins¶
Un produit orphelin est un produit enregistre dans le registre mais dont les fichiers n'existent plus sur le disque. Cela se produit quand des fichiers sont supprimes manuellement sans passer par la desinstallation MSI.
# Detect orphaned MSI products (registered but install location missing)
$uninstallPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
Get-ChildItem $uninstallPath | ForEach-Object {
$props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
if ($props.WindowsInstaller -eq 1 -and $props.InstallLocation) {
$location = $props.InstallLocation.TrimEnd('\')
if ($location -and -not (Test-Path $location)) {
[PSCustomObject]@{
ProductCode = $_.PSChildName
DisplayName = $props.DisplayName
InstallLocation = $location
Status = "ORPHANED"
}
}
}
} | Format-Table -AutoSize
ProductCode DisplayName InstallLocation Status
----------- ----------- --------------- ------
{B7A4F2E1-3C8D-4E5F-A6B0-9D1C2E3F4A5B} OldApp Pro 2.1 D:\Programs\OldApp ORPHANED
{C8B5A3F2-4D9E-5F60-B7C1-AE2D3F4050C6} Legacy Tool Suite C:\Program Files\LegacyTool ORPHANED
Composants orphelins¶
Un composant orphelin pointe vers un produit qui n'est plus enregistre. Cela peut provoquer des erreurs lors des reparations ou des nouvelles installations.
# Detect orphaned components (pointing to non-existent products)
$productsPath = "HKLM:\SOFTWARE\Classes\Installer\Products"
$existingProducts = @{}
if (Test-Path $productsPath) {
Get-ChildItem $productsPath | ForEach-Object { $existingProducts[$_.PSChildName] = $true }
}
$orphanedComponents = 0
$componentsPath = "HKLM:\SOFTWARE\Classes\Installer\Components"
if (Test-Path $componentsPath) {
Get-ChildItem $componentsPath | ForEach-Object {
$componentGuid = $_.PSChildName
$refs = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
$refs.PSObject.Properties | Where-Object { $_.Name -notlike "PS*" } | ForEach-Object {
if (-not $existingProducts.ContainsKey($_.Name)) {
$orphanedComponents++
}
}
}
}
Write-Host "Orphaned component references found: $orphanedComponents"
Nettoyage manuel¶
Nettoyage du registre MSI : procedure risquee
La suppression manuelle d'entrees MSI dans le registre peut rendre la desinstallation, la mise a jour et la reparation d'un produit impossibles. Ne procedez au nettoyage manuel que si toutes les autres methodes ont echoue.
L'ancien outil msizap.exe (inclus dans le Windows SDK) a ete deprecie par Microsoft. La methode recommandee pour nettoyer un produit MSI bloque est l'outil Windows Installer Cleanup Utility (egalement deprecie) ou le Microsoft Program Install and Uninstall Troubleshooter (MicrosoftProgram_Install_and_Uninstall.meta.diagcab).
En dernier recours, la procedure manuelle pour supprimer un produit MSI du registre :
# Manual MSI product cleanup procedure
# WARNING: Only use this after all other methods have failed
param(
[Parameter(Mandatory)]
[string]$ProductCode # Format: {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
)
# Step 1: Compute the compressed GUID
$clean = $ProductCode -replace '[{}\-]', ''
$groups = @(8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2)
$compressed = ""
$pos = 0
foreach ($len in $groups) {
$chars = $clean.Substring($pos, $len).ToCharArray()
[Array]::Reverse($chars)
$compressed += -join $chars
$pos += $len
}
Write-Host "ProductCode: $ProductCode"
Write-Host "Compressed GUID: $compressed"
# Step 2: List all registry locations to clean (DO NOT delete without confirmation)
$locations = @(
"HKLM:\SOFTWARE\Classes\Installer\Products\$compressed",
"HKLM:\SOFTWARE\Classes\Installer\Features\$compressed",
"HKLM:\SOFTWARE\Classes\Installer\UpgradeCodes",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\$compressed",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$ProductCode",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$ProductCode"
)
foreach ($loc in $locations) {
if (Test-Path $loc) {
Write-Host "[EXISTS] $loc" -ForegroundColor Yellow
} else {
Write-Host "[ABSENT] $loc" -ForegroundColor Gray
}
}
Write-Host "`nReview the [EXISTS] entries above. Remove them only if you are certain."
ProductCode: {AC76BA86-7AD7-1033-7B44-AC0F074E4100}
Compressed GUID: 68AB67CA7DA73301B744CAF070E41400
[EXISTS] HKLM:\SOFTWARE\Classes\Installer\Products\68AB67CA7DA73301B744CAF070E41400
[EXISTS] HKLM:\SOFTWARE\Classes\Installer\Features\68AB67CA7DA73301B744CAF070E41400
[EXISTS] HKLM:\SOFTWARE\Classes\Installer\UpgradeCodes
[EXISTS] HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\68AB67CA7DA73301B744CAF070E41400
[EXISTS] HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{AC76BA86-7AD7-1033-7B44-AC0F074E4100}
[ABSENT] HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{AC76BA86-7AD7-1033-7B44-AC0F074E4100}
Review the [EXISTS] entries above. Remove them only if you are certain.
Outils de diagnostic¶
| Outil | Description | Source |
|---|---|---|
| Orca | Editeur officiel de tables MSI (inclus dans le Windows SDK) | Microsoft |
| InstEd | Editeur MSI gratuit avec interface moderne | instedd.com |
| lessmsi | Extracteur de contenu MSI sans installation | github.com/activescott/lessmsi |
| Super Orca | Editeur MSI gratuit, alternative a Orca | pantaray.com |
| MSI Viewer | Visualiseur MSI leger | github.com |
| Process Monitor | Trace les operations de registre en temps reel | Sysinternals |
| WiLogUtl.exe | Analyseur de logs MSI (inclus dans le Windows SDK) | Microsoft |
L'alternative a Win32_Product¶
Comme mentionne plus haut, Get-WmiObject Win32_Product (ou Get-CimInstance Win32_Product) est a eviter. Voici la methode recommandee pour interroger les logiciels installes sans declencher de reparation :
# Fast software inventory without triggering MSI repair checks
function Get-InstalledSoftware {
$paths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
foreach ($path in $paths) {
Get-ItemProperty $path -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -and $_.SystemComponent -ne 1 } |
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate,
@{N='Size_MB'; E={ if($_.EstimatedSize) { [math]::Round($_.EstimatedSize/1024,1) } }},
@{N='IsMSI'; E={ $_.WindowsInstaller -eq 1 }},
@{N='ProductCode'; E={ $_.PSChildName }}
}
}
# Usage: fast query, no side effects
Get-InstalledSoftware | Sort-Object DisplayName | Format-Table -AutoSize
DisplayName DisplayVersion Publisher InstallDate Size_MB IsMSI ProductCode
----------- -------------- --------- ----------- ------- ----- -----------
7-Zip 23.01 (x64) 23.01 Igor Pavlov 20240115 5.2 False 7-Zip
Adobe Acrobat Reader DC 23.008.20533 Adobe Inc. 20240201 412.3 True {AC76BA86-...}
Git 2.43.0 The Git Development.. 20240110 324.1 False Git_is1
...
Performance
Cette fonction s'execute en moins d'une seconde sur la plupart des systemes, contre plusieurs minutes pour Get-CimInstance Win32_Product. De plus, elle ne declenche aucune verification de coherence et ne modifie rien.
En resume
- La journalisation verbose (
/l*v) est la premiere etape de tout depannage MSI ; la cle Logging = "voicewarmupx" active la journalisation permanente - Les produits orphelins (enregistres mais fichiers manquants) et les composants orphelins (pointant vers des produits supprimes) sont detectables par script
- La suppression manuelle d'entrees MSI dans le registre est un dernier recours ; utilisez le Microsoft Program Install and Uninstall Troubleshooter en priorite
De MSI a MSIX¶
Le probleme fondamental de MSI¶
L'architecture MSI repose sur un principe ancien : l'installeur modifie le systeme global — registre, systeme de fichiers, variables d'environnement. Ce modele a deux faiblesses majeures :
- Pollution du registre : chaque produit MSI dissemine ses donnees dans de multiples emplacements du registre, rendant le nettoyage complet difficile
- Conflits entre applications : deux produits MSI peuvent ecrire dans les memes cles de registre, provoquant des conflits imprevisibles
MSIX, le format de packaging moderne de Microsoft, resout ces problemes en virtualisant le registre par paquet (voir le chapitre 21 — Applications modernes).
Comparaison MSI vs MSIX¶
| Aspect | MSI | MSIX |
|---|---|---|
| Modele de registre | Ecriture directe dans le registre global (HKLM, HKCU) | Registre virtualise par paquet (registry.dat fusionne en lecture seule) |
| Isolation | Aucune — acces complet au registre systeme | Conteneur isole — ecritures redirigees vers user.dat |
| Desinstallation | Suppression sequentielle des cles enregistrees (risque de residus) | Suppression atomique du conteneur entier |
| Conflit entre apps | Possible (cles partagees, DLL Hell) | Impossible (chaque app a son propre registre) |
| Rollback | Script de rollback genere a l'installation | Pas necessaire (installation atomique) |
| Reparation | Auto-reparation par verification des keypaths | Pas necessaire (integrite garantie par le conteneur) |
| Taille dans le registre | Dizaines de cles reparties dans 5+ arborescences | Aucune cle dans le registre global (sauf Uninstall) |
| Mise a jour | Major upgrade = desinstallation + reinstallation | Mise a jour differentielle |
| Permissions | Necessite souvent des privileges admin (per-machine) | Installation per-user par defaut, sans elevation |
| Registre Uninstall | Inscrit dans les 3 emplacements standards | Inscrit automatiquement par le systeme |
Conversion MSI vers MSIX¶
L'outil MSIX Packaging Tool de Microsoft capture les ecritures de registre d'un installeur MSI et les convertit en un fichier registry.dat integre au package MSIX.
# Check if MSIX Packaging Tool is available
$msixTool = Get-AppxPackage -Name "Microsoft.MSIXPackagingTool" -ErrorAction SilentlyContinue
if ($msixTool) {
Write-Host "MSIX Packaging Tool v$($msixTool.Version) is installed."
} else {
Write-Host "MSIX Packaging Tool is not installed."
Write-Host "Install it from the Microsoft Store or via:"
Write-Host " winget install Microsoft.MSIXPackagingTool"
}
Le processus de conversion :
- L'outil demarre une capture (similaire a Process Monitor)
- L'installeur MSI s'execute normalement et ecrit dans le registre
- L'outil intercepte toutes les ecritures de registre
- Les ecritures sont converties en un
registry.datembarque dans le package MSIX - A l'installation du MSIX, le
registry.datest fusionne dans le registre virtuel du paquet
Limites de la conversion
Tous les packages MSI ne sont pas convertibles en MSIX. Les installations qui necessitent des pilotes noyau, des services Windows ou des modifications profondes du systeme (boot-time drivers, filtre de fichiers) ne peuvent pas etre conteneurisees. Les Custom Actions qui executent du code arbitraire posent egalement des problemes car leur comportement n'est pas toujours capturable.
Le registre virtuel MSIX en pratique¶
Quand un package MSIX est installe, son registry.dat est charge comme une ruche virtuelle. L'application voit les cles comme si elles etaient dans le registre reel, mais elles sont en realite isolees dans le conteneur (voir le chapitre 19 — Virtualisation).
# Examine the virtual registry of an MSIX package
$package = Get-AppxPackage -Name "Microsoft.WindowsTerminal" -ErrorAction SilentlyContinue
if ($package) {
$registryDat = Join-Path $package.InstallLocation "registry.dat"
if (Test-Path $registryDat) {
Write-Host "Virtual registry found: $registryDat"
Write-Host "Size: $([math]::Round((Get-Item $registryDat).Length / 1024, 1)) KB"
} else {
Write-Host "No registry.dat found (app may not use virtual registry)"
}
}
Virtual registry found: C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.19.10302.0_x64__8wekyb3d8bbwe\registry.dat
Size: 14.8 KB
En resume
- MSIX virtualise le registre par paquet (registry.dat) au lieu d'ecrire dans le registre global, eliminant la pollution et les conflits
- La desinstallation MSIX est atomique : le conteneur entier est supprime, sans residus dans le registre
- Le MSIX Packaging Tool capture les ecritures de registre d'un installeur MSI et les convertit en registre virtualise, mais les installations avec drivers ou services ne sont pas convertibles
En resume
- Windows Installer dissemine les donnees de chaque produit dans trois arborescences principales du registre :
Installer\Products,UninstalletUserData - Les GUIDs sont stockes dans un format comprime obtenu par inversion de groupes de caracteres
- L'arborescence
Uninstallest la source de verite pour "Programmes et fonctionnalites" — elle existe en trois exemplaires (64 bits, 32 bits, per-user) - La table MSI
Registrygere les ecritures de registre avec un systeme de rollback automatique en cas d'echec - L'auto-reparation detecte les composants manquants via la verification des keypaths et les reinstalle silencieusement
AlwaysInstallElevatedest un risque de securite critique quand il est active dans HKLM et HKCU simultanement- Evitez
Win32_Productpour l'inventaire logiciel — interrogez directement les clesUninstalldu registre - MSIX remplace l'approche MSI par un registre virtualise par paquet, eliminant la pollution du registre global et les conflits entre applications