Servizio con UI in autostart su Domain Controller Windows Server 2025 (XCP‑ng): diagnosi e soluzione degli hang dei processi SYSTEM

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.

Indice

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:

  1. Promuovere un nuovo DC Server 2025 su XCP‑ng.
  2. Installare il servizio C# di tipo AutoStart che crea una form.
  3. 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 /f o riavvio.
  • Tracce ProcMon con attese su wait handle correlati a CSRSS/win32k e 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 verificaIndicazioni praticheRationale
Confronto ambienteEseguire 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 risorseMonitorare 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 servizioI 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 sessioneVerificare 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 miratoAvviare il servizio senza creare la form per confermare che la UI sia la causa dell’hang.Elimina rapidamente i falsi positivi.
Account di servizioEseguire il servizio con Network Service o account dedicato, non SYSTEM, quando possibile.Riduce privilegi e interferenze con processi critici.
Dipendenze/timeoutControllare 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”

  1. Modificare temporaneamente il servizio rimuovendo la creazione della form: sostituire Form1 con un no‑op o un semplice logger.
  2. 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/Pipe e 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

  1. Servizio (background, in sessione 0): nessuna referenza a WinForms/WPF. Espone un endpoint locale (Named Pipe / TCP loopback / gRPC) con autenticazione e autorizzazione.
  2. 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.
  • OnStart deve tornare rapidamente; spostare i lavori lunghi su thread/Task dedicati con cancellazione reattiva (CancellationToken in ExecuteAsync).
  • OnStop/StopAsync deve 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

  1. Clone di staging del servizio: build “no‑UI”.
  2. Distribuzione progressiva su un DC secondario/non critico.
  3. Monitoraggio dei tempi di arresto/avvio e degli eventi SCM.
  4. 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

  1. Conferma: rimuovere la creazione della form → il problema scompare? Allora la UI è la causa.
  2. Refactor: creare un servizio Worker senza riferimenti UI.
  3. IPC: predisporre Named Pipe/gRPC locale con ACL restrittive.
  4. UI: applicazione desktop avviata da Task Scheduler al login dell’amministratore.
  5. Account: ridurre privilegi del servizio (Network Service/gMSA).
  6. Test: con e senza ruolo DC; su host fisico; con/ senza enhancements XCP‑ng.
  7. 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

AreaEvento/StrumentoIndicazioni
Service Control Manager7031, 7034Crash o terminazioni inattese; cercare correlazioni temporali con il servizio “UI”.
System1074, 6006Tempi lunghi allo shutdown; valutare incrementi anomali dopo l’installazione.
Application1000, 1001Errori applicativi o report WER quando la UI tenta di interagire con il desktop inesistente.
ProcMonTraceRicerca di handle GDI/USER non rilasciati, ALPC e chiamate a win32k.
ResMon/Wait ChainAnalisi atteseIndividua 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/OnStop per 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

  1. Rimuovere ogni istanza di Form/UI dal servizio.
  2. Rilasciare build “headless” del servizio e installarla sul DC.
  3. Creare un’app UI separata e avviarla via Task Scheduler al logon degli amministratori.
  4. Stabilire IPC locale sicuro (Named Pipe con ACL).
  5. Ridurre l’account del servizio a Network Service o gMSA.
  6. Eseguire test su DC di laboratorio e su host fisico.
  7. Monitorare eventi SCM e tempi di arresto; raccogliere dump se necessario.

Indice