PowerShell : exécuter un script en Administrateur (session élevée) — RunAs, guillemets et ExecutionPolicy

Besoin de lancer un script .ps1 sur des postes cibles en session élevée sans se battre avec les guillemets ni l’ExecutionPolicy ? Voici des modèles Start-Process -Verb RunAs pour 5.1/7+, avec explications, cas particuliers (UNC, 32/64‑bit), journalisation et dépannage.

Sommaire

Objectif et symptôme

Objectif : déclencher un script PowerShell distribué sur des machines clientes dans une session élevée (Administrateur), tout en contrôlant l’expérience (attente, code retour, journalisation) et en évitant les blocages de stratégie d’exécution.

Symptôme courant : un appel naïf ouvre bien une nouvelle console élevée, mais le script ne s’y exécute pas et reste dans la session appelante. La cause : mauvais guillemets autour du chemin après -File et/ou ExecutionPolicy restrictive (souvent Restricted).

$CommandLine = "-file 'C:\temp\MyScript.ps1'"
Start-Process -FilePath powershell.exe -Verb RunAs -ArgumentList $CommandLine

Solution recommandée — modèles prêts à l’emploi

Windows PowerShell 5.1 (forme simple, chaîne unique)

Start-Process -FilePath 'powershell.exe' -Verb RunAs `
  -ArgumentList '-NoProfile -ExecutionPolicy RemoteSigned -File "C:\Temp\MyScript.ps1"'

Windows PowerShell 5.1 (forme robuste, tableau d’arguments)

Le tableau supprime 95 % des problèmes d’échappement :

$args = @(
  '-NoProfile',
  '-ExecutionPolicy','RemoteSigned',  # ou 'Bypass' pour ce processus uniquement
  '-File','C:\Temp\MyScript.ps1'
)
Start-Process -FilePath 'powershell.exe' -Verb RunAs -ArgumentList $args

Attendre la fin et récupérer le code retour

$p = Start-Process -FilePath 'powershell.exe' -Verb RunAs -ArgumentList $args -PassThru -Wait
$exitCode = $p.ExitCode
Write-Host "Script terminé avec le code: $exitCode"

PowerShell 7+ (remplacer par pwsh.exe)

Start-Process -FilePath 'pwsh.exe' -Verb RunAs `
  -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File "C:\Temp\MyScript.ps1"'

Pourquoi ces modèles fonctionnent

  • Chemin après -File entre guillemets doubles : powershell.exe/pwsh.exe attendent le chemin du script entre guillemets doubles. Les guillemets simples sont transmis littéralement et empêchent la résolution correcte si le chemin contient des espaces.
  • Stratégie d’exécution : -ExecutionPolicy RemoteSigned (ou Bypass) neutralise le blocage courant (Restricted) pour le processus lancé seulement. Rappel : l’Execution Policy n’est pas une barrière de sécurité, c’est une politique d’exécution locale.
  • -NoProfile : exécution plus rapide et prédictible (pas de profils utilisateur qui modifient l’environnement).
  • -Verb RunAs : demande l’élévation via UAC. Combiné à -Wait, la session appelante attend la fin du script élevé, ce qui facilite l’orchestration.

Passer des paramètres à votre script .ps1

Après -File "MonScript.ps1", tout ce qui suit est remis au script comme arguments. Exemples :

Paramètres nommés et valeurs avec espaces

$args = @(
  '-NoProfile','-ExecutionPolicy','Bypass','-File','C:\Temp\MyScript.ps1',
  '-ComputerName','PC-001',
  '-Path','C:\Program Files\MonAppli'
)
Start-Process 'powershell.exe' -Verb RunAs -ArgumentList $args -Wait

Utiliser -Command à la place de -File (alternative)

Utile pour des one-liners ou si vous souhaitez encapsuler l’appel avec & (call operator) :

Start-Process 'powershell.exe' -Verb RunAs -ArgumentList @(
  '-NoProfile','-ExecutionPolicy','Bypass','-Command',
  '& "C:\Temp\MyScript.ps1" -ComputerName PC-001 -Path "C:\Program Files\MonAppli"'
) -Wait

Éviter les guillemets piégeux : encodez la commande

Pour des scénarios extrêmes (caractères spéciaux, pipelines complexes), encodez la commande en Base64 (Unicode) et utilisez -EncodedCommand :

$cmd   = '& "C:\Temp\MyScript.ps1" -ComputerName "PC-001" -Path "C:\Program Files\MonAppli"'
$bytes = [Text.Encoding]::Unicode.GetBytes($cmd)
$enc   = [Convert]::ToBase64String($bytes)

Start-Process 'powershell.exe' -Verb RunAs -ArgumentList @(
'-NoProfile','-ExecutionPolicy','Bypass','-EncodedCommand', \$enc
) -Wait 

Tableau : guillemets & échappement — le pense‑bête

SituationÀ éviterÀ faireRemarque
Chemin de script après -File'C:\Temp\Mon Script.ps1'"C:\Temp\Mon Script.ps1"Les guillemets simples sont littéraux pour l’exécutable.
Passer des valeurs avec espaces-Path C:\Program Files\...-Path "C:\Program Files\..." ou tableau @('-Path','C:\Program Files\...')Le tableau d’arguments évite l’échappement.
Arguments très complexesChaîne unique imbriquée-EncodedCommandEncodage Unicode puis Base64.
Combiner -Verb RunAs et -CredentialLes utiliser ensembleChoisir l’un OU l’autreMutuellement exclusifs dans Start-Process.

Codes retour : de bout en bout

Pour remonter un code précis au parent :

# Dans C:\Temp\MyScript.ps1
try {
  # ... votre logique
  exit 0  # succès
}
catch {
  Write-Error $_
  exit 10 # code spécifique à votre appli
}

Côté lanceur :

$p = Start-Process 'powershell.exe' -Verb RunAs -ArgumentList $args -PassThru -Wait
switch ($p.ExitCode) {
  0  { 'OK'; break }
  10 { 'Erreur métier'; break }
  default { "Échec (code $($p.ExitCode))" }
}

PowerShell 5.1 vs 7+ : différences utiles

Point cléWindows PowerShell 5.1 (powershell.exe)PowerShell 7+ (pwsh.exe)
ExécutableC:\Windows\System32\WindowsPowerShell\v1.0\powershell.exeInstallé côte‑à‑côte, pwsh.exe
-ExecutionPolicyPris en chargePris en charge sur Windows (ignoré sur Linux/macOS)
Modules/CompatibilitéCompatibilité large avec héritagePlus moderne, parfois besoin de 5.1 pour anciens modules

Auto‑élévation dans le script (prêt à coller)

Relance automatique du script en Admin si nécessaire :

# Au tout début de C:\Temp\MyScript.ps1
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
  Start-Process -FilePath 'powershell.exe' -Verb RunAs `
    -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`""
  exit
}

Journalisation et traçabilité (Transcript)

En production, enregistrez un transcript pour chaque exécution élevée.

$log = "C:\Temp\Logs\MyScript_{0:yyyyMMdd_HHmmss}.log" -f (Get-Date)
Start-Transcript -Path $log -Force
try {
  # ... votre logique
}
finally {
  Stop-Transcript
}

Cas particuliers & environment gotchas

Exécuter en 64‑bit depuis un contexte 32‑bit

Depuis un processus 32‑bit, powershell.exe pointe vers SysWOW64. Pour forcer la version 64‑bit :

$exe64 = "$env:WINDIR\Sysnative\WindowsPowerShell\v1.0\powershell.exe"
Start-Process $exe64 -Verb RunAs -ArgumentList @(
  '-NoProfile','-ExecutionPolicy','Bypass','-File','C:\Temp\MyScript.ps1'
) -Wait

Chemins UNC et Zone d’origine

  • Les scripts stockés sur un partage réseau peuvent être marqués « téléchargés » (Zone.Identifier) et déclencher des exigences de signature.
  • Solutions : Unblock-File lors du déploiement, copier en local avant exécution, ou -ExecutionPolicy Bypass pour ce processus.

Répertoire de travail

Si votre script utilise des chemins relatifs, fixez le répertoire de travail :

Start-Process 'powershell.exe' -Verb RunAs -WorkingDirectory 'C:\Temp' -ArgumentList @(
  '-NoProfile','-ExecutionPolicy','Bypass','-File','C:\Temp\MyScript.ps1'
) -Wait

Exécution silencieuse

Pour limiter l’impact visuel :

Start-Process 'powershell.exe' -Verb RunAs -WindowStyle Hidden -ArgumentList @(
  '-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-File','C:\Temp\MyScript.ps1'
) -Wait

Note : l’invite UAC peut quand même apparaître selon la stratégie.

Exécuter élevé à distance (sans session interactive)

-Verb RunAs nécessite une session interactive. Pour un déploiement « silencieux » et réellement élevé sur des postes distants, créez puis déclenchez une tâche planifiée avec privilèges les plus élevés :

$action     = New-ScheduledTaskAction -Execute 'powershell.exe' `
  -Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\Temp\MyScript.ps1"'
$principal  = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$settings   = New-ScheduledTaskSettingsSet -Compatibility Win8 -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
$task       = New-ScheduledTask -Action $action -Principal $principal -Settings $settings
Register-ScheduledTask -TaskName 'RunMyScriptElevated' -InputObject $task -Force
Start-ScheduledTask -TaskName 'RunMyScriptElevated'
# (Optionnel) Attendre la fin, puis nettoyer:
Start-Sleep -Seconds 10
Unregister-ScheduledTask -TaskName 'RunMyScriptElevated' -Confirm:$false

Tableau : Execution Policy — portée & impact

NiveauDéfinition rapideUsage typique en déploiement
RestrictedAucun script autoriséEnvironnement verrouillé (défaut fréquent)
AllSignedScripts signés uniquementContexte PKI d’entreprise
RemoteSignedLocaux OK, téléchargés signésBon compromis sécurité/usage
BypassAucune vérificationProcessus lancé uniquement via -ExecutionPolicy Bypass
UnrestrictedAutorise tout, avec avertissementsÀ éviter en prod

Contrôles de sécurité & bonnes pratiques

  • Signer vos scripts quand c’est possible (AllSigned/RemoteSigned).
  • Limiter Bypass au processus lancé plutôt qu’à la machine (Set-ExecutionPolicy machine‑wide à éviter).
  • Ne mélangez pas -Verb RunAs et -Credential : ils s’excluent.
  • Surveillez le code retour et journalisez systématiquement.
  • Testez 32/64‑bit et UNC vs local (copie locale recommandée).

Diagnostic rapide (erreurs courantes)

Message/ComportementCause probableCorrectif
« Running scripts is disabled on this system »Execution Policy restrictiveAjouter -ExecutionPolicy RemoteSigned ou Bypass, signer/unblock
La console élevée s’ouvre mais rien ne s’exécuteChemin du script entre guillemets simplesUtiliser "C:\Chemin\Mon Script.ps1" après -File
Code retour inattenduLe script ne fait pas exit <code>Standardiser les exit côté .ps1 et lire $p.ExitCode
Échec en contexte 32‑bitRedirection vers SysWOW64Utiliser le chemin Sysnative pour forcer 64‑bit
Échec à distance sans UI-Verb RunAs requiert l’interactifTâche planifiée avec Highest ou service

Modèles prêts à l’emploi (copier‑coller)

Lancer un script élevé, attendre, journaliser, remonter le code

$script  = 'C:\Temp\MyScript.ps1'
$logFile = "C:\Temp\Logs\Run_{0:yyyyMMdd_HHmmss}.log" -f (Get-Date)
$args    = @(
  '-NoProfile','-NonInteractive',
  '-ExecutionPolicy','RemoteSigned',
  '-File', $script
)

# Lancer élevé

\$p = Start-Process 'powershell.exe' -Verb RunAs -ArgumentList \$args -PassThru -Wait

# Journal minimal

"{0\:u} - Script: {1} - ExitCode: {2}" -f (Get-Date), \$script, \$p.ExitCode | Out-File -FilePath \$logFile -Append

# Interprétation

if (\$p.ExitCode -eq 0) { 'Succès' } else { "Échec (\$(\$p.ExitCode))" } 

Détection automatique 5.1 vs 7+ et fallback

$exeCandidates = @('pwsh.exe','powershell.exe')
$exe = $exeCandidates | Where-Object { Get-Command $_ -ErrorAction SilentlyContinue } | Select-Object -First 1
if (-not $exe) { throw "Aucun moteur PowerShell trouvé." }

\$args = @('-NoProfile','-ExecutionPolicy','Bypass','-File','C:\Temp\MyScript.ps1')
Start-Process \$exe -Verb RunAs -ArgumentList \$args -Wait 

Copier depuis un partage UNC puis exécuter localement (fiable)

$src  = '\\Serveur\Share\MyScript.ps1'
$dest = 'C:\Temp\MyScript.ps1'
New-Item -ItemType Directory -Path (Split-Path $dest) -Force | Out-Null
Copy-Item $src $dest -Force
Unblock-File $dest

Start-Process 'powershell.exe' -Verb RunAs -ArgumentList @(
'-NoProfile','-ExecutionPolicy','RemoteSigned','-File',\$dest
) -Wait 

FAQ rapide

Q : Pourquoi -File 'C:\x.ps1' échoue ?
R : Les guillemets simples sont transmis tels quels au processus natif ; il ne peut pas résoudre le chemin correctement. Utilisez des guillemets doubles ou un tableau d’arguments.

Q : Bypass est‑il dangereux ?
R : C’est une politique d’exécution, pas une barrière de sécurité. Limitez‑la au processus lancé, signez vos scripts et contrôlez la source des fichiers.

Q : Puis‑je élever et changer d’utilisateur en même temps ?
R : Non. -Verb RunAs et -Credential sont exclusifs. Utilisez une tâche planifiée si vous devez exécuter en SYSTEM ou sous un autre compte.

Q : Comment vérifier l’élévation dans le script ?
R : Utilisez :

$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

Conclusion

En pratique, la recette fiable pour exécuter un script PowerShell en mode Administrateur sur des postes distribués consiste à : (1) lancer powershell.exe/pwsh.exe avec -Verb RunAs, (2) toujours entourer le chemin du script de guillemets doubles après -File, (3) lever les blocages via -ExecutionPolicy RemoteSigned ou Bypass au seul niveau du processus, (4) ajouter -Wait et lire ExitCode, et (5) basculer vers une tâche planifiée élevée pour les scénarios distants sans interface. Les modèles ci‑dessus couvrent ces besoins, avec des garde‑fous pour UNC, 32/64‑bit et journalisation, afin d’obtenir des déploiements stables et auditables.

Sommaire