Vous importez un certificat dans TrustedPublisher via PowerShell Direct sur une VM Hyper‑V et obtenez UnauthorizedAccessException: 0x80070005 (E_ACCESSDENIED)
? Voici une analyse approfondie et des correctifs reproductibles, avec scripts prêts à l’emploi et bonnes pratiques d’automatisation.
Contexte et symptômes
Dans une VM Hyper‑V fraîchement installée, un script PowerShell exécuté au travers de PowerShell Direct (par exemple Invoke-Command -Session $session
ou Enter-PSSession -VMName
) tente d’exécuter :
Import-Certificate -FilePath ".\adafruit_industries.cer" `
-CertStoreLocation "Cert:\LocalMachine\TrustedPublisher"
Bien que le script s’exécute sous le compte Administrateur local, la commande échoue avec :
UnauthorizedAccessException : 0x80070005 (E_ACCESSDENIED)
Observations usuelles :
- Le même script fonctionne dans Root (
Cert:\LocalMachine\Root
). - Il fonctionne également dans TrustedPublisher si l’on ouvre d’abord une session graphique interactive dans la VM, puis on relance le script.
Pourquoi l’accès est-il refusé ? (analyse détaillée)
La combinaison « VM neuve + session distante via PowerShell Direct + import dans TrustedPublisher » révèle plusieurs conditions rarement documentées. Les causes sont cumulatives : corriger l’une ou l’autre peut suffire suivant l’environnement.
Cause | Explication approfondie | Symptômes/indices |
---|---|---|
Magasin TrustedPublisher non matérialisé | La ruche HKLM\SOFTWARE\Microsoft\SystemCertificates\TrustedPublisher et son sous‑dossier Certificates sont générés à la première utilisation. Sur des images « proprement » sysprepées, ils peuvent ne pas exister avant un premier accès interactif à la Console des certificats ou une première opération CAPI qui force la création. Lorsque le magasin n’existe pas, certains appels via .NET/PowerShell renvoient un E_ACCESSDENIED quelque peu trompeur. | Get-ChildItem HKLM:\SOFTWARE\Microsoft\SystemCertificates\TrustedPublisher renvoie « Not Found » ou montre une clé vide ; l’import via certutil fonctionne quand même. |
Filtrage du jeton Administrateur (UAC) | En session distante, même le compte Administrateur local peut recevoir un jeton filtré (token filtering) dépourvu de certains privilèges sensibles. L’import dans des magasins « protégés » (comme TrustedPublisher) peut exiger un jeton pleinement élevé. | whoami /priv montre des privilèges désactivés ; whoami /groups inclut Local account and member of Administrators group. |
Paramètre LocalAccountTokenFilterPolicy | Par défaut (0 ), Windows applique des restrictions supplémentaires aux connexions distantes de comptes locaux, ce qui ampute le jeton de certains droits. Activer la valeur 1 restaure un comportement plus permissif. | Après passage à 1 et redémarrage, l’import réussit sans autre changement. |
Solutions express (choisir la plus adaptée)
Voici un résumé des options éprouvées. Elles ne sont pas exclusives : combinez‑les si nécessaire.
Option | Commande / mise en œuvre | Quand l’utiliser | Avantages | Précautions |
---|---|---|---|---|
Pré‑créer le magasin | New-Item -Path 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\TrustedPublisher' -Force | Out-Null New-Item -Path 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\TrustedPublisher\Certificates' -Force | Out-Null Import-Certificate -FilePath '.\adafruit_industries.cer' ` -CertStoreLocation 'Cert:\LocalMachine\TrustedPublisher' | Machines neuves, image de référence, exécution sans session graphique préalable. | Idempotent, simple, aucun outil tiers. | Exige tout de même un jeton suffisamment élevé. |
Exécuter en LocalSystem (SYSTEM) | Via tâche planifiée (intégré, pas d’outil tiers) : $action = New-ScheduledTaskAction -Execute 'PowerShell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\Temp\InstallCert.ps1' $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest $task = New-ScheduledTask -Action $action -Principal $principal Register-ScheduledTask -TaskName 'InstallTrustedPublisherCert' -InputObject $task Start-ScheduledTask -TaskName 'InstallTrustedPublisherCert' | Environnements verrouillés, pas d’administrateur interactif. | SYSTEM n’est pas soumis à l’UAC ; fiabilité maximale. | Orchestration un peu plus lourde (copier le script, créer/ouvrir la tâche). |
Désactiver le filtrage UAC pour comptes locaux distants | reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System` /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f shutdown /r /t 0 | Lab/DEV, pipelines d’intégration, hôtes isolés. | Restaure un jeton complet pour l’Administrateur local à distance. | Moins strict sur le plan sécurité. Éviter en production si possible. |
Utiliser certutil | certutil -addstore -f "TrustedPublisher" ".\adafruit_industries.cer" | Lorsque Import-Certificate échoue mais que CAPI bas niveau passe. | Contourne certains échecs de l’enveloppe .NET/PowerShell. | Vérifier le code retour : 0 indique la réussite. |
Intégrer via GPO | Utiliser une GPO « Trusted Publishers » ou « Certificate Services Client — Automatique » avec déploiement du certificat dans Ordinateur local. | Parc d’ordinateurs, image maître, VDI. | Traçabilité, conformité, pas d’élévation ad‑hoc. | Requiert un domaine AD et une politique de déploiement. |
Procédure de diagnostic pas‑à‑pas
- Confirmer l’existence du magasin
Test-Path 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\TrustedPublisher' Test-Path 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\TrustedPublisher\Certificates'
Si l’un des deux renvoieFalse
, pré‑créez les clés puis réessayez l’import. - Vérifier le jeton et les privilèges
whoami /groups whoami /priv
Si vous voyez un jeton restreint, essayez soit l’exécution en SYSTEM, soit l’option LocalAccountTokenFilterPolicy. - Tenter un import bas niveau
certutil -addstore -f "TrustedPublisher" ".\adafruit_industries.cer"
Sicertutil
réussit mais PowerShell échoue, privilégiezcertutil
dans votre automation (ou investiguez les modules/.NET installés). - Tracer avec CAPI2 Activez le journal Microsoft‑Windows‑CAPI2/Operational pour obtenir une chronologie détaillée des échecs d’accès au magasin.
Script idempotent « tout‑en‑un »
Le script ci‑dessous couvre la majorité des scénarios : création du magasin, import via PowerShell ou bascule automatique vers certutil
, vérification de la présence du certificat, journalisation explicite. Vous pouvez l’exécuter en Administrateur (ou en SYSTEM via tâche planifiée).
param(
[Parameter(Mandatory)]
[ValidateScript({ Test-Path $_ })]
[string]$FilePath,
[switch]$ForceCertutilFallback
)
function Get-CertThumbprint {
param([string]$Path)
$bytes = [System.IO.File]::ReadAllBytes((Resolve-Path $Path))
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($bytes)
return $cert.Thumbprint.ToUpperInvariant()
}
function Ensure-TrustedPublisherStore {
$base = 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\TrustedPublisher'
if (-not (Test-Path $base)) { New-Item -Path $base -Force | Out-Null }
$certs = Join-Path $base 'Certificates'
if (-not (Test-Path $certs)) { New-Item -Path $certs -Force | Out-Null }
}
function Test-CertPresent {
param([string]$Thumbprint)
try {
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store -ArgumentList @('TrustedPublisher','LocalMachine')
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
$found = $store.Certificates | Where-Object { $_.Thumbprint -eq $Thumbprint }
$store.Close()
return [bool]$found
} catch {
return $false
}
}
function Add-Cert-TrustedPublisher {
param([string]$Path, [switch]$PreferCertutil)
Ensure-TrustedPublisherStore
if ($PreferCertutil) {
Write-Host 'Tentative via certutil...'
$p = Start-Process -FilePath 'certutil.exe' -ArgumentList @('-addstore','-f','TrustedPublisher',"`"$Path`"") -NoNewWindow -PassThru -Wait
return $p.ExitCode
}
try {
Import-Certificate -FilePath $Path -CertStoreLocation 'Cert:\LocalMachine\TrustedPublisher' -ErrorAction Stop | Out-Null
return 0
} catch {
Write-Warning "Import-Certificate a échoué : $($_.Exception.Message)"
Write-Host 'Bascule vers certutil...'
$p = Start-Process -FilePath 'certutil.exe' -ArgumentList @('-addstore','-f','TrustedPublisher',"`"$Path`"") -NoNewWindow -PassThru -Wait
return $p.ExitCode
}
}
# --- Main ---
$thumb = Get-CertThumbprint -Path $FilePath
if (Test-CertPresent -Thumbprint $thumb) {
Write-Host "Déjà présent dans TrustedPublisher : $thumb"
exit 0
}
$code = Add-Cert-TrustedPublisher -Path $FilePath -PreferCertutil:$ForceCertutilFallback
if ($code -ne 0) {
Write-Error "Échec de l'import (code retour $code). Consultez le journal CAPI2."
exit $code
}
if (Test-CertPresent -Thumbprint $thumb) {
Write-Host "Import réussi : $thumb"
exit 0
} else {
Write-Error "Import non vérifié malgré un code retour $code."
exit 1
}
Exécution en SYSTEM sans outil tiers (tâche planifiée)
Lorsque l’UAC ou le filtrage du jeton gêne, faites exécuter le script précédent par le compte LocalSystem via le Planificateur de tâches :
- Copiez le script dans
C:\Temp\InstallCert.ps1
ainsi que le fichier.cer
dansC:\Temp
. - Dans une session PowerShell (hôte ou VM), lancez :
$path = 'C:\Temp\adafruit_industries.cer'
$cmd = "-NoProfile -ExecutionPolicy Bypass -File C:\Temp\InstallCert.ps1 -FilePath `"$path`""
$action = New-ScheduledTaskAction -Execute 'PowerShell.exe' -Argument $cmd
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest
$task = New-ScheduledTask -Action $action -Principal $principal
Register-ScheduledTask -TaskName 'InstallTrustedPublisherCert' -InputObject $task -Force
Start-ScheduledTask -TaskName 'InstallTrustedPublisherCert'
Start-Sleep -Seconds 5
Get-ScheduledTask -TaskName 'InstallTrustedPublisherCert' | Get-ScheduledTaskInfo | Format-List *
Une fois terminé, supprimez la tâche et les fichiers temporaires pour ne pas polluer l’image.
Méthode GPO (déploiement à grande échelle)
Pour une image de référence ou un parc, évitez les scripts ad‑hoc et passez par la stratégie de groupe :
- Créez un objet GPO ciblant le Contexte ordinateur.
- Déployez le certificat dans Ordinateur > Paramètres Windows > Paramètres de sécurité > Stratégies de clés publiques > Éditeurs de confiance.
- Vérifiez l’application via
gpresult /h
etcertlm.msc
.
Avantages : traçabilité, conformité, révocation centralisée, logs unifiés.
Comparatif Import-Certificate vs certutil
Critère | Import-Certificate | certutil |
---|---|---|
Niveau d’abstraction | Cmdlet .NET/PowerShell (facile à intégrer). | Outil CAPI natif (bas niveau, très robuste). |
Dépendances | Modules PowerShell & API .NET sous‑jacentes. | Inclus avec Windows (pas de modules requis). |
Résilience magasin « lazily created » | Peut échouer si la clé registre n’existe pas. | Tend à réussir, crée/force mieux le magasin. |
Gestion des codes retour | Exceptions PowerShell (try/catch). | Codes numériques (0 = succès), journal CAPI2. |
Checklist de durcissement et de bonnes pratiques
- Ne désactivez pas globalement l’UAC pour « faire marcher » un import ponctuel. Préférez l’exécution sous SYSTEM ou la GPO.
- Conservez un dépôt versionné des fichiers
.cer
(Git, artefacts CI) et signez vos scripts d’installation. - Validez la chaîne : assurez‑vous que l’AC racine (ou intermédiaire) de l’éditeur est dans Root, sinon la signature de l’éditeur ne sera pas considérée comme approuvée même si le certificat est présent dans TrustedPublisher.
- Journalisez systématiquement l’empreinte (thumbprint) importée et vérifiez‑la après import.
- Nettoyez l’image : supprimez tâches, scripts et certificats temporaires une fois l’opération terminée.
- Automatisez l’import avant la généralisation de l’image (audit mode / OOBE) pour éviter d’avoir à ouvrir une session graphique.
FAQ rapide
Pourquoi l’import réussit dans Root mais échoue dans TrustedPublisher ?
Les exigences d’accès aux magasins diffèrent. TrustedPublisher est rarement initialisé sur une image neuve et peut nécessiter un jeton plus « complet ». Root est quasi toujours présent.
Pourquoi ça marche dès que j’ouvre une session graphique dans la VM ?
La première session interactive provoque souvent la création paresseuse des clés du magasin. Ensuite, Import-Certificate
fonctionne au prochain passage.
Dois‑je redémarrer après avoir défini LocalAccountTokenFilterPolicy=1
?
Le changement est parfois pris en compte immédiatement, mais un redémarrage garantit l’application et simplifie le dépannage.
Est‑il risqué d’activer LocalAccountTokenFilterPolicy=1
?
Oui, c’est moins strict pour les connexions distantes du compte local. Limitez‑le aux environnements isolés/lab ou utilisez‑le de façon transitoire durant l’automatisation.
Comment retirer un certificat « TrustedPublisher » en cas d’erreur ?
Utilisez l’empreinte pour le supprimer :
$thumb = 'VOTRE_EMPREINTE_SANS_ESPACES'
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store -ArgumentList @('TrustedPublisher','LocalMachine')
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$cert = $store.Certificates | Where-Object { $_.Thumbprint -eq $thumb }
if ($cert) { $store.Remove($cert) }
$store.Close()
Exemple complet de pipeline (PowerShell Direct)
Le bloc suivant illustre une séquence robuste depuis l’hôte Hyper‑V : création du magasin, copie du certificat, import (avec repli), contrôle de présence, puis nettoyage. À adapter à votre orchestrateur.
# Sur l'hôte Hyper-V
$vmName = 'MaVM'
$certPath = 'C:\Temp\adafruit_industries.cer'
# Ouvrir une session PowerShell Direct
$session = New-PSSession -VMName $vmName -Credential (Get-Credential)
# Copier le .cer dans la VM
Copy-Item -Path $certPath -Destination 'C:\Temp' -ToSession $session -Force
# Exécuter l'import robuste dans la VM
Invoke-Command -Session $session -ScriptBlock {
param($path)
New-Item -Path 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\TrustedPublisher' -Force | Out-Null
New-Item -Path 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\TrustedPublisher\Certificates' -Force | Out-Null
try {
Import-Certificate -FilePath $path -CertStoreLocation 'Cert:\LocalMachine\TrustedPublisher' -ErrorAction Stop | Out-Null
} catch {
Start-Process -FilePath 'certutil.exe' -ArgumentList @('-addstore','-f','TrustedPublisher',"`"$path`"") -NoNewWindow -Wait
}
# Vérification
$bytes = [System.IO.File]::ReadAllBytes($path)
$c = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($bytes)
$tp = $c.Thumbprint.ToUpperInvariant()
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store -ArgumentList @('TrustedPublisher','LocalMachine')
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
$ok = $store.Certificates | Where-Object { $_.Thumbprint -eq $tp }
$store.Close()
if ($ok) { "OK: $tp" } else { throw "Échec: $tp introuvable" }
} -ArgumentList 'C:\Temp\adafruit_industries.cer'
Erreurs connexes à connaître
0x800B0109 (CERT_E_UNTRUSTEDROOT)
: la chaîne de confiance n’est pas complète (racine manquante dans Root).0x80092004 (CRYPT_E_NOT_FOUND)
: magasin non initialisé ou fichier .cer invalide.0x80090008 (NTE_BAD_ALGID)
: algorithme non supporté sur un OS ancien/limité.
Points clés à retenir
- L’erreur
E_ACCESSDENIED
lors d’unImport-Certificate
dans TrustedPublisher sur une VM Hyper‑V neuve est le plus souvent liée au magasin non matérialisé et/ou au filtrage UAC en session distante. - La pré‑création du magasin + un repli
certutil
règle l’écrasante majorité des cas sans modifier la posture sécurité. - Pour l’industrialisation, préférez GPO ou une tâche planifiée SYSTEM.
- Toujours vérifier par empreinte après import et journaliser l’opération.
Note : dans plusieurs échanges communautaires, aucune solution technique n’a été fournie et des redirections « génériques » ont été proposées. Cet article rassemble les causes connues et des procédures concrètes pour corriger l’erreur de manière reproductible.