Besoin de garantir qu’un service Windows démarre toujours avec une priorité processeur élevée ? Voici des méthodes fiables (scriptables et réversibles) pour forcer et vérifier la priorité « High », y compris un script PowerShell prêt à l’emploi et son déploiement via le Planificateur de tâches.
Contexte et objectif
Lorsqu’on prépare une image Windows (poste ou serveur), on veut généralement deux choses : définir le StartupType du service MyService
sur Automatic et s’assurer qu’à chaque redémarrage, la priorité de son processus reste « High ». Or, par conception, Windows relance un service avec la priorité Normale : toute modification manuelle faite après l’installation est perdue au reboot. Il faut donc une mécanisation durable.
Ce qu’il faut savoir sur les priorités Windows
- La priorité s’applique au processus (pas au service en tant qu’objet SCM).
- Elle n’est pas persistée : à chaque démarrage ou relance du service, la classe de priorité revient à sa valeur par défaut, sauf si vous intervenez (code, script, outil).
Classes de priorité (Win32) : Idle = 64, Normal = 32, High = 128, Real‑time = 256, BelowNormal = 16 384, AboveNormal = 32 768.
Recommandation : évitez Real‑time (risque de blocage du système). « High » est généralement adapté aux services critiques non temps‑réel ; si vous observez une contention CPU, essayez « AboveNormal ».
Approches possibles (résumé comparatif)
Approche | Étapes clés | Avantages | Points d’attention |
---|---|---|---|
Tâche planifiée « au démarrage » (la plus simple) | 1) Script PowerShell (SetServicePriority.ps1 ) qui repère le PID du service et fixe la priorité.2) Tâche planifiée : déclencheur At startup, action PowerShell, Run with highest privileges, compte SYSTEM. 3) Délai de 10–30 s selon le temps d’apparition du processus. 4) Option : second déclencheur On an event (Event ID 7036) pour traiter les redémarrages à chaud. | Pas de changement du binaire ; adaptable à tout service ; pilotable par GPO. | Le script doit être présent localement ; prévoir une attente si le processus n’est pas encore lancé. |
Priorité fixée dans le code du service | Appeler SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS) au démarrage du service. | Solution définitive, zéro dépendance externe. | Nécessite accès au code et recompilation. |
Outils tiers / GPO de démarrage | Utiliser Process Lasso / Prio / Process Hacker ou un script Startup via GPO pour fixer la priorité. | Gestion centralisée, options avancées. | Dépendances supplémentaires, gouvernance logicielle à prévoir. |
Mise en œuvre détaillée avec le Planificateur de tâches
1) Créer un script PowerShell robuste
Placez le script dans un répertoire de service (C:\ProgramData\ServiceTuning\
) avec des ACL restrictives (Administrators, SYSTEM).
# C:\ProgramData\ServiceTuning\SetServicePriority.ps1
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[Parameter(Mandatory=$false)][string]$ServiceName = 'MyService',
[ValidateSet('Idle','BelowNormal','Normal','AboveNormal','High','RealTime')]
[string]$Priority = 'High',
[int]$TimeoutSeconds = 90,
[switch]$Log
)
function Write-Log {
param([string]$Message, [string]$Type = 'Information', [int]$Id = 1000)
if ($Log) {
$src = 'ServicePriorityTuner'
try {
if (-not [System.Diagnostics.EventLog]::SourceExists($src)) {
New-EventLog -LogName Application -Source $src | Out-Null
}
Write-EventLog -LogName Application -Source $src -EntryType $Type -EventId $Id -Message $Message
} catch { }
}
Write-Verbose $Message
}
function Get-PriorityValue([string]$name) {
switch ($name.ToLower()) {
'idle' { 64 }
'belownormal' { 16384 }
'normal' { 32 }
'abovenormal' { 32768 }
'high' { 128 }
'realtime' { 256 }
default { 32 }
}
}
try {
# Assurer le StartupType=Automatic
try {
$svc = Get-Service -Name $ServiceName -ErrorAction Stop
if ($svc.StartType -ne 'Automatic') {
Set-Service -Name $ServiceName -StartupType Automatic
Write-Log "StartupType du service '$ServiceName' forcé à Automatic."
}
} catch {
throw "Service '$ServiceName' introuvable."
}
```
# Attendre que le service soit Running et qu'un PID soit associé
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$pid = $null
do {
$cimSvc = Get-CimInstance Win32_Service -Filter "Name='$ServiceName'" -ErrorAction Stop
if ($cimSvc.State -eq 'Running' -and $cimSvc.ProcessId -gt 0) {
$pid = $cimSvc.ProcessId
break
}
Start-Sleep -Seconds 1
} while ($sw.Elapsed.TotalSeconds -lt $TimeoutSeconds)
if (-not $pid) {
throw "Le processus du service '$ServiceName' n'a pas été détecté (timeout ${TimeoutSeconds}s)."
}
# Tenter d'abord l'API .NET (PriorityClass)
$setOk = $false
try {
$p = Get-Process -Id $pid -ErrorAction Stop
$target = [System.Diagnostics.ProcessPriorityClass]::$Priority
if ($PSCmdlet.ShouldProcess("$($p.ProcessName) (PID $pid)", "Set PriorityClass=$Priority")) {
$p.PriorityClass = $target
}
$setOk = $true
Write-Log "Priorité du PID $pid (proc '$($p.ProcessName)') réglée sur $Priority via .NET."
} catch {
Write-Log "Échec .NET, tentative WMI/CIM... Détails: $($_.Exception.Message)" 'Warning' 1001
}
# Repli via WMI (SetPriority avec valeur numérique Win32)
if (-not $setOk) {
$prioValue = Get-PriorityValue $Priority
$procCim = Get-CimInstance Win32_Process -Filter "ProcessId=$pid" -ErrorAction Stop
$res = Invoke-CimMethod -InputObject $procCim -MethodName SetPriority -Arguments @{ Priority = $prioValue }
if ($res.ReturnValue -eq 0) {
$setOk = $true
Write-Log "Priorité du PID $pid réglée sur $Priority (valeur $prioValue) via WMI."
} else {
throw "Invoke-CimMethod SetPriority a renvoyé $($res.ReturnValue)."
}
}
# Vérification
$check = Get-Process -Id $pid -ErrorAction Stop | Select-Object -ExpandProperty PriorityClass
Write-Log "Vérification: PriorityClass actuelle du PID $pid = $check."
if ($check.ToString() -ne $Priority) {
throw "Vérification échouée: PriorityClass='$check' ≠ '$Priority'."
}
exit 0
```
}
catch {
Write-Log "Erreur: $($_.Exception.Message)" 'Error' 1999
exit 1
}
Bonnes pratiques :
- Exécuter le script avec Run with highest privileges sous le compte
SYSTEM
(ou un compte administrateur local dédié). - Signer le script si une stratégie d’exécution restrictive est en place ; sinon, utiliser
-ExecutionPolicy Bypass
uniquement pour la tâche planifiée. - Journaliser dans l’Event Log (
-Log
) pour faciliter le diagnostic sur le terrain.
2) Créer la tâche planifiée en ligne de commande
Deux déclencheurs conseillés : At startup (avec délai) et On an event (ID 7036), pour couvrir les relances à chaud du service.
:: Création du répertoire script
mkdir "C:\ProgramData\ServiceTuning" 2>nul
:: Tâche 1 - Au démarrage, avec délai de 20s
schtasks /create /tn "\Ops\Set-MyService-High (Startup)" ^
/ru "SYSTEM" /sc onstart /delay 0000:20 /rl HIGHEST ^
/tr "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File C:\ProgramData\ServiceTuning\SetServicePriority.ps1 -ServiceName 'MyService' -Priority High -TimeoutSeconds 120 -Log"
:: Tâche 2 - Sur événement 7036 (Service Control Manager)
schtasks /create /tn "\Ops\Set-MyService-High (7036)" ^
/ru "SYSTEM" /sc ONEVENT /ec System ^
/mo "*[System[Provider[@Name='Service Control Manager'] and (EventID=7036)]] and *[EventData[Data='MyService']]" ^
/rl HIGHEST ^
/tr "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File C:\ProgramData\ServiceTuning\SetServicePriority.ps1 -ServiceName 'MyService' -Priority High -TimeoutSeconds 120 -Log"
Remarque : le filtre ONEVENT ci‑dessus déclenche sur 7036
quand le service ciblé change d’état (peu importe l’état). Le script, lui, n’applique la priorité que si l’état actuel est Running, ce qui évite les soucis de localisation du texte « running » dans les journaux selon la langue du système.
3) Création de la tâche via PowerShell (Planificateur de tâches)
Les cmdlets gèrent parfaitement le déclencheur At startup. Pour un déclencheur événementiel On an event, privilégiez schtasks
(ou l’API COM).
$service = 'MyService'
$script = 'C:\ProgramData\ServiceTuning\SetServicePriority.ps1'
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$script`" -ServiceName `"$service`" -Priority High -TimeoutSeconds 120 -Log"
$trigger = New-ScheduledTaskTrigger -AtStartup -RandomDelay (New-TimeSpan -Seconds 15)
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest
Register-ScheduledTask -TaskName "Set-$service-High (Startup)" -Action $action -Trigger $trigger -Principal $principal -Description "Force la priorité High du service $service à chaque démarrage."
Validation et diagnostic
- Vérifiez le StartupType :
Get-Service MyService | Select Name,StartType
. - Vérifiez la priorité du processus :
(Get-Process -Id (Get-CimInstance Win32_Service -Filter "Name='MyService'").ProcessId).PriorityClass
. - Consultez le journal Application pour les entrées de
ServicePriorityTuner
si l’option-Log
a été activée. - Exécutez un redémarrage test et mesurez le temps d’apparition du PID (ajustez delay et
-TimeoutSeconds
en conséquence).
Cas particuliers et pièges fréquents
- Nom du service vs nom de l’exécutable : un service peut s’appeler
MyService
mais tourner sousMyServiceHost.exe
(ou être hébergé parsvchost.exe
). La méthode par PID viaWin32_Service
évite l’ambiguïté du process name. - Relances à chaud : si le service se recycle sans reboot (crash, Recover, Update), seul le déclencheur On an event assurera la remise à niveau immédiate.
- Droits : la modification de la priorité requiert PROCESS_SET_INFORMATION. Lancer la tâche en
SYSTEM
(ou administrateur local) règle ce point. - Real‑time : réservé aux cas ultra‑spécifiques. Un processus en « Realtime » peut monopoliser le CPU et bloquer I/O, rendant la machine quasi‑inutilisable.
- Changements d’OS / mise à jour : les tâches planifiées et scripts sous
C:\ProgramData\
survivent généralement aux mises à jour ; documentez et sauvegardez le dossier\Ops\
dans vos procédures d’image. - Plusieurs instances : si un service peut lancer plusieurs processus « worker », adaptez le script pour parcourir tous les PIDs associés (normalement, un service Win32_Service expose un seul PID, mais des enfants peuvent exister).
Automatisation dans l’image Windows
Pour intégrer la solution dans une image :
- Déposez le script dans
C:\ProgramData\ServiceTuning\
durant la phase specialize ou viaSetupComplete.cmd
. - Créez les tâches planifiées avec
schtasks
en fin d’installation.
:: C:\Windows\Setup\Scripts\SetupComplete.cmd
@echo off
set SERVICENAME=MyService
set SCRIPT=C:\ProgramData\ServiceTuning\SetServicePriority.ps1
mkdir "C:\ProgramData\ServiceTuning" 2>nul
copy /y "%~dp0SetServicePriority.ps1" "%SCRIPT%"
schtasks /create /tn "\Ops\Set-%SERVICENAME%-High (Startup)" ^
/ru "SYSTEM" /sc onstart /delay 0000:20 /rl HIGHEST ^
/tr "powershell.exe -NoProfile -ExecutionPolicy Bypass -File %SCRIPT% -ServiceName '%SERVICENAME%' -Priority High -TimeoutSeconds 120 -Log"
schtasks /create /tn "\Ops\Set-%SERVICENAME%-High (7036)" ^
/ru "SYSTEM" /sc ONEVENT /ec System ^
/mo "*[System[Provider[@Name='Service Control Manager'] and (EventID=7036)]] and *[EventData[Data='%SERVICENAME%']]" ^
/rl HIGHEST ^
/tr "powershell.exe -NoProfile -ExecutionPolicy Bypass -File %SCRIPT% -ServiceName '%SERVICENAME%' -Priority High -TimeoutSeconds 120 -Log"
exit /b 0
Alternative : le faire dans le code du service
Si vous avez la main sur le binaire, appliquer la classe de priorité dans le code est l’option la plus fiable.
Exemple C#
using System.Diagnostics;
public class Program
{
public static void Main()
{
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
// Continuer l'initialisation du service...
}
}
Exemple C/C++
#include <windows.h>
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) {
// TODO: journaliser GetLastError()
}
// Suite du démarrage du service...
return 0;
}
Placez ces appels le plus tôt possible dans la séquence de démarrage du service (par ex. dans OnStart
), avant la création de threads intensifs.
Déploiement à l’échelle (AD/GPO)
Pour un parc Active Directory, vous pouvez centraliser la solution :
- GPO > Preferences > Scheduled Tasks : poussez la tâche Startup et, si nécessaire, la tâche ONEVENT (via XML ou
schtasks
). - GPO > Windows Settings > Scripts (Startup) : copiez le script puis exécutez un .cmd qui crée les tâches.
- Contrôle : utilisez un WMI Filter ou un Item‑level targeting pour n’appliquer qu’aux machines concernées.
Vérifications de performance et sécurité
- Surveillance : collectez le compteur Processor > % Processor Time du processus pour vérifier l’effet de la nouvelle priorité.
- Équité : une priorité « High » peut défavoriser d’autres processus en cas de CPU saturé. Mesurez en condition réelle.
- Durcissement : stockez le script dans
C:\ProgramData
avec ACL strictes ; évitezC:\Temp
et désactivez l’héritage si nécessaire.
FAQ
Pourquoi ma modification de priorité via le Gestionnaire des tâches ne survit‑elle pas au redémarrage ?
Parce que la classe de priorité est une propriété volatile du processus. Le service recrée un nouveau processus à chaque démarrage, qui repart avec la valeur par défaut.
Puis‑je le faire via le Registre ?
Non : il n’existe pas de clé native pour persister la classe de priorité d’un exécutable particulier (hors outils tiers qui ajoutent leur propre couche).
Et si le service tourne dans svchost.exe
?
Le script se base sur le PID exposé par Win32_Service
et modifiera la priorité du svchost.exe
hébergeant MyService
. Attention : cela peut impacter d’autres services hébergés dans la même instance.
Dois‑je aussi ajuster la priorité des threads ?
La plupart du temps, non. La classe de priorité du processus suffit. Les réglages de priorité de thread sont plus fins, mais rarement nécessaires pour un service classique.
Checklist de mise en production
- ☑
Set-Service -Name MyService -StartupType Automatic
- ☑ Script
SetServicePriority.ps1
copié enC:\ProgramData\ServiceTuning\
(ACL durcies) - ☑ Tâche Startup (
SYSTEM
, Highest, délai 10–30 s) - ☑ Tâche ONEVENT 7036 (optionnelle, pour relances à chaud)
- ☑ Journalisation activée (
-Log
) et supervisée - ☑ Tests : redémarrage, vérification de
PriorityClass
, mesures de charge - ☑ Documentation / sauvegarde de la config
Conclusion
Il n’existe pas de « switch » Windows qui mémorise la priorité d’un service entre les redémarrages. La combinaison StartupType = Automatic + tâche planifiée qui fixe la priorité au bon moment (au démarrage et/ou sur l’événement 7036) fournit une solution fiable, scriptable et réversible. Si vous contrôlez le code, intégrer l’appel SetPriorityClass
directement dans le service est encore plus radical. Dans tous les cas, surveillez l’impact et évitez la classe Realtime hors cas exceptionnels.
Annexe : modèle XML de tâche planifiée (ONEVENT)
À importer dans le Planificateur de tâches (remplacez MyService
et le chemin du script) :
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>Fixe la priorité High du service MyService sur 7036</Description>
</RegistrationInfo>
<Triggers>
<EventTrigger>
<Enabled>true</Enabled>
<Subscription>
<![CDATA[
<QueryList>
<Query Id="0" Path="System">
<Select Path="System">*[System[Provider[@Name='Service Control Manager'] and (EventID=7036)]] and *[EventData[Data='MyService']]</Select>
</Query>
</QueryList>
]]>
</Subscription>
</EventTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId> <!-- SYSTEM -->
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>true</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
</Settings>
<Actions Context="Author">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-NoProfile -ExecutionPolicy Bypass -File C:\ProgramData\ServiceTuning\SetServicePriority.ps1 -ServiceName "MyService" -Priority High -TimeoutSeconds 120 -Log</Arguments>
</Exec>
</Actions>
</Task>
Annexe : variante du script — boucle d’attente sur apparition du binaire
Si le service met du temps à créer son exécutable (ou s’il spawne un worker secondaire), préférez une détection par nom d’exe après lecture du PID initial :
$svc = Get-CimInstance Win32_Service -Filter "Name='MyService'"
$exe = Split-Path -Leaf $svc.PathName.Trim('"')
$deadline = (Get-Date).AddSeconds(90)
do {
$p = Get-Process -Name ([IO.Path]::GetFileNameWithoutExtension($exe)) -ErrorAction SilentlyContinue
if ($p) { $p.PriorityClass = 'High'; break }
Start-Sleep 1
} while (Get-Date) -lt $deadline
Annexe : mapping complet des valeurs
Classe (.NET / Win32) | Constante Win32 | Valeur entière | Quand l’utiliser |
---|---|---|---|
Idle | IDLE_PRIORITY_CLASS | 64 | Tâche d’arrière‑plan sans contrainte. |
BelowNormal | BELOW_NORMAL_PRIORITY_CLASS | 16 384 | Processus peu prioritaire mais interactif. |
Normal | NORMAL_PRIORITY_CLASS | 32 | Valeur par défaut de Windows. |
AboveNormal | ABOVE_NORMAL_PRIORITY_CLASS | 32 768 | Petit boost sans impacts majeurs. |
High | HIGH_PRIORITY_CLASS | 128 | Services critiques non temps‑réel. |
RealTime | REALTIME_PRIORITY_CLASS | 256 | À proscrire sauf cas extrême et maîtrisé. |
En bref
Pour qu’un service Windows démarre systématiquement avec une priorité haute, le plus efficace est d’automatiser la remise à niveau de la priorité à chaque apparition du processus : au démarrage (tâche At startup) et lors des relances (tâche ONEVENT 7036). Le script ci‑dessus, sécurisé et journalisé, couvre la majorité des cas — sans toucher au binaire. Si vous possédez le code, intégrez directement l’appel SetPriorityClass
pour un résultat définitif.