Su un Domain Controller Windows Server 2025 in esecuzione su XCP‑ng, un servizio C# impostato in avvio automatico che istanzia una form WinForms può causare l’hang dei processi in esecuzione come SYSTEM al momento della terminazione. Ecco cause, diagnosi e soluzioni pratiche.
Contesto e sintomo
Un amministratore ha installato su un DC Windows Server 2025 (VM su XCP‑ng) un servizio Windows scritto in C# che, all’avvio, crea un oggetto Form (ad esempio using (var f = new Form1()) { }) per scopi di inizializzazione “grafica”. Dopo il riavvio, qualsiasi altro processo eseguito come SYSTEM (inclusi servizi di sistema e tool di manutenzione) resta bloccato (stato di hang) in fase di terminazione. Il comportamento è riproducibile con i soli passaggi:
- Promuovere un nuovo DC Server 2025 su XCP‑ng.
- Installare il servizio C# di tipo AutoStart che crea una form.
- Riavviare il server.
Il fenomeno non si manifesta necessariamente su host fisico o su server membro non-DC, suggerendo che la combinazione “ruolo DC + VM + UI in sessione 0” amplifichi una condizione di deadlock o di attesa non soddisfatta durante le fasi di arresto.
Perché succede: la radice del problema
I servizi Windows girano in Sessione 0, isolata dalle sessioni utente. Dalla famiglia Windows Vista/Server 2008 in poi, la Session 0 Isolation impedisce ai servizi di interagire con il desktop (l’opzione “Allow service to interact with desktop” è deprecata). Quando un servizio prova a creare elementi UI (WinForms/WPF), vengono comunque prodotti handle di finestra e viene attivato un message loop che:
- Non è visibile né interattivo.
- Introduce attese su messaggi di sistema (es.
WMQUERYENDSESSION/WMENDSESSION) e su oggetti GDI/USER. - Può interferire con la sequenza di arresto dei servizi e dei processi che condividono il contesto SYSTEM, innescando deadlock o attese prolungate durante la terminazione.
Su un Domain Controller, la pipeline di arresto è particolarmente sensibile: servizi critici (es. LSASS, Netlogon, Active Directory Domain Services) coordinano la chiusura ordinata. Un servizio “non conforme” che mantiene attivi thread con una UI non interattiva in sessione 0 può inibire la chiusura regolare di altri servizi SYSTEM, propagando l’effetto “hang”.
In ambiente virtualizzato (XCP‑ng), eventuali componenti di integrazione o driver grafici/console possono accentuare la latenza di gestione degli oggetti grafici anche se non c’è un desktop reale: si sommano code di messaggi e handle non rilasciati, con maggiore probabilità di blocchi allo shutdown o nella terminazione dei processi figli.
Segnali diagnostici tipici
- Eventi di servizio che non riesce a fermarsi entro il timeout (Service Control Manager), con riavvio forzato o stato “Arresto in corso” persistente.
- Processi SYSTEM (o servizi dipendenti) che rimangono in stato Terminating o non escono mai finché non si interviene con
taskkill /fo riavvio. - Tracce ProcMon con attese su wait handle correlati a
CSRSS/win32ke call di GDI/USER, o catene di attesa (Wait Chain) che puntano a thread della vostra istanza di servizio. - Eventi di sistema allo shutdown (es. 1074, 6006) che mostrano tempi anomali, seguiti da errori 7031/7034 del Service Control Manager su altri servizi.
Checklist di verifica rapida
| Area di verifica | Indicazioni pratiche | Rationale |
|---|---|---|
| Confronto ambiente | Eseguire lo stesso servizio su Server 2025 fisico (non VM). Eseguire lo stesso servizio su un server membro (non DC). | Isola il ruolo dell’hypervisor, del ruolo DC o del sistema operativo. |
| Utilizzo risorse | Monitorare CPU, RAM, I/O all’avvio/stop del servizio; verificare ballooning/memoria dinamica in VM. | Picchi o carenze possono ritardare la terminazione di processi SYSTEM. |
| Progettazione del servizio | I servizi non devono presentare UI. Separare logica e interfaccia: UI come app utente che comunica via IPC. | Evita deadlock tra sessione 0 e sessioni interattive. |
| Gestione sessione | Verificare che il servizio non instanzi controlli/finestra in sessione 0. Avviare la UI con Task Scheduler all’accesso di un amministratore. | Windows Server 2025 rafforza le restrizioni su servizi interattivi. |
| Testing mirato | Avviare il servizio senza creare la form per confermare che la UI sia la causa dell’hang. | Elimina rapidamente i falsi positivi. |
| Account di servizio | Eseguire il servizio con Network Service o account dedicato, non SYSTEM, quando possibile. | Riduce privilegi e interferenze con processi critici. |
| Dipendenze/timeout | Controllare dipendenze, ordini di avvio/stop, e time‑out; evitare attese indefinite in OnStop. | Un blocco in uno stop si propaga a catena. |
Procedura di diagnosi passo‑passo
Disaccoppiare la variabile “UI”
- Modificare temporaneamente il servizio rimuovendo la creazione della form: sostituire
Form1con un no‑op o un semplice logger. - Riavviare il server e ripetere lo scenario: se l’hang scompare, la UI in sessione 0 è la concausa.
Verificare la sessione e l’account
whoami
query session
tasklist /v | findstr <NomeProcessoServizio>
Attenzione: un servizio correttamente progettato non deve mai creare UI quando Environment.UserInteractive è false o quando Process.GetCurrentProcess().SessionId == 0.
Osservare le catene di attesa
- Resource Monitor: tab “CPU” > Analyze Wait Chain… sul processo bloccato.
- Process Explorer: guardare i thread del servizio, in particolare quelli con stack Win32k/GDI.
- ProcMon: filtrare per il processo del servizio, cercando operazioni su
WindowMessage/ALPC/Pipee eventuali NAME NOT FOUND / ACCESS DENIED ricorrenti.
Dump al momento del blocco
procdump -ma -h -w <ProcessName.exe> C:\Dumps
Analizzare i dump per thread in attesa su messaggi di finestra, mutex, o handle di sistema che bloccano l’uscita.
Correzione architetturale consigliata
La soluzione robusta è separare il servizio (headless) dalla UI (app desktop) e farli comunicare tramite un canale locale sicuro.
Modello a due componenti
- Servizio (background, in sessione 0): nessuna referenza a WinForms/WPF. Espone un endpoint locale (Named Pipe / TCP loopback / gRPC) con autenticazione e autorizzazione.
- UI (app utente): avviata al logon dell’amministratore con Task Scheduler, si collega al servizio per mostrare stato e inviare comandi.
Esempio minimal di servizio .NET “headless”
// .NET 6/7/8 - Worker Service
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureLogging(b => b.AddEventLog())
.ConfigureServices((ctx, services) =>
{
services.AddHostedService();
// Registrare qui il server IPC (Named Pipe / gRPC) senza UI
})
.Build()
.Run();
Regole d’oro:
- Niente WinForms/WPF/STAThread nel processo di servizio.
OnStartdeve tornare rapidamente; spostare i lavori lunghi su thread/Task dedicati con cancellazione reattiva (CancellationTokeninExecuteAsync).OnStop/StopAsyncdeve chiudere l’endpoint IPC, rilasciare handle e terminare in tempi deterministici.
Esempio di UI che rifiuta l’avvio in Sessione 0
static class Program
{
[STAThread]
static void Main()
{
if (System.Diagnostics.Process.GetCurrentProcess().SessionId == 0)
{
// Loggare e uscire: nessuna UI in sessione 0
return;
} Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
Avvio della UI con Task Scheduler
Creare una attività pianificata che si avvia al logon degli amministratori e che esegue la UI con privilegi elevati:
schtasks /Create /TN "MyApp UI" ^
/TR "C:\Program Files\MyApp\MyApp.UI.exe" ^
/SC ONLOGON /RU "DOMAIN\AdminUser" /RL HIGHEST
In alternativa usare PowerShell Register-ScheduledTask con trigger AtLogOn e “Run only when user is logged on”.
Impostazioni di sistema da conoscere (ma non come “fix” primario)
Queste chiavi possono aiutare la diagnosi, ma non risolvono l’errore di design:
- ServicesPipeTimeout (
HKLM\SYSTEM\CurrentControlSet\Control): estende il tempo concesso ai servizi per avviarsi/fermarsi. Utile per evitare falsi positivi di timeout durante i test. - WaitToKillServiceTimeout (
HKLM\SYSTEM\CurrentControlSet\Control): tempo di attesa allo shutdown prima di forzare la terminazione. Aumentarlo può “nascondere” il problema ma non lo elimina.
New-ItemProperty `
-Path 'HKLM:\SYSTEM\CurrentControlSet\Control' `
-Name 'ServicesPipeTimeout' -PropertyType DWord -Value 60000
Applicare con cautela e solo durante le analisi.
Considerazioni specifiche per Domain Controller
- I DC eseguono servizi critici in SYSTEM con dipendenze complesse; un blocco in fase di arresto/riavvio può impattare repliche AD, DNS integrato e policy startup/shutdown.
- Evitare di installare software con UI o agent “potenzialmente interattivi” sui DC. Se indispensabile, usarne la versione “server/agent headless”.
- Non abilitare impostazioni obsolete come “interact with desktop” su servizi: oltre a non funzionare, peggiora la stabilità.
Note sull’ambiente XCP‑ng
L’ipotesi “effetto amplificato dall’hypervisor” va ponderata, soprattutto quando le stesse binarie non riproducono il problema su hardware fisico. Suggerimenti di verifica:
- Aggiornare le guest additions e i driver paravirtualizzati.
- Se presente, disattivare accelerazioni grafiche non necessarie per server headless.
- Testare con memoria statica (senza ballooning) durante le prove.
- Confrontare lo shutdown con e senza snapshot/backup in corso.
Anche se l’hypervisor può contribuire a ritardi di messaggistica e gestione handle grafici, la radice rimane l’uso improprio della UI nel servizio.
Hardening applicativo: comunicazione e sicurezza
- Preferire Named Pipes su loopback per comandi locali. Implementare controllo dell’identità del client (SID dell’utente) e autorizzazioni ACL.
- Se si usa TCP/HTTP locale (gRPC/REST), limitare a
127.0.0.1, abilitare TLS e autenticazione (es. Windows Integrated o token locali protetti). - Firmare digitalmente i binari e abilitare il Code Integrity nelle policy del DC, per ridurre superfici di attacco.
Strategia di migrazione senza downtime
- Clone di staging del servizio: build “no‑UI”.
- Distribuzione progressiva su un DC secondario/non critico.
- Monitoraggio dei tempi di arresto/avvio e degli eventi SCM.
- Rollback plan: in caso di regressione, ripristinare la versione precedente o disabilitare temporaneamente l’avvio automatico del servizio.
Domande frequenti
Posso inizializzare componenti WinForms “senza mostrare la finestra” nel servizio?
No. Anche solo creare una Form o controlli WinForms genera handle e un message pump che appartengono al modello UI, incompatibile con la sessione 0. È il comportamento che scatena l’hang in fase di terminazione.
Se separo la UI, posso lasciare il servizio in SYSTEM?
Meglio di no, se non indispensabile. Preferire Network Service o un account gestito (gMSA) con privilegi minimi necessari; riduce l’impatto su altri processi SYSTEM.
Basta aumentare i timeout di servizio per “risolvere”?
No. I timeout mitigano i sintomi, ma non rimuovono la causa. Il design corretto è servizio headless + UI separata.
È un bug di .NET o di Windows Server 2025?
È un anti‑pattern noto: UI in un servizio. Su versioni recenti e su DC l’effetto collaterale diventa più evidente per via delle restrizioni e della sensibilità dei processi SYSTEM.
Playbook operativo
- Conferma: rimuovere la creazione della form → il problema scompare? Allora la UI è la causa.
- Refactor: creare un servizio Worker senza riferimenti UI.
- IPC: predisporre Named Pipe/gRPC locale con ACL restrittive.
- UI: applicazione desktop avviata da Task Scheduler al login dell’amministratore.
- Account: ridurre privilegi del servizio (Network Service/gMSA).
- Test: con e senza ruolo DC; su host fisico; con/ senza enhancements XCP‑ng.
- Osservabilità: Event Log, dump, wait chain; documentare tempi di stop.
Esempi di configurazione
Configurare l’account del servizio
sc.exe config MyService obj= "NT AUTHORITY\NetworkService" password= ""
sc.exe failure MyService reset= 86400 actions= restart/60000
Protezione Named Pipe (ACL di esempio)
// All'avvio del servizio, creare la pipe con un DACL che consenta solo Admin locali:
var security = new PipeSecurity();
security.AddAccessRule(new PipeAccessRule("Administrators", PipeAccessRights.FullControl, System.Security.AccessControl.AccessControlType.Allow));
using var server = new NamedPipeServerStream("MyAppPipe", PipeDirection.InOut, 1,
PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 0, 0, security);
Tabella di controllo eventi e strumenti
| Area | Evento/Strumento | Indicazioni |
|---|---|---|
| Service Control Manager | 7031, 7034 | Crash o terminazioni inattese; cercare correlazioni temporali con il servizio “UI”. |
| System | 1074, 6006 | Tempi lunghi allo shutdown; valutare incrementi anomali dopo l’installazione. |
| Application | 1000, 1001 | Errori applicativi o report WER quando la UI tenta di interagire con il desktop inesistente. |
| ProcMon | Trace | Ricerca di handle GDI/USER non rilasciati, ALPC e chiamate a win32k. |
| ResMon/Wait Chain | Analisi attese | Individua thread del servizio come vertice della catena di blocco. |
Best practice di sviluppo per servizi Windows su server moderni
- Zero UI nel processo di servizio. Anche “solo” istanziare una form è vietato.
- Cancellation first: progettare
ExecuteAsync/OnStopper una chiusura in < 10 secondi. - Threading: evitare thread STA e componenti COM in sessione 0 se non strettamente necessario.
- Logging: Event Log nativo + file rolling; niente finestra di log/console.
- Test su DC: testare sempre su un DC di laboratorio, non solo su server membri.
- Virtualization‑aware: valutare l’impatto di driver PV, snapshot, backup quiescing.
Conclusioni
Il blocco dei processi SYSTEM su un DC Windows Server 2025 ospitato su XCP‑ng è coerente con un errore di progettazione: un servizio che, in sessione 0, istanzia componenti UI (WinForms/WPF). La soluzione non è aumentare i timeout né forzare l’uscita dei processi, ma disaccoppiare la UI dalla logica di servizio, eseguire l’interfaccia come applicazione utente e mantenere il servizio completamente headless. Completano la mitigazione i test incrociati (DC vs membro, VM vs fisico), la riduzione dei privilegi dell’account di servizio e la revisione delle dipendenze. Seguendo il playbook riportato, l’uscita dei processi SYSTEM torna deterministica, la pipeline di shutdown del DC rimane stabile e l’operatività complessiva del dominio è preservata.
Appendice: piano di remediation sintetico
- Rimuovere ogni istanza di
Form/UI dal servizio. - Rilasciare build “headless” del servizio e installarla sul DC.
- Creare un’app UI separata e avviarla via Task Scheduler al logon degli amministratori.
- Stabilire IPC locale sicuro (Named Pipe con ACL).
- Ridurre l’account del servizio a Network Service o gMSA.
- Eseguire test su DC di laboratorio e su host fisico.
- Monitorare eventi SCM e tempi di arresto; raccogliere dump se necessario.
