Bentornati!

Questo è un problema vecchio, risalente all’epoca di Windows XP, ma ancora attuale presso chi sta tuttora usando XP e non si è ancora deciso a migrare a Windows Vista o Windows 7.

Per motivi legati al risparmio energetico e alle specifiche del Green PC, il Power Manager di XP, spegne la macchina dopo due minuti dall’accensione, se non c’è nessun “contatto” di un utente alla tastiera/mouse o se nessun servizio provvede ad effettuare operazioni che contemplino anche la chiamata SetThreadExecutionState. Qesta è l’API prevista specificatamente per questo compito: mantenere sveglio XP..

Ad esempio, una sessione remota non genera nessuna chiamata alla SetThreadExecutionState, per cui anche nel caso in cui ci si colleghi da remoto con una sessione RDP, la macchina trascorsi i due minuti tornerà nella condizione precedente: se era spenta si spegnerà, se era in hybernation si reibernerà, se era in stand-by ci ritornerà.

Questo problema affligge soprattutto le grandi aziende. Un utente normale, probabilmente non si è mai accorto di questa “funzionalità”.

Per le grandi aziende però, dove ci sono centinaia di PC da amministrare e tenere aggiornati, è normale, pianificare operazioni notturne. Quindi, risvegliare i PC tramite il Wake On Lan, ed eseguire operazioni schedulate, tipo l’installazione di patch o backup, mentre gli utenti non sono presenti.

La soluzione è creare un servizio, che implementi la SetThreadExecutionState, e che parta in automatico. Questo farà in modo che la macchina rimanga sveglia per il tempo necessario ad eseguire l’opearazione pianificata. Al termine, basterà semplicemente interrompere il servizio e il power manager riporterà la macchina al suo stato precedente come se nulla fosse accaduto.

Nell’esempio, avevo anche aggiunto la gestione di una chiave di registry, che se presente impostava un timeout dopo il quale il servizio si terminava in automatico.

Per fare ciò, sono partito dall’esempio Simple Service dell’SDK, disponibile nel folder “C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\WinBase\Service” dopo l’installazione del Platform SDK, disponibile qui: http://www.microsoft.com/downloads/details.aspx?familyid=C17BA869-9671-4330-A63E-1FD44E0E2505&displaylang=en

Se ricompilate l’esempio allegato a questo post, potete modificare la linea di codice dove è definito il percorso della chiave di registry che conterrà il valore che si dovrà chiamare “Timeout”, che porterà al termine del servizio dopo quel tempo, espresso in secondi.

static LPTSTR gsSubKey = _T("Software\\YourCompany\\NoSleep");

Inserite il nome della vostra società al posto di “YourCompany”, e ricordatevi poi di creare la chiave sotto “HKLM\Software\nome\NoSleep”. E’ atteso che il valore sia di tipo REG_DWORD. Se li valore non viene trovato, il default è 3600 secondi, ossia un ora.
 
Il core del servizio, una volta eseguita l’inizializzazione e letto il timeout da rispettare, è questo:
while(TRUE)
{
hr = SetThreadExecutionState(ES_SYSTEM_REQUIRED|ES_CONTINUOUS);

dwWait = WaitForMultipleObjects( 1, hEvents, FALSE, 1000 );

if ( dwWait != WAIT_TIMEOUT ) // not overlapped i/o event - error occurred,
break; // or server stop signaled

if (dwCount==dwTimeout)
break;
else
++dwCount;
}

Facciamo la chiamata alla SetThreadExecutionState, e poi attendiamo che scatti un evento o che trascorrano 1000 millisecondi.
Si può essere risvegliati dalla WaitForMultipleObjects, per colpa del timeout raggiunto, 1 secondo, o dall’evento generato dal comando di Stop sicevuto dallo SCM (Service Control Manager). Nel caso non sia il Timeout di un secondo quindi, usciamo e terminiamo l’esecuzione.
Se invece è il timeout della funzione, andiamo ad aggiungere 1 ad un contatore che confronteremo col nostro Timeout applicativo letto nel registry.
Scaduto anche quello (il default è impostato ad un ora), termineremo in ogni caso, e da lì il Power Manager dovrebbe darci ancora due minuti di tempo e poi riportare il sistema allo stato precedente oppure possiamo fare direttamente shutdown (con la ExitWindowsEx o eseguendo Shutdown.exe).
 
Nell’esempio sono definite anche queste stringhe:
// name of the executable
#define SZAPPNAME "NoSleep"
// internal name of the service
#define SZSERVICENAME "No_Sleep"
// displayed name of the service
#define SZSERVICEDISPLAYNAME "NoSleep Service"
Per cui per avviare da linea di comando il servizio, dovrete inviare il comando “Net Start No_Sleep” e per interromperlo “Net Stop No_Sleep”, mentre a livello di snap-in dei servizi, il servizio si chiamerà “NoSleep Service”, e l’eseguibile “Nosleep.exe”.
 
Un altra sezione di codice fondamentale è la seguente in cui installiamo/creiamo il servizio:
schService = CreateService(
schSCManager, // SCManager database
TEXT(SZSERVICENAME), // name of service
TEXT(SZSERVICEDISPLAYNAME), // name to display
SERVICE_QUERY_STATUS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_AUTO_START, // start type
SERVICE_ERROR_NORMAL, // error control type
szPath, // service's binary
NULL, // no load ordering group
NULL, // no tag identifier
TEXT(SZDEPENDENCIES), // dependencies
NULL, // LocalSystem account
NULL); // no password
Qui possiamo impostare lo startup automatico del servizio, SERVICE_AUTO_START, e l’account da usare per l’esecuzione, che in questo caso abbiamo lasciato a NULL per indicare Local System. Se volete usare un altro account questo è il posto dove specificarlo.

L’installazione/rimozione del servizio è manuale da linea di comando. Queste le opzioni:

NoSleep –install                   to install the service
NoSleep –remove                 to remove the service
NoSleep -debug <params>   to run as a console app for debugging

Il sorgente è allegato al post, ma non è compilato.

Per la compilazione, dovete aprire la “Cmd Shell” dell’SDK e usare Setenv.bat per compilare per la piattaforma (x86/x64), la versione debug/release  e il sistema operativo target che volete.

setenvoptions

Ricapitolando..

Windows XP quando viene risvegliato con il “Wake On Lan” si riporta nello stato precedente dopo pochi minuti se il Power Manager non detecta la presenza di un utente. Per evitare questo e poter effettuare operazioni pianificate come l’installazione di patch o backup, è necessario mantenere la macchina sveglia attraverso la chiamata all’API SetThreadExecutionState.

Questo Servizio di esempio permette esattamente questo.

Alla prossima!

Mario Raccagni
Senior Support Engineer
Platform Development Support Team