La cause n’était finalement ni la licence du tenant, ni l’absence de jointure Entra, ni l’horloge, ni l’installation Click-to-Run. Le journal AAD révélait une clé protégée du Web Account Manager (WAM) devenue incohérente avec son parent de stockage TPM :
0xCAA5001C Token broker operation failed
GetTokenSilently
0xCAA5004B Device information is missing
0x8029040E TPM_E_PCP_WRONG_PARENT
NgcHelper::SignWithSymmetricPopKey
La correction effective a été une réinitialisation du package utilisateur Microsoft.AAD.BrokerPlugin, après sauvegarde complète et vérifiée. Le TPM n’a pas été effacé. Après reconnexion manuelle dans Word, la licence est passée à O365BusinessRetail = vNext, Licensed, et Outlook ainsi que OneDrive se sont reconnectés.
Cet article présente une méthode progressive. Commencez toujours par les diagnostics en lecture seule. N’effacez jamais le TPM ou un cache d’identité sans sauvegarde et sans comprendre l’impact sur BitLocker, Windows Hello et les autres comptes.
Conclusion technique confirmée
La cause confirmée était un état utilisateur WAM/AAD Broker contenant une clé protégée incohérente avec son parent TPM. Le TPM matériel, l’absence de jointure Entra, DeployedInAuditMode et O365BusinessRetail.TenantId = "1," n’étaient pas la cause.
Résumé de la résolution
| Élément | Avant | Après |
|---|---|---|
| Produit | O365BusinessRetail | O365BusinessRetail |
| Mode d’activation | Legacy | vNext |
| Licence vNext | Aucune | User|Subscription, Licensed |
Fichiers dans %LOCALAPPDATA%\Microsoft\Office\Licenses | 0 | 1 |
| Erreur racine | 0x8029040E répétée | 0 après correction |
| Jointure Entra | Non | Non, inchangée |
| Jointure AD locale | Oui | Oui, inchangée |
| TPM | Sain et opérationnel | Inchangé, jamais effacé |
| Word / Outlook / OneDrive | Non licenciés ou bloqués | Connectés et opérationnels |
Environnement du cas
Les identifiants ont été anonymisés.
- Windows 11 Pro, build
10.0.26200; - poste joint à un domaine Active Directory local ;
DomainJoined = YES;AzureAdJoined = NO;WorkplaceJoined = NO;AzureAdPrt = NO;- Microsoft 365 Apps for business ;
ProductReleaseIds = OneNoteFreeRetail,O365BusinessRetail;- utilisateur licencié Microsoft 365 Business Premium ;
- aucun contrôleur de domaine joignable pendant l’intervention, le poste étant hors site ;
- UPN Active Directory différent de l’adresse de connexion Microsoft 365.
La différence entre l’UPN AD local et l’adresse Microsoft 365 constitue une dette d’hygiène d’identité, surtout lorsque mail et proxyAddresses sont vides. Elle n’était toutefois pas la cause bloquante : la licence a été acquise sans modifier l’UPN AD.
Symptômes
Les symptômes observés pendant quatre jours étaient les suivants :
- Word : « Consultation uniquement (sans licence) » ;
- Office : « Produit sans licence » ;
- Word et Outlook ouverts en période de grâce ;
- fenêtre Microsoft « Une erreur s’est produite » ;
- Tag
7ita9; - code
3399811147, soit0xCAA5004B; - environ 700 événements AAD en 24 heures ;
- aucune licence dans le cache vNext.
La licence avait été vérifiée dans le centre d’administration Microsoft 365 : elle était attribuée à l’utilisateur et ses plans de service étaient actifs.
Diagnostics à effectuer avant toute modification
1. Identifier le produit Click-to-Run
$c2r = 'HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration'
Get-ItemProperty -LiteralPath $c2r |
Select-Object ProductReleaseIds,VersionToReport,Platform,
'O365BusinessRetail.TenantId',
'O365BusinessRetail.OSPPReady',
DeployedInAuditMode |
Format-List
Dans ce cas :
ProductReleaseIds : OneNoteFreeRetail,O365BusinessRetail
O365BusinessRetail.TenantId : 1,
O365BusinessRetail.OSPPReady : 1
DeployedInAuditMode : True
2. Contrôler la licence vNext
Depuis Office 2104, Microsoft fournit vNextDiag.ps1 pour inspecter l’activation moderne. Pour Microsoft 365 Apps, cet outil est plus pertinent qu’ospp.vbs, réservé principalement aux éditions en licence en volume.
& 'C:\Program Files\Microsoft Office\root\Office16\vNextDiag.ps1' -action list
État en échec :
========== Mode per ProductReleaseId ==========
o365businessretail = Legacy
========== vNext licenses found ==========
No licenses found.
État après résolution, anonymisé :
========== Mode per ProductReleaseId ==========
o365businessretail = vNext
{
"Type": "User|Subscription",
"Product": "O365BusinessRetail",
"LicenseState": "Licensed",
"EntitlementStatus": "Provisioned",
"TenantId": "<GUID-TENANT>",
"LicenseId": "EWW_<ID-ANONYMISE>"
}
Vérifiez également la présence des fichiers, sans afficher leur contenu :
Get-ChildItem "$env:LOCALAPPDATA\Microsoft\Office\Licenses" `
-Recurse -Force -File -ErrorAction SilentlyContinue |
Select-Object FullName,Length,CreationTime,LastWriteTime
La documentation Microsoft explique les valeurs de LicensingNext, le dossier Licenses et l’usage de vNextDiag.ps1 : Vérifier l’état de licence et d’activation de Microsoft 365 Apps.
3. Vérifier l’état de jointure, sans conclure trop vite
dsregcmd.exe /status
Une activation utilisateur de Microsoft 365 Apps n’exige pas, à elle seule, que le poste soit joint à Entra ID. Dans ce cas, l’activation a réussi alors que les valeurs suivantes sont restées inchangées :
DomainJoined : YES
AzureAdJoined : NO
WorkplaceJoined : NO
AzureAdPrt : NO
WamDefaultSet : NO
L’absence de jointure hybride était donc un contexte, pas une cause.
4. Vérifier le TPM sans exposer son secret propriétaire
N’utilisez pas Get-Tpm | Format-List * dans un journal partageable : cela peut écrire OwnerAuth, qui est une donnée sensible.
Get-Tpm | Select-Object `
TpmPresent,TpmReady,TpmEnabled,TpmActivated,TpmOwned,
RestartPending,ManufacturerIdTxt,ManufacturerVersionFull20,
ManagedAuthLevel,OwnerClearDisabled,AutoProvisioning,
LockedOut,LockoutCount |
Format-List
Le TPM de ce poste était présent, prêt, activé, provisionné et non verrouillé. Les événements TPM-WMI confirmaient le bon fonctionnement du matériel et de l’attestation.
Le code 0x8029040E a une signification précise dans Winerror : TPM_E_PCP_WRONG_PARENT, c’est-à-dire qu’une clé a été chargée sous un mauvais parent de stockage. Voir COM Error Codes — TPM.
Cette distinction est essentielle : un état TPM sain n’empêche pas une clé applicative locale d’être orpheline ou incohérente.
5. Lire le journal AAD et conserver l’erreur interne
$since = (Get-Date).AddHours(-24)
$events = Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-AAD/Operational'
StartTime = $since
} -ErrorAction SilentlyContinue
$events |
Select-Object TimeCreated,Id,LevelDisplayName,Message |
Format-List
"WrongParent=" + @($events | Where-Object Message -match `
'0x8029040E|TPM_E_PCP_WRONG_PARENT').Count
Il ne faut pas s’arrêter au message supérieur Device information is missing. Dans ce cas, la chaîne d’appel donnait la vraie piste :
GetTokenSilently
NgcHelper::SignWithSymmetricPopKey
0x8029040E TPM_E_PCP_WRONG_PARENT
6. Contrôler l’heure avec une source Internet hors site
L’absence de contrôleur de domaine était normale puisque le poste était hors site. La pile de temps a été vérifiée contre une source externe :
w32tm.exe /query /status /verbose
w32tm.exe /query /peers
Une dérive importante peut casser OAuth, mais l’heure était correcte après synchronisation et les erreurs TPM ont persisté. L’horloge n’était donc pas la cause.
Ce qui avait déjà été écarté
Avant d’agir sur WAM, les points suivants avaient été vérifiés :
- licence présente et plans actifs dans le tenant ;
- réparation en ligne d’Office sans effet ;
DISM /RestoreHealthréussi ;sfc /scannowsans violation ;- absence de stratégies Office HKLM/HKCU ;
- caches du compte Microsoft personnel retirés ;
- cache produit reconstruit de
O365HomePremRetailversO365BusinessRetail; - credentials Windows, OneAuth et comptes WAM déjà purgés puis reconnectés ;
- aucune automatisation Outlook responsable des lancements ;
- aucun signe de panne du TPM matériel.
Chronologie des tentatives et résultat de chacune
| Étape | Action | Résultat |
|---|---|---|
| 1 | Diagnostic initial complet | Environ 700 erreurs AAD/24 h, aucune licence vNext |
| 2 | Synchronisation NTP Internet | Heure correcte, erreur inchangée |
| 3 | Purges ciblées déjà sauvegardées : Office Licenses, OneAuth, IdentityCache, comptes WAM | Produit correct reconstruit, activation toujours impossible |
| 4 | Réenregistrement AppX du Broker AAD et de Cloud Experience Host | Package Status=Ok, mais 3 nouveaux 0x8029040E dès OneDrive |
| 5 | ProtectionPolicy=1 après export de la clé, puis redémarrage | 3 nouveaux 0x8029040E : insuffisant seul |
| 6 | Inventaire TPM/NGC/DPAPI | TPM sain ; erreur limitée à l’état utilisateur WAM |
| 7 | Sauvegarde intégrale et vérifiée du package AAD Broker | 56 fichiers source, 56 fichiers sauvegardés et hachés |
| 8 | Reset-AppxPackage du seul Microsoft.AAD.BrokerPlugin | WrongParentCount=0 après relance OneDrive |
| 9 | Connexion manuelle dans Word au vrai compte professionnel | Licence O365BusinessRetail acquise, Licensed |
| 10 | Ouverture d’Outlook classique | Fenêtre blanche plusieurs minutes, puis boîte chargée et connectée |
Pourquoi les premières réparations n’ont pas suffi
Réenregistrer le Broker n’efface pas son état utilisateur
La commande suivante réenregistre le manifeste, mais conserve les données locales du package :
$pkg = Get-AppxPackage -Name Microsoft.AAD.BrokerPlugin
Add-AppxPackage -Register "$($pkg.InstallLocation)\AppxManifest.xml" `
-DisableDevelopmentMode -ForceApplicationShutdown
Le package était déjà déclaré Ok. Le réenregistrement n’a donc pas remplacé la clé protégée devenue incohérente. Le test OneDrive réalisé juste après a reproduit trois erreurs TPM_E_PCP_WRONG_PARENT.
ProtectionPolicy=1 n’était pas la solution isolée
Microsoft documente ProtectionPolicy=1 parmi les pistes pour les erreurs d’activation liées au TPM : Microsoft 365 Apps activation error: “Trusted Platform Module malfunctioned”.
Dans ce cas, la valeur a été exportée puis ajoutée, suivie d’un redémarrage. L’erreur a persisté. Ce test évite d’attribuer à tort la résolution à la seule valeur de registre.
La stratégie a néanmoins été conservée pendant la recréation du Broker, conformément à la séquence de dépannage Microsoft. Elle doit être documentée et réévaluée selon la politique DPAPI de l’entreprise.
Effacer le TPM aurait été disproportionné
Le volume système était chiffré mais la protection BitLocker apparaissait suspendue et les protecteurs n’étaient pas clairement remontés. Un effacement TPM aurait pu affecter BitLocker, Windows Hello et d’autres clés. Comme le TPM matériel était sain et que l’erreur concernait un état WAM utilisateur, il n’a pas été effacé.
Correction qui a fonctionné
1. Fermer Office et arrêter proprement OneDrive
Le script fourni plus bas refuse la réparation si une application Office est ouverte. Il arrête OneDrive avec /shutdown au lieu de tuer indistinctement ses processus.
2. Sauvegarder le package et les clés de registre
Le dossier suivant a été copié intégralement :
%LOCALAPPDATA%\Packages\Microsoft.AAD.BrokerPlugin_cw5n1h2txyewy
La copie a été validée par :
- comparaison du nombre de fichiers ;
- comparaison du nombre d’octets ;
- calcul SHA-256 de chaque fichier ;
- export des clés AAD, WorkplaceJoin, Office Identity, Office Licensing et AppModel concernées ;
- ACL limitée à l’utilisateur, SYSTEM et Administrateurs.
3. Réinitialiser uniquement le package utilisateur AAD Broker
Get-AppxPackage -Name Microsoft.AAD.BrokerPlugin | Reset-AppxPackage
Cette commande réinitialise les données du package pour l’utilisateur courant. Elle doit être exécutée dans le profil affecté. Dans ce dossier réel, les 56 fichiers avaient été sauvegardés avant l’opération.
Microsoft recommande également la suppression ciblée des données BrokerPlugin dans son guide TPM. Ici, Reset-AppxPackage a fourni une limite plus claire et un résultat immédiatement mesurable. Des cas Microsoft Q&A décrivent la même piste pour un profil WAM corrompu : SSO Failure: Microsoft Apps for single user on VDI.
4. Relancer OneDrive comme test WAM
OneDrive constituait un excellent déclencheur reproductible de GetTokenSilently.
Résultat avant reset :
AAD events : 5
WrongParent : 3
OneDrive : lancé
Résultat après reset :
AAD events : 9
WrongParent : 0
OneDrive : lancé et stable
Les neuf événements restants étaient des avertissements d’initialisation ou liés à l’absence normale de DC/fédération, pas 0x8029040E.
5. Ouvrir Word et se connecter manuellement
Le script ne saisit jamais d’identifiants. L’utilisateur ouvre Word et se connecte dans la fenêtre Microsoft avec son vrai compte Microsoft 365 professionnel.
Si Windows propose d’inscrire ou d’administrer l’appareil alors que ce n’est pas souhaité, choisir « Non, se connecter à cette application uniquement ». Ne transformez pas un dépannage de licence en inscription Entra ou MDM non planifiée.
Validation de la réussite
Une résolution n’est pas prouvée par la seule disparition de la fenêtre d’erreur. Les contrôles suivants ont été obtenus :
Product : O365BusinessRetail
Activation mode : vNext
License type : User|Subscription
LicenseState : Licensed
EntitlementStatus : Provisioned
License files : 1
TPM_E_PCP_WRONG_PARENT: 0 après correction
0xCAA5004B : 0 après correction
Word : activé
OneDrive : connecté
Outlook classique : connecté et réactif
Le premier démarrage d’Outlook a affiché une grande fenêtre blanche pendant plusieurs minutes. Le processus n’était pas déclaré bloqué, consommait encore du CPU et maintenait plusieurs connexions TLS vers Microsoft 365. Il a finalement chargé la boîte. Cette phase doit être observée avant de tuer Outlook ou de reconstruire inutilement le profil MAPI.
Réponses aux questions fréquentes
Que signifie O365BusinessRetail.TenantId = "1," ?
Cette valeur Click-to-Run n’est ni un GUID de tenant valide ni une preuve fiable de la licence vNext. Microsoft ne publie pas de sémantique permettant de l’interpréter comme un compteur. Dans ce cas, "1," est resté présent après l’activation réussie, tandis que vNextDiag.ps1 et le fichier de licence affichaient le vrai GUID du tenant.
Conclusion opérationnelle : considérez cette valeur comme une métadonnée C2R interne et non autoritative. Ne la modifiez pas manuellement. Pour connaître le tenant réellement associé à la licence, utilisez vNextDiag.ps1.
DeployedInAuditMode = True bloque-t-il l’activation ?
Non dans ce cas. La valeur est restée à True avant et après l’acquisition réussie de la licence. La basculer arbitrairement à False aurait modifié un état de déploiement Click-to-Run sans preuve qu’il intervenait dans WAM.
Conclusion : ne changez pas cette valeur comme « recette » d’activation. Son rôle exact n’est pas documenté publiquement de façon suffisante pour justifier une modification isolée.
« Device information is missing » est-il normal sans jointure Entra ?
L’absence de données de périphérique peut apparaître dans les chemins WAM d’un poste sans PRT ni jointure Entra. Le texte supérieur n’est pas suffisant pour conclure.
Dans ce cas, l’élément bloquant était l’erreur interne TPM_E_PCP_WRONG_PARENT. Après correction, Office s’est activé avec AzureAdJoined = NO, WorkplaceJoined = NO et AzureAdPrt = NO. Une jointure Entra n’était donc pas requise pour cette licence utilisateur.
Comment forcer une nouvelle acquisition de licence sans réinstaller Office ?
Pour inspecter et retirer une licence vNext existante de manière ciblée :
& 'C:\Program Files\Microsoft Office\root\Office16\vNextDiag.ps1' -action list
# Après sauvegarde, utiliser uniquement le LicenseId EWW affiché :
& 'C:\Program Files\Microsoft Office\root\Office16\vNextDiag.ps1' `
-action remove -LicenseId 'EWW_<ID>'
Au prochain lancement, Word redemande une activation. L’authentification doit rester interactive ; ne placez jamais un mot de passe, un jeton ou un code MFA dans un script.
Dans le cas décrit, retirer la licence ne suffisait pas, car WAM ne pouvait pas signer la demande. Il fallait d’abord réparer le Broker AAD.
Comment distinguer un problème profil d’un problème machine sans utiliser un autre compte ?
Plusieurs indices concordants sont utiles :
- Le TPM machine est sain (
Get-Tpm, TPM-WMI, attestation). - Le package système est installé et
Status=Ok. - L’échec se produit dans
%LOCALAPPDATA%et HKCU. - La sauvegarde puis la réinitialisation du package utilisateur suppriment l’erreur sans modifier le TPM ni la jointure machine.
- Un test A/B avec OneDrive donne
WrongParent > 0avant etWrongParent = 0après.
Ce test réversible du Broker constitue ici une discrimination profil/machine forte, sans ouvrir une session avec un deuxième utilisateur.
Était-ce lié aux incidents Microsoft 365 d’août-septembre 2026 ?
Deux incidents étaient temporellement proches :
OP1460815, le 24 août 2026, avec des invites répétées d’activation Office rapportées par la communauté ;MO1465074, commencé le 31 août 2026, lié à une configuration d’authentification centrale et affectant plusieurs services Microsoft 365.
Des administrateurs ont effectivement signalé des problèmes généralisés sur Reddit à propos de MO1465074, et une chronologie publique de l’incident est conservée par PennO365.
Cependant, les mesures locales ne permettent pas d’attribuer ce cas à la panne Microsoft :
- les autres postes du parc s’activaient ;
- les incidents étaient déclarés résolus ;
- l’erreur exacte
0x8029040Eest locale et signifie « mauvais parent de stockage » ; - le problème était reproductible à chaque appel WAM ;
- la réinitialisation locale du Broker l’a supprimé immédiatement.
Hypothèse prudente : l’incident cloud ou une demande de renouvellement a pu révéler un cache local déjà incohérent. Aucune preuve ne permet d’affirmer qu’il l’a créé.
Ne pas confondre les erreurs apparues après la correction
Après réparation, deux appels ont produit AADSTS500014 vers un ancien tenant SharePoint différent du tenant de licence. L’erreur interne précisait que le principal de service de la ressource SharePoint y était désactivé.
Ce signal était distinct :
- autre tenant dans l’authority OAuth ;
- aucun
0x8029040E; - aucun
0xCAA5004B; - licence toujours
Licensed; - Outlook et OneDrive connectés.
Cela illustre une règle de diagnostic importante : deux événements AAD 1098 ne représentent pas nécessairement la même panne. Lisez le code interne, l’authority, le client et la ressource.
Arbre de décision recommandé
- Vérifier la licence et les plans dans le tenant.
- Vérifier l’état de service Microsoft 365 si plusieurs utilisateurs sont touchés.
- Contrôler l’heure, DNS et HTTPS.
- Lire
vNextDiag.ps1et le dossierOffice\Licenses. - Lire
dsregcmd /status, sans exiger une jointure Entra non prévue. - Lire AAD/Operational et rechercher l’erreur interne.
- Si
0x8029040Eest présent, vérifier le TPM sans l’effacer. - Réenregistrer le Broker AAD : test non destructif.
- Si l’erreur persiste, sauvegarder intégralement le package utilisateur puis le réinitialiser.
- Reconnecter manuellement Word et vérifier
Licensed. - Tester OneDrive et Outlook, puis vérifier l’absence de récidive.
- N’envisager l’effacement TPM qu’en dernier recours, avec protecteurs BitLocker et clés de récupération vérifiés.
Sources techniques
Une annexe de preuves anonymisées reprend les sorties déterminantes, y compris les tentatives infructueuses et la validation finale. Les journaux bruts ne doivent pas être publiés.
- Microsoft Learn — Microsoft 365 Apps activation error: “Trusted Platform Module malfunctioned”
- Microsoft Learn — COM Error Codes (TPM, PLA, FVE)
- Microsoft Learn — Vérifier la licence et l’état d’activation de Microsoft 365 Apps
- Microsoft Learn — Authentication automatically fails in Microsoft 365 services
- Microsoft Q&A — SSO Failure: Microsoft Apps for single user on VDI
- Étude communautaire d’une clé NGC orpheline — OneDrive PRT/NGC technical notes
- Chronologie publique — Incident MO1465074, PennO365
Script PowerShell complet : diagnostic et dry-run
Le script ci-dessous est compatible Windows PowerShell 5.1.
Comportement de sécurité :
Diagnoseest l’action par défaut et reste en lecture seule ;- toute action de réparation est simulée tant que
-Applyn’est pas fourni ; ResetBrokereffectue et vérifie une sauvegarde avant la réinitialisation ;- le script refuse
-Applysi une application Office est ouverte ; - aucun identifiant, mot de passe ou jeton MFA n’est demandé ;
OwnerAuthdu TPM n’est jamais journalisé ;- le dossier de traces est protégé par ACL ;
- aucun redémarrage n’est forcé.
Exemples :
# Diagnostic en lecture seule
.\Office-WAM-Activation-Debug.ps1
# Voir exactement ce que ferait la réparation, sans la faire
.\Office-WAM-Activation-Debug.ps1 -Action ResetBroker
# Réenregistrement non destructif du Broker
.\Office-WAM-Activation-Debug.ps1 -Action ReregisterBroker -Apply
# Reset du Broker après sauvegarde intégrale vérifiée
.\Office-WAM-Activation-Debug.ps1 -Action ResetBroker -Apply
# Simulation de la stratégie DPAPI documentée par Microsoft
.\Office-WAM-Activation-Debug.ps1 -Action SetProtectionPolicy
# Application réelle : lancer PowerShell en administrateur
.\Office-WAM-Activation-Debug.ps1 -Action SetProtectionPolicy -Apply
Les journaux originaux peuvent contenir des SID, GUID de tenant, identifiants de licence et chemins de profil. Anonymisez-les avant publication.
[CmdletBinding()]
param(
[ValidateSet('Diagnose','ReregisterBroker','ResetBroker','SetProtectionPolicy','RestoreProtectionPolicy')]
[string]$Action = 'Diagnose',
[switch]$Apply,
[ValidateRange(1,168)]
[int]$EventHours = 24,
[string]$OutputRoot = 'C:\INFORMATIQUE',
[string]$RestoreFrom
)
# PowerShell 5.1 compatible. No credential is requested, read or stored.
# Diagnose is always read-only. Every repair action is a dry run unless -Apply is present.
$ErrorActionPreference = 'Stop'
$script:DryRun = -not $Apply
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$machineSafe = $env:COMPUTERNAME -replace '[^A-Za-z0-9_-]', '_'
$runRoot = Join-Path $OutputRoot "Office-WAM-Debug-$machineSafe-$stamp"
$logRoot = Join-Path $runRoot 'Logs'
$backupRoot = Join-Path $runRoot 'Backup'
New-Item -ItemType Directory -Path $logRoot,$backupRoot -Force | Out-Null
# Diagnostic files can contain technical identifiers. Restrict access immediately.
$userSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
& icacls.exe $runRoot /inheritance:r /grant:r "*$userSid`:(OI)(CI)F" '*S-1-5-32-544:(OI)(CI)F' '*S-1-5-18:(OI)(CI)F' | Out-Null
if ($LASTEXITCODE -ne 0) { throw 'Unable to secure the diagnostic directory ACL.' }
$script:TranscriptPath = Join-Path $logRoot 'Transcript.txt'
Start-Transcript -LiteralPath $script:TranscriptPath -Force | Out-Null
function Write-Log {
param([string]$Message)
$line = '[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'),$Message
Write-Host $line
$line | Add-Content -LiteralPath (Join-Path $logRoot 'Actions.log') -Encoding UTF8
}
function Save-Output {
param([string]$Name,[scriptblock]$Command)
try {
& $Command 2>&1 | Out-File -LiteralPath (Join-Path $logRoot "$Name.txt") -Encoding UTF8 -Width 400
}
catch {
$_ | Format-List * -Force | Out-File -LiteralPath (Join-Path $logRoot "$Name-ERROR.txt") -Encoding UTF8 -Width 400
}
}
function Test-IsAdministrator {
$p = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-Office16Path {
@(
'C:\Program Files\Microsoft Office\root\Office16',
'C:\Program Files (x86)\Microsoft Office\root\Office16',
'C:\Program Files\Microsoft Office\Office16',
'C:\Program Files (x86)\Microsoft Office\Office16'
) | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1
}
function Get-TreeStats {
param([Parameter(Mandatory=$true)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
return [pscustomobject]@{Path=$Path;Exists=$false;Files=0;Bytes=0}
}
$m = Get-ChildItem -LiteralPath $Path -Recurse -Force -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum
[pscustomobject]@{Path=$Path;Exists=$true;Files=[int]$m.Count;Bytes=[long]$m.Sum}
}
function Export-RegIfPresent {
param([string]$NativeKey,[string]$FileName)
& reg.exe query $NativeKey *> $null
if ($LASTEXITCODE -eq 0) {
$destination = Join-Path $backupRoot $FileName
& reg.exe export $NativeKey $destination /y | Out-Null
if ($LASTEXITCODE -ne 0) { throw "Registry backup failed: $NativeKey" }
Write-Log "Registry backup: $NativeKey"
}
}
function Backup-TreeVerified {
param([string]$Source,[string]$Destination,[string]$Label)
if (-not (Test-Path -LiteralPath $Source)) {
Write-Log "Backup source absent: $Source"
return
}
$before = Get-TreeStats $Source
New-Item -ItemType Directory -Path $Destination -Force | Out-Null
& robocopy.exe $Source $Destination /E /COPY:DAT /DCOPY:DAT /R:1 /W:1 /XJ /NP /NFL /NDL /NJH /NJS |
Out-File -LiteralPath (Join-Path $logRoot "Robocopy-$Label.txt") -Encoding UTF8
if ($LASTEXITCODE -ge 8) { throw "Robocopy failed for $Label with exit code $LASTEXITCODE" }
$after = Get-TreeStats $Destination
@($before,$after) | Export-Csv -LiteralPath (Join-Path $logRoot "Backup-Verification-$Label.csv") -NoTypeInformation -Encoding UTF8
if ($after.Files -lt $before.Files -or $after.Bytes -lt $before.Bytes) {
throw "Incomplete backup for $Label"
}
Get-ChildItem -LiteralPath $Destination -Recurse -Force -File -ErrorAction Stop | Get-FileHash -Algorithm SHA256 |
Export-Csv -LiteralPath (Join-Path $logRoot "Backup-SHA256-$Label.csv") -NoTypeInformation -Encoding UTF8
Write-Log "Verified backup: $Label ($($after.Files) files, $($after.Bytes) bytes)"
}
function Assert-OfficeClosed {
$names = @('WINWORD','OUTLOOK','EXCEL','POWERPNT','MSACCESS','MSPUB','ONENOTE','LYNC')
$running = @(Get-Process -Name $names -ErrorAction SilentlyContinue)
if ($running) {
throw ('Close Office applications before -Apply. Running: ' + (($running.ProcessName | Sort-Object -Unique) -join ', '))
}
}
function Stop-OneDriveCleanly {
$exe = @(
(Join-Path $env:LOCALAPPDATA 'Microsoft\OneDrive\OneDrive.exe'),
'C:\Program Files\Microsoft OneDrive\OneDrive.exe',
'C:\Program Files (x86)\Microsoft OneDrive\OneDrive.exe'
) | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1
if ($exe -and (Get-Process OneDrive -ErrorAction SilentlyContinue)) {
Start-Process -FilePath $exe -ArgumentList '/shutdown' -Wait
Start-Sleep -Seconds 4
Write-Log 'OneDrive stopped cleanly.'
}
}
function Write-Diagnostics {
param([string]$Prefix)
$since = (Get-Date).AddHours(-$EventHours)
$office16 = Get-Office16Path
$vNextDiag = if ($office16) { Join-Path $office16 'vNextDiag.ps1' } else { $null }
$brokerData = Join-Path $env:LOCALAPPDATA 'Packages\Microsoft.AAD.BrokerPlugin_cw5n1h2txyewy'
Save-Output "$Prefix-00-Context" {
[pscustomobject]@{
Captured=(Get-Date -Format o)
Computer=$env:COMPUTERNAME
User=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
UserSid=$userSid
Elevated=(Test-IsAdministrator)
Windows=(Get-CimInstance Win32_OperatingSystem).Caption
WindowsVersion=(Get-CimInstance Win32_OperatingSystem).Version
PowerShell=$PSVersionTable.PSVersion.ToString()
Action=$Action
DryRun=$script:DryRun
} | Format-List
}
Save-Output "$Prefix-01-Office-ClickToRun" {
Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration' -ErrorAction SilentlyContinue |
Select-Object ProductReleaseIds,VersionToReport,ClientVersionToReport,UpdateChannel,CDNBaseUrl,Platform,'O365BusinessRetail.TenantId','O365BusinessRetail.OSPPReady',DeployedInAuditMode | Format-List
}
Save-Output "$Prefix-02-Office-User-Licensing" {
Get-ItemProperty -LiteralPath 'HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\Licensing' -ErrorAction SilentlyContinue |
Select-Object TimeOfLastDACheck,DeviceEntitlementsExist,EligibleForExtendedGrace | Format-List
Get-ItemProperty -LiteralPath 'HKCU:\SOFTWARE\Microsoft\Office\16.0\Common\Licensing\LicensingNext' -ErrorAction SilentlyContinue | Format-List
Get-TreeStats (Join-Path $env:LOCALAPPDATA 'Microsoft\Office\Licenses') | Format-List
}
Save-Output "$Prefix-03-vNextDiag" {
if ($vNextDiag -and (Test-Path -LiteralPath $vNextDiag)) { & $vNextDiag -action list }
else { 'vNextDiag.ps1 not found.' }
}
Save-Output "$Prefix-04-DsRegCmd" { dsregcmd.exe /status }
Save-Output "$Prefix-05-TPM" {
# Never use Format-List * here: OwnerAuth is a TPM secret.
Get-Tpm | Select-Object TpmPresent,TpmReady,TpmEnabled,TpmActivated,TpmOwned,RestartPending,ManufacturerIdTxt,ManufacturerVersionFull20,ManagedAuthLevel,OwnerClearDisabled,AutoProvisioning,LockedOut,LockoutCount | Format-List
}
Save-Output "$Prefix-06-Time" {
w32tm.exe /query /status /verbose
w32tm.exe /query /peers
}
Save-Output "$Prefix-07-Appx-Broker" {
Get-AppxPackage -Name Microsoft.AAD.BrokerPlugin | Select-Object Name,PackageFullName,Status,InstallLocation | Format-List
Get-AppxPackage -Name Microsoft.Windows.CloudExperienceHost | Select-Object Name,PackageFullName,Status,InstallLocation | Format-List
Get-TreeStats $brokerData | Format-List
}
Save-Output "$Prefix-08-Processes-Services" {
Get-Process WINWORD,OUTLOOK,EXCEL,POWERPNT,ONENOTE,OneDrive,Microsoft.AAD.BrokerPlugin -ErrorAction SilentlyContinue |
Select-Object ProcessName,Id,StartTime,Responding,Path | Format-Table -AutoSize
Get-Service ClickToRunSvc,TokenBroker -ErrorAction SilentlyContinue | Select-Object Name,Status,StartType | Format-Table -AutoSize
}
Save-Output "$Prefix-09-Network" {
'login.microsoftonline.com','officeclient.microsoft.com','ols.officeapps.live.com' | ForEach-Object {
Test-NetConnection -ComputerName $_ -Port 443 -InformationLevel Detailed
}
}
Save-Output "$Prefix-10-AAD-Events" {
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-AAD/Operational';StartTime=$since} -ErrorAction SilentlyContinue |
Select-Object TimeCreated,Id,LevelDisplayName,ProviderName,Message | Format-List
}
$events = @(Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-AAD/Operational';StartTime=$since} -ErrorAction SilentlyContinue)
@(
"Captured=$(Get-Date -Format o)"
"WindowHours=$EventHours"
"AAD_EventCount=$($events.Count)"
"AAD_ErrorCount=$(@($events | Where-Object Level -eq 2).Count)"
"WrongParentCount=$(@($events | Where-Object Message -match '0x8029040E|TPM_E_PCP_WRONG_PARENT').Count)"
"CAA5004BCount=$(@($events | Where-Object Message -match '0xCAA5004B').Count)"
"CAA5001CCount=$(@($events | Where-Object Message -match '0xCAA5001C').Count)"
) | Out-File -LiteralPath (Join-Path $logRoot "$Prefix-11-Summary.txt") -Encoding UTF8
}
function Backup-BrokerState {
$packageFamily = 'Microsoft.AAD.BrokerPlugin_cw5n1h2txyewy'
Export-RegIfPresent 'HKCU\Software\Microsoft\Windows\CurrentVersion\AAD' 'HKCU-AAD.reg'
Export-RegIfPresent 'HKCU\Software\Microsoft\Windows NT\CurrentVersion\WorkplaceJoin' 'HKCU-WorkplaceJoin.reg'
Export-RegIfPresent 'HKCU\Software\Microsoft\Office\16.0\Common\Identity' 'HKCU-Office-Identity.reg'
Export-RegIfPresent 'HKCU\Software\Microsoft\Office\16.0\Common\Licensing' 'HKCU-Office-Licensing.reg'
Export-RegIfPresent 'HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\SystemAppData\Microsoft.AAD.BrokerPlugin_cw5n1h2txyewy' 'HKCU-AadBroker-SystemAppData.reg'
Backup-TreeVerified (Join-Path $env:LOCALAPPDATA "Packages\$packageFamily") (Join-Path $backupRoot $packageFamily) 'AadBroker'
}
try {
Write-Log "Action=$Action Apply=$Apply DryRun=$script:DryRun"
Write-Diagnostics 'Before'
switch ($Action) {
'Diagnose' {
Write-Log 'Read-only diagnosis completed. No repair was attempted.'
}
'ReregisterBroker' {
if ($script:DryRun) {
Write-Log 'DRY RUN: would close broker-hosted apps and re-register Microsoft.AAD.BrokerPlugin.'
}
else {
Assert-OfficeClosed
Stop-OneDriveCleanly
$pkg = Get-AppxPackage -Name Microsoft.AAD.BrokerPlugin
if (-not $pkg) { throw 'Microsoft.AAD.BrokerPlugin package not found.' }
Add-AppxPackage -Register (Join-Path $pkg.InstallLocation 'AppxManifest.xml') -DisableDevelopmentMode -ForceApplicationShutdown
Write-Log 'AAD BrokerPlugin re-registered.'
Write-Diagnostics 'After'
}
}
'ResetBroker' {
$source = Join-Path $env:LOCALAPPDATA 'Packages\Microsoft.AAD.BrokerPlugin_cw5n1h2txyewy'
$stats = Get-TreeStats $source
Write-Log "Broker source: Exists=$($stats.Exists), Files=$($stats.Files), Bytes=$($stats.Bytes)"
if ($script:DryRun) {
Write-Log 'DRY RUN: would export relevant registry keys, copy and hash the complete BrokerPlugin data tree, verify the backup, stop OneDrive, then run Reset-AppxPackage.'
}
else {
Assert-OfficeClosed
Backup-BrokerState
Stop-OneDriveCleanly
$pkg = Get-AppxPackage -Name Microsoft.AAD.BrokerPlugin
if (-not $pkg) { throw 'Microsoft.AAD.BrokerPlugin package not found.' }
$pkg | Reset-AppxPackage
Write-Log 'AAD BrokerPlugin user data reset after verified backup.'
Write-Diagnostics 'After'
Write-Log 'Open Word manually and sign in through the Microsoft window. Never put credentials in this script.'
}
}
'SetProtectionPolicy' {
if (-not $script:DryRun -and -not (Test-IsAdministrator)) { throw 'SetProtectionPolicy with -Apply requires an elevated PowerShell window.' }
$native = 'HKLM\SOFTWARE\Microsoft\Cryptography\Protect\Providers\df9d8cd0-1501-11d1-8c7a-00c04fc297eb'
$psPath = 'HKLM:\SOFTWARE\Microsoft\Cryptography\Protect\Providers\df9d8cd0-1501-11d1-8c7a-00c04fc297eb'
$item = Get-ItemProperty -LiteralPath $psPath -ErrorAction SilentlyContinue
$exists = $null -ne $item -and $item.PSObject.Properties.Name -contains 'ProtectionPolicy'
$old = if ($exists) { [int]$item.ProtectionPolicy } else { $null }
[pscustomobject]@{Exists=$exists;Value=$old;RegistryPath=$psPath} | Export-Clixml -LiteralPath (Join-Path $backupRoot 'ProtectionPolicy-Before.clixml')
Export-RegIfPresent $native 'HKLM-DPAPI-Provider.reg'
if ($script:DryRun) {
Write-Log "DRY RUN: would set ProtectionPolicy=1. PreviousExists=$exists PreviousValue=$old"
}
else {
New-Item -Path $psPath -Force | Out-Null
New-ItemProperty -LiteralPath $psPath -Name ProtectionPolicy -PropertyType DWord -Value 1 -Force | Out-Null
Write-Log 'ProtectionPolicy set to 1. Reboot is required for a meaningful test.'
Write-Diagnostics 'After'
}
}
'RestoreProtectionPolicy' {
if (-not $script:DryRun -and -not (Test-IsAdministrator)) { throw 'RestoreProtectionPolicy with -Apply requires an elevated PowerShell window.' }
if (-not $RestoreFrom) { throw 'Use -RestoreFrom with the earlier Office-WAM-Debug run directory.' }
$resolved = (Resolve-Path -LiteralPath $RestoreFrom).Path
if (-not $resolved.StartsWith((Join-Path $OutputRoot 'Office-WAM-Debug-'),[System.StringComparison]::OrdinalIgnoreCase)) {
throw "Restore path refused: $resolved"
}
$stateFile = Join-Path $resolved 'Backup\ProtectionPolicy-Before.clixml'
$state = Import-Clixml -LiteralPath $stateFile
if ($script:DryRun) {
Write-Log "DRY RUN: would restore ProtectionPolicy. PreviousExists=$($state.Exists) PreviousValue=$($state.Value)"
}
else {
if ($state.Exists) {
New-ItemProperty -LiteralPath $state.RegistryPath -Name ProtectionPolicy -PropertyType DWord -Value ([int]$state.Value) -Force | Out-Null
}
else {
Remove-ItemProperty -LiteralPath $state.RegistryPath -Name ProtectionPolicy -ErrorAction SilentlyContinue
}
Write-Log 'ProtectionPolicy restored to its recorded previous state.'
Write-Diagnostics 'After'
}
}
}
@(
"RunRoot=$runRoot"
"Action=$Action"
"DryRun=$script:DryRun"
"Completed=$(Get-Date -Format o)"
'CredentialsRequested=False'
) | Out-File -LiteralPath (Join-Path $runRoot 'RESULT.txt') -Encoding UTF8
Stop-Transcript | Out-Null
Get-ChildItem -LiteralPath $runRoot -Recurse -Force -File -ErrorAction SilentlyContinue |
Where-Object FullName -ne (Join-Path $runRoot 'SHA256.csv') |
Get-FileHash -Algorithm SHA256 -ErrorAction SilentlyContinue |
Export-Csv -LiteralPath (Join-Path $runRoot 'SHA256.csv') -NoTypeInformation -Encoding UTF8
Write-Host "Logs: $runRoot"
exit 0
}
catch {
$_ | Format-List * -Force | Out-File -LiteralPath (Join-Path $runRoot 'ERROR.txt') -Encoding UTF8 -Width 400
Stop-Transcript -ErrorAction SilentlyContinue | Out-Null
Write-Error $_
Write-Host "Logs: $runRoot"
exit 1
}
Télécharger le script PowerShell
Le fichier téléchargeable est strictement identique au script complet affiché ci-dessus.
Télécharger Office-WAM-Activation-Debug.ps1