Skip to content

Execution Policy & Script Launcher

Maîtriser l'Execution Policy et créer un framework de lancement de scripts PowerShell.


Execution Policy

Comprendre les Niveaux

L'Execution Policy contrôle quels scripts peuvent s'exécuter sur le système.

Policy Description Usage
Restricted Aucun script ne peut s'exécuter Default Windows client
AllSigned Seuls les scripts signés peuvent s'exécuter Production sécurisée
RemoteSigned Scripts locaux OK, distants doivent être signés Recommandé serveurs
Unrestricted Tous les scripts, avertissement pour distants Développement
Bypass Aucune restriction, aucun avertissement Automatisation CI/CD
Undefined Pas de policy définie à ce scope -

Scopes (Priorité décroissante)

# Voir toutes les policies par scope
Get-ExecutionPolicy -List

# Scope          ExecutionPolicy
# -----          ---------------
# MachinePolicy  Undefined        # GPO Machine (priorité max)
# UserPolicy     Undefined        # GPO User
# Process        Undefined        # Session PowerShell actuelle
# CurrentUser    RemoteSigned     # Registre utilisateur
# LocalMachine   RemoteSigned     # Registre machine

Consulter et Modifier

# Voir la policy effective
Get-ExecutionPolicy

# Modifier pour la machine (nécessite Admin)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine

# Modifier pour l'utilisateur courant (sans Admin)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

# Modifier pour la session uniquement
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

Contourner les Restrictions

Méthodes Sans Modifier la Policy

# 1. Bypass en ligne de commande
powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\script.ps1"

# 2. Bypass avec NoProfile (plus rapide)
powershell.exe -ExecutionPolicy Bypass -NoProfile -File "script.ps1"

# 3. Lire et exécuter le contenu
Get-Content "script.ps1" | Invoke-Expression

# 4. Télécharger et exécuter (attention sécurité!)
iex (New-Object Net.WebClient).DownloadString('https://example.com/script.ps1')

# 5. Encoder en Base64
$command = Get-Content "script.ps1" -Raw
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encoded = [Convert]::ToBase64String($bytes)
powershell.exe -EncodedCommand $encoded

Via Batch Wrapper

@echo off
REM launcher.bat - Lance un script PowerShell en bypass
powershell.exe -ExecutionPolicy Bypass -NoProfile -File "%~dp0%1"
pause

Script Launcher Framework

Launcher Simple (GUI)

<#
.SYNOPSIS
    Script Launcher - Liste et exécute les scripts du répertoire courant.

.DESCRIPTION
    Framework interactif pour lancer des scripts PowerShell avec élévation Admin.

.NOTES
    Placer dans le répertoire contenant vos scripts.
    Double-cliquer ou lancer via le .bat associé.
#>

#Requires -Version 5.1

# === Configuration ===
$ScriptPath = $PSScriptRoot
if (-not $ScriptPath) { $ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition }

# === Fonctions ===
function Show-Menu {
    param(
        [string]$Title,
        [array]$Options
    )

    Clear-Host
    Write-Host "╔════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
    Write-Host "║  $Title" -ForegroundColor Cyan -NoNewline
    Write-Host (" " * (60 - $Title.Length - 3)) -NoNewline
    Write-Host "║" -ForegroundColor Cyan
    Write-Host "╠════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan

    for ($i = 0; $i -lt $Options.Count; $i++) {
        $num = $i + 1
        $option = $Options[$i]
        Write-Host "║  " -ForegroundColor Cyan -NoNewline
        Write-Host "[$num]" -ForegroundColor Yellow -NoNewline
        Write-Host " $option" -NoNewline
        $padding = 60 - 7 - $option.Length
        Write-Host (" " * [Math]::Max(0, $padding)) -NoNewline
        Write-Host "║" -ForegroundColor Cyan
    }

    Write-Host "║  " -ForegroundColor Cyan -NoNewline
    Write-Host "[0]" -ForegroundColor Red -NoNewline
    Write-Host " Quitter" -NoNewline
    Write-Host (" " * 45) -NoNewline
    Write-Host "║" -ForegroundColor Cyan
    Write-Host "╚════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
    Write-Host ""
}

function Test-Administrator {
    $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Invoke-AsAdmin {
    param([string]$ScriptPath)

    $arguments = "-ExecutionPolicy Bypass -NoProfile -File `"$ScriptPath`""

    Start-Process -FilePath "powershell.exe" `
                  -ArgumentList $arguments `
                  -Verb RunAs `
                  -Wait
}

# === Main ===
$isAdmin = Test-Administrator
$adminStatus = if ($isAdmin) { "[ADMIN]" } else { "[USER]" }

# Récupérer les scripts (exclure ce launcher)
$scripts = Get-ChildItem -Path $ScriptPath -Filter "*.ps1" |
           Where-Object { $_.Name -ne $MyInvocation.MyCommand.Name } |
           Sort-Object Name

if ($scripts.Count -eq 0) {
    Write-Host "Aucun script trouvé dans: $ScriptPath" -ForegroundColor Red
    Read-Host "Appuyez sur Entrée pour quitter"
    exit
}

# Boucle principale
do {
    $scriptNames = $scripts | ForEach-Object { $_.BaseName }
    Show-Menu -Title "SCRIPT LAUNCHER $adminStatus" -Options $scriptNames

    Write-Host "Admin actuel: " -NoNewline
    if ($isAdmin) {
        Write-Host "Oui" -ForegroundColor Green
    } else {
        Write-Host "Non (tapez 'admin' + numéro pour élever)" -ForegroundColor Yellow
    }
    Write-Host ""

    $choice = Read-Host "Choix"

    if ($choice -eq "0") {
        break
    }
    elseif ($choice -match "^admin\s*(\d+)$") {
        $num = [int]$Matches[1]
        if ($num -ge 1 -and $num -le $scripts.Count) {
            $selectedScript = $scripts[$num - 1].FullName
            Write-Host "`nLancement en tant qu'Admin: $($scripts[$num - 1].Name)" -ForegroundColor Magenta
            Invoke-AsAdmin -ScriptPath $selectedScript
        }
    }
    elseif ($choice -match "^\d+$") {
        $num = [int]$choice
        if ($num -ge 1 -and $num -le $scripts.Count) {
            $selectedScript = $scripts[$num - 1].FullName
            Write-Host "`nExécution: $($scripts[$num - 1].Name)" -ForegroundColor Green
            Write-Host ("-" * 60)

            try {
                & $selectedScript
            }
            catch {
                Write-Host "Erreur: $_" -ForegroundColor Red
            }

            Write-Host ("-" * 60)
            Read-Host "`nAppuyez sur Entrée pour continuer"
        }
    }
} while ($true)

Write-Host "`nAu revoir!" -ForegroundColor Cyan

Fichier Batch Associé

@echo off
REM ScriptLauncher.bat - Double-cliquer pour lancer le menu
REM Placer dans le même répertoire que ScriptLauncher.ps1

cd /d "%~dp0"
powershell.exe -ExecutionPolicy Bypass -NoProfile -File "%~dp0ScriptLauncher.ps1"

Launcher Avancé avec Catégories

<#
.SYNOPSIS
    Advanced Script Launcher avec catégories et logging.
#>

#Requires -Version 5.1

param(
    [switch]$NoMenu,
    [string]$RunScript
)

# === Configuration ===
$Config = @{
    ScriptPath    = $PSScriptRoot
    LogPath       = Join-Path $PSScriptRoot "Logs"
    Categories    = @{
        "Install_"   = "Installation"
        "Config_"    = "Configuration"
        "Audit_"     = "Audit & Rapports"
        "Maint_"     = "Maintenance"
        "Deploy_"    = "Déploiement"
    }
    DefaultCategory = "Autres"
}

# === Classes ===
class ScriptInfo {
    [string]$Name
    [string]$FullPath
    [string]$Category
    [string]$Description
    [bool]$RequiresAdmin

    ScriptInfo([System.IO.FileInfo]$file, [hashtable]$categories, [string]$default) {
        $this.Name = $file.BaseName
        $this.FullPath = $file.FullName
        $this.Category = $default

        # Déterminer la catégorie
        foreach ($prefix in $categories.Keys) {
            if ($file.Name.StartsWith($prefix)) {
                $this.Category = $categories[$prefix]
                break
            }
        }

        # Parser les métadonnées du script
        $content = Get-Content $file.FullName -TotalCount 30 -ErrorAction SilentlyContinue
        $this.Description = ($content | Where-Object { $_ -match '^\s*\.SYNOPSIS' } |
                            Select-Object -First 1) -replace '^\s*\.SYNOPSIS\s*', ''
        $this.RequiresAdmin = ($content -match '#Requires\s+-RunAsAdministrator').Count -gt 0
    }
}

# === Fonctions ===
function Write-Log {
    param(
        [string]$Message,
        [ValidateSet("INFO", "WARN", "ERROR", "SUCCESS")]
        [string]$Level = "INFO"
    )

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logEntry = "[$timestamp] [$Level] $Message"

    # Créer le dossier Logs si nécessaire
    if (-not (Test-Path $Config.LogPath)) {
        New-Item -ItemType Directory -Path $Config.LogPath -Force | Out-Null
    }

    $logFile = Join-Path $Config.LogPath "launcher_$(Get-Date -Format 'yyyyMMdd').log"
    Add-Content -Path $logFile -Value $logEntry

    # Console
    $color = switch ($Level) {
        "INFO"    { "White" }
        "WARN"    { "Yellow" }
        "ERROR"   { "Red" }
        "SUCCESS" { "Green" }
    }
    Write-Host $logEntry -ForegroundColor $color
}

function Get-ScriptsByCategory {
    param([array]$Scripts)

    $grouped = @{}
    foreach ($script in $Scripts) {
        if (-not $grouped.ContainsKey($script.Category)) {
            $grouped[$script.Category] = @()
        }
        $grouped[$script.Category] += $script
    }
    return $grouped
}

function Show-CategoryMenu {
    param([hashtable]$GroupedScripts)

    Clear-Host
    $isAdmin = Test-Administrator

    Write-Host ""
    Write-Host "  ╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
    Write-Host "  ║              SCRIPT LAUNCHER - MENU PRINCIPAL                ║" -ForegroundColor Cyan
    Write-Host "  ╠══════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan

    # Status Admin
    Write-Host "  ║  Status: " -ForegroundColor Cyan -NoNewline
    if ($isAdmin) {
        Write-Host "ADMINISTRATEUR" -ForegroundColor Green -NoNewline
    } else {
        Write-Host "UTILISATEUR STANDARD" -ForegroundColor Yellow -NoNewline
    }
    Write-Host (" " * (48 - $(if ($isAdmin) { 14 } else { 20 }))) -NoNewline
    Write-Host "║" -ForegroundColor Cyan

    Write-Host "  ╠══════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan

    $index = 1
    $categoryIndex = @{}

    foreach ($category in ($GroupedScripts.Keys | Sort-Object)) {
        $count = $GroupedScripts[$category].Count
        $categoryIndex[$index] = $category

        Write-Host "  ║  " -ForegroundColor Cyan -NoNewline
        Write-Host "[$index]" -ForegroundColor Yellow -NoNewline
        Write-Host " $category " -NoNewline -ForegroundColor White
        Write-Host "($count scripts)" -ForegroundColor DarkGray -NoNewline

        $padding = 60 - 7 - $category.Length - " ($count scripts)".Length
        Write-Host (" " * [Math]::Max(0, $padding)) -NoNewline
        Write-Host "║" -ForegroundColor Cyan

        $index++
    }

    Write-Host "  ╠══════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
    Write-Host "  ║  " -ForegroundColor Cyan -NoNewline
    Write-Host "[R]" -ForegroundColor Magenta -NoNewline
    Write-Host " Rafraîchir    " -NoNewline
    Write-Host "[L]" -ForegroundColor Magenta -NoNewline
    Write-Host " Logs    " -NoNewline
    Write-Host "[0]" -ForegroundColor Red -NoNewline
    Write-Host " Quitter                   ║" -ForegroundColor Cyan
    Write-Host "  ╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
    Write-Host ""

    return $categoryIndex
}

function Show-ScriptsMenu {
    param(
        [string]$Category,
        [array]$Scripts
    )

    Clear-Host
    Write-Host ""
    Write-Host "  ╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
    Write-Host "  ║  Catégorie: $Category" -ForegroundColor Cyan -NoNewline
    Write-Host (" " * (50 - $Category.Length)) -NoNewline
    Write-Host "║" -ForegroundColor Cyan
    Write-Host "  ╠══════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan

    for ($i = 0; $i -lt $Scripts.Count; $i++) {
        $script = $Scripts[$i]
        $num = $i + 1
        $adminIcon = if ($script.RequiresAdmin) { "[A]" } else { "   " }

        Write-Host "  ║  " -ForegroundColor Cyan -NoNewline
        Write-Host "[$num]" -ForegroundColor Yellow -NoNewline
        Write-Host " $adminIcon " -ForegroundColor Red -NoNewline
        Write-Host $script.Name -NoNewline

        $padding = 60 - 10 - $script.Name.Length
        Write-Host (" " * [Math]::Max(0, $padding)) -NoNewline
        Write-Host "║" -ForegroundColor Cyan
    }

    Write-Host "  ╠══════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
    Write-Host "  ║  " -ForegroundColor Cyan -NoNewline
    Write-Host "[A]" -ForegroundColor Red -NoNewline
    Write-Host " = Requiert Admin    " -NoNewline
    Write-Host "[B]" -ForegroundColor Magenta -NoNewline
    Write-Host " Retour    " -NoNewline
    Write-Host "[0]" -ForegroundColor Red -NoNewline
    Write-Host " Quitter      ║" -ForegroundColor Cyan
    Write-Host "  ╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "  Préfixez avec 'admin ' pour forcer l'élévation (ex: admin 1)" -ForegroundColor DarkGray
    Write-Host ""
}

function Invoke-Script {
    param(
        [ScriptInfo]$Script,
        [switch]$AsAdmin
    )

    Write-Log "Exécution: $($Script.Name)" -Level INFO

    $needsElevation = $Script.RequiresAdmin -or $AsAdmin

    if ($needsElevation -and -not (Test-Administrator)) {
        Write-Log "Élévation requise pour: $($Script.Name)" -Level WARN

        $arguments = "-ExecutionPolicy Bypass -NoProfile -File `"$($Script.FullPath)`""
        Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -Verb RunAs -Wait

        Write-Log "Script terminé (élevé): $($Script.Name)" -Level SUCCESS
    }
    else {
        try {
            & $Script.FullPath
            Write-Log "Script terminé: $($Script.Name)" -Level SUCCESS
        }
        catch {
            Write-Log "Erreur: $($_.Exception.Message)" -Level ERROR
        }
    }
}

function Test-Administrator {
    $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

# === Main ===

# Charger les scripts
$scriptFiles = Get-ChildItem -Path $Config.ScriptPath -Filter "*.ps1" |
               Where-Object { $_.Name -notmatch "^(ScriptLauncher|Launcher)" }

$scripts = $scriptFiles | ForEach-Object {
    [ScriptInfo]::new($_, $Config.Categories, $Config.DefaultCategory)
}

if ($scripts.Count -eq 0) {
    Write-Host "Aucun script trouvé dans: $($Config.ScriptPath)" -ForegroundColor Red
    exit 1
}

# Mode non-interactif
if ($RunScript) {
    $target = $scripts | Where-Object { $_.Name -eq $RunScript }
    if ($target) {
        Invoke-Script -Script $target
    } else {
        Write-Host "Script non trouvé: $RunScript" -ForegroundColor Red
    }
    exit
}

# Mode interactif
$groupedScripts = Get-ScriptsByCategory -Scripts $scripts

do {
    $categoryIndex = Show-CategoryMenu -GroupedScripts $groupedScripts
    $choice = Read-Host "  Choix"

    switch -Regex ($choice) {
        "^0$" {
            Write-Host "`n  Au revoir!" -ForegroundColor Cyan
            exit
        }
        "^[Rr]$" {
            # Rafraîchir
            continue
        }
        "^[Ll]$" {
            # Ouvrir les logs
            if (Test-Path $Config.LogPath) {
                Invoke-Item $Config.LogPath
            }
        }
        "^\d+$" {
            $num = [int]$choice
            if ($categoryIndex.ContainsKey($num)) {
                $selectedCategory = $categoryIndex[$num]
                $categoryScripts = $groupedScripts[$selectedCategory]

                do {
                    Show-ScriptsMenu -Category $selectedCategory -Scripts $categoryScripts
                    $scriptChoice = Read-Host "  Choix"

                    if ($scriptChoice -eq "0") { exit }
                    if ($scriptChoice -match "^[Bb]$") { break }

                    $forceAdmin = $false
                    if ($scriptChoice -match "^admin\s*(\d+)$") {
                        $forceAdmin = $true
                        $scriptChoice = $Matches[1]
                    }

                    if ($scriptChoice -match "^\d+$") {
                        $scriptNum = [int]$scriptChoice
                        if ($scriptNum -ge 1 -and $scriptNum -le $categoryScripts.Count) {
                            $selectedScript = $categoryScripts[$scriptNum - 1]

                            Write-Host ""
                            Write-Host ("=" * 60) -ForegroundColor DarkGray
                            Invoke-Script -Script $selectedScript -AsAdmin:$forceAdmin
                            Write-Host ("=" * 60) -ForegroundColor DarkGray
                            Read-Host "`n  Appuyez sur Entrée pour continuer"
                        }
                    }
                } while ($true)
            }
        }
    }
} while ($true)

Créer un Raccourci Bureau

# CreateLauncherShortcut.ps1 - Crée un raccourci sur le bureau

$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$env:USERPROFILE\Desktop\Script Launcher.lnk")

$Shortcut.TargetPath = "powershell.exe"
$Shortcut.Arguments = "-ExecutionPolicy Bypass -NoProfile -File `"$PSScriptRoot\ScriptLauncher.ps1`""
$Shortcut.WorkingDirectory = $PSScriptRoot
$Shortcut.IconLocation = "powershell.exe,0"
$Shortcut.Description = "Lance le menu des scripts PowerShell"
$Shortcut.Save()

Write-Host "Raccourci créé sur le bureau!" -ForegroundColor Green

Conventions de Nommage

Pour utiliser les catégories automatiques :

Préfixe Catégorie Exemple
Install_ Installation Install_Office365.ps1
Config_ Configuration Config_Firewall.ps1
Audit_ Audit & Rapports Audit_Users.ps1
Maint_ Maintenance Maint_CleanTemp.ps1
Deploy_ Déploiement Deploy_Agent.ps1

Sécurité : Signer vos Scripts

Créer un Certificat Auto-Signé (Dev)

# Créer un certificat de signature de code
$cert = New-SelfSignedCertificate `
    -Subject "CN=MonEntreprise PowerShell Signing" `
    -Type CodeSigningCert `
    -CertStoreLocation Cert:\CurrentUser\My `
    -NotAfter (Get-Date).AddYears(5)

# Exporter le certificat (pour le distribuer)
Export-Certificate -Cert $cert -FilePath "C:\Certs\PowerShellSigning.cer"

# L'ajouter aux éditeurs de confiance (sur chaque machine)
Import-Certificate -FilePath "C:\Certs\PowerShellSigning.cer" `
                   -CertStoreLocation Cert:\LocalMachine\TrustedPublisher

Signer un Script

# Récupérer le certificat
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert |
        Where-Object { $_.Subject -match "MonEntreprise" }

# Signer le script
Set-AuthenticodeSignature -FilePath "C:\Scripts\MonScript.ps1" -Certificate $cert

# Vérifier la signature
Get-AuthenticodeSignature -FilePath "C:\Scripts\MonScript.ps1"

Script de Signature en Masse

# SignAllScripts.ps1 - Signe tous les scripts d'un répertoire

param(
    [Parameter(Mandatory)]
    [string]$Path,

    [string]$CertSubject = "MonEntreprise"
)

$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert |
        Where-Object { $_.Subject -match $CertSubject } |
        Select-Object -First 1

if (-not $cert) {
    throw "Certificat de signature non trouvé"
}

$scripts = Get-ChildItem -Path $Path -Filter "*.ps1" -Recurse

foreach ($script in $scripts) {
    $sig = Get-AuthenticodeSignature -FilePath $script.FullName

    if ($sig.Status -ne "Valid") {
        Write-Host "Signature: $($script.Name)" -ForegroundColor Yellow
        Set-AuthenticodeSignature -FilePath $script.FullName -Certificate $cert | Out-Null
    }
}

Write-Host "`nTerminé! $($scripts.Count) scripts traités." -ForegroundColor Green

GPO : Configurer l'Execution Policy

Via GPO (Domaine AD)

Computer Configuration
└── Policies
    └── Administrative Templates
        └── Windows Components
            └── Windows PowerShell
                └── Turn on Script Execution
                    → Enabled
                    → Execution Policy: Allow only signed scripts (AllSigned)
                                     ou Allow local scripts and remote signed scripts (RemoteSigned)

Via Registre (Local)

# Machine
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell" `
                 -Name "ExecutionPolicy" -Value "RemoteSigned"

# Utilisateur
Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell" `
                 -Name "ExecutionPolicy" -Value "RemoteSigned"

Troubleshooting

Script Bloqué (Zone Internet)

# Voir si un fichier est bloqué
Get-Item "script.ps1" -Stream Zone.Identifier -ErrorAction SilentlyContinue

# Débloquer un fichier
Unblock-File -Path "script.ps1"

# Débloquer tous les fichiers d'un répertoire
Get-ChildItem -Path "C:\Scripts" -Recurse | Unblock-File

Vérifier la Signature

# Vérifier la signature d'un script
Get-AuthenticodeSignature -FilePath "script.ps1"

# Status possibles :
# - Valid : Signature valide
# - NotSigned : Pas signé
# - HashMismatch : Script modifié après signature
# - UnknownError : Certificat non approuvé

Debugger les Problèmes de Policy

# Voir quelle policy bloque
$policy = Get-ExecutionPolicy -List | Where-Object { $_.ExecutionPolicy -ne 'Undefined' }
$policy | Format-Table -AutoSize

# Voir si une GPO force la policy
Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell" -ErrorAction SilentlyContinue

Voir Aussi