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.
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
(ouBypass
) 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 | À faire | Remarque |
---|---|---|---|
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 complexes | Chaîne unique imbriquée | -EncodedCommand | Encodage Unicode puis Base64. |
Combiner -Verb RunAs et -Credential | Les utiliser ensemble | Choisir l’un OU l’autre | Mutuellement 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écutable | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe | Installé côte‑à‑côte, pwsh.exe |
-ExecutionPolicy | Pris en charge | Pris en charge sur Windows (ignoré sur Linux/macOS) |
Modules/Compatibilité | Compatibilité large avec héritage | Plus 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
Niveau | Définition rapide | Usage typique en déploiement |
---|---|---|
Restricted | Aucun script autorisé | Environnement verrouillé (défaut fréquent) |
AllSigned | Scripts signés uniquement | Contexte PKI d’entreprise |
RemoteSigned | Locaux OK, téléchargés signés | Bon compromis sécurité/usage |
Bypass | Aucune vérification | Processus lancé uniquement via -ExecutionPolicy Bypass |
Unrestricted | Autorise 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/Comportement | Cause probable | Correctif |
---|---|---|
« Running scripts is disabled on this system » | Execution Policy restrictive | Ajouter -ExecutionPolicy RemoteSigned ou Bypass , signer/unblock |
La console élevée s’ouvre mais rien ne s’exécute | Chemin du script entre guillemets simples | Utiliser "C:\Chemin\Mon Script.ps1" après -File |
Code retour inattendu | Le script ne fait pas exit <code> | Standardiser les exit côté .ps1 et lire $p.ExitCode |
Échec en contexte 32‑bit | Redirection vers SysWOW64 | Utiliser le chemin Sysnative pour forcer 64‑bit |
Échec à distance sans UI | -Verb RunAs requiert l’interactif | Tâ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.