Guida per Autostoppisti alla Programmazione di un Servizio Windows in Visual Basic.NET

Introduzione

Recentemente mi è stato richiesto di sviluppare un programma che ogni quarto d’ora interrogasse dei database periferici per importare nel database centrale alcuni dati; esso doveva essere operativo 24 ore su 24, indipendente dalla presenza di un operatore e sviluppato in .NET. La mia esperienza, unita a qualche mezza frase raccolta alla macchinetta del caffè, mi hanno suggerito che ben presto a questo semplice servizio verrà chiesto di eseguire anche altre operazioni, quali esportazioni quotidiane verso la contabilità ad un certa ora, importazioni quotidiane da file in altro orario rispetto al precedente… e quanto altro la mente perversa di un buon capo-progetto riuscirà ad immaginare.

Raccontato così, sembrava proprio un lavoro per un Servizio di Windows… solo che non avevo mai sviluppato un Servizio di Windows, prima d’ora!

Armato del mio browser preferito, ho solcato le onde di Internet alla ricerca di maggiori informazioni; come mi aspettavo non è stato difficile trovarne: curiosamente MSDN, Code Project, Developer Fusion (solo per citarne alcuni) mi hanno insegnato come sviluppare un servizio che tenga d’occhio una directory e segnali nel Registro Eventi di Windows eventuali variazioni.

Eppure, nonostante la cura e la precisione che gli autori ci hanno messo, quegli ottimi tutorial non hanno risolto tutti i problemi a cui sono andato incontro. Questa guida si rivolge perciò a chi ha la necessità di sviluppare qualcosa di un po’ più complesso.

Un’ultima cosa: in questo tutorial userò VB.NET della versione inglese del Visual Studio 2003, perché questo uso nella mia attività quotidiana, ma ho valide ragioni per pensare che le differenze rispetto ad altri linguaggi .NET-compliant (penso soprattutto a C# e Delphi.NET) o alle versioni successive dello stesso VB.NET non dovrebbero essere troppo marcate.

Basi

Cominciamo con la buona notizia: se sapete sviluppare in VB.NET, potete davvero sviluppare un Servizio di Windows.

E adesso, dopo esserci rinfrancati, un po’ di terrore ed esoterismo…

Caratteristiche di un Servizio di Windows

  • E’ un’applicazione in esecuzione in background
  • Può essere mandata in esecuzione all’avvio del sistema operativo (ma potrebbe anche essere avviato a richiesta di un altro servizio o applicazione)
  • E’ in esecuzione in una sessione separata da quella di un eventuale utente collegato (anzi, in effetti, non è affatto necessario che un utente sia loggato sul computer)

Queste caratteristiche fanno sì che un Servizio non abbia un interfaccia utente, né – di per sé -la potrebbe avere.

Le componenti di un servizio

  1. Service Control Manager (SCM): è esso stesso un servizio, in esecuzione con privilegi di sistema; si occupa di avviare, fermare, mettere in pausa e riavviare gli altri servizi.
  2. Service Control Program (SCP): applicazione del Pannello di Controllo di Windows, che fornisce all’utente del pc un interfaccia per avviare, fermare, mettere in pausa e riavviare i servizi installati.
  3. Service Application (SA): è il servizio vero e proprio, racchiuso in un eseguibile

Creazione di un servizio

Tra i vari template che Visual Studio mette a disposizione, v’è anche quello per la creazione dei Servizi: creando un nuovo progetto (menu File | New | Project), dalla lista dei template disponibili, scegliere quello relativo Windows Service.

Diamogli sin d’ora un nome significativo: nel nostro caso tmTestService andrà benissimo (magari in un altro tutorial spiegherò perché odio i sorgenti con nomi così poco significativi, e proporrò terrificanti pene per i responsabili).

Apparentemente VS ha creato un progetto simile ad una Windows Application: in realtà, analizzando le References del progetto potremmo notare che è stato aggiunto il riferimento alla namespace System.ServiceProcess, mentre è assente la namespace System.Windows.

Nel Solution Explorer notiamo che il progetto include già il nostro primo servizio, chiamato semplicemente Service1; visto che il nome non sembra molto significativo facciamo doppio click su quel Service1, aprendolo in modalità Design; ispezionandone le proprietà modifichiamo sia il Name, sia il ServiceName in tmFirst.

Il nome di default non è molto significativo
Impostazione di default del nuovo servizio
Un nome più significativo (tmFirst, ad esempio) fa sempre bene
Attribuiamo valori significativi alle proprietà

Giusto per curiosità, ricompiliamo il progetto e… sorpresa: il primo di una lunga sequenza di passi falsi!

Appena modifichiamo le proprietà viene generato un errore: niente di preoccupante, però
L’errore generato alla modifica delle proprietà

Il messaggio dice che c’è un errore alla riga 39 del nostro nuovo servizio: ma se visualizziamo il codice, notiamo che l’errore è dovuto proprio al fatto che abbiamo cambiato il nome del nostro servizio e la riga si trova nella regione generata automaticamente dal Component Designer.

È abbastanza semplice correggere l'errore
Correzione dell’errore

Una leggenda metropolitana vuole tutta questa regione come interdetta all’intervento umano: in realtà, come si può intuire dai commenti lasciati dal Designer, solo la Sub InitializeComponent è realmente proibita, mentre altrove è stata prevista la possibilità di intervento manuale.

Nel nostro caso particolare, modificando quel Service1 in tmFirst renderemo il nostro servizio nuovamente compilabile (anche se non strettamente necessario, suggerisco caldamente di modificare anche il nome del file in tmFirst.vb).

A questo punto possiamo notare alcune cose:

  • i servizi vengono generati derivandoli dalla classe System.ServiceProcess.ServiceBase
  • nel codice generato sono stati inseriti due metodi, in override dei metodi della classe padre: OnStart ed OnStop. Si tratta dei due metodi che come minimo devono essere implementati.

La classe ServiceBase

OnStart

Viene eseguito quando il servizio viene avviato; solitamente qui viene messo il codice di inizializzazione, come – tanto per fare un esempio – impostazione del timer o l’impostazione del log…

OnStop

Viene eseguito quando il servizio viene stoppato; solitamente viene scritto il codice di cleanup dell’applicazione, rilascio di risorse, stop del timer…

OnContinue

Viene eseguito quando il servizio viene fatto ripartire dopo una pausa

OnPause

Viene eseguito quando il servizio viene messo in pausa; quando verrà riavviato, un servizio messo in pausa riprenderà l’esecuzione dal punto in cui era arrivato al momento della messa in pausa; un servizio fermato, invece, quando verrà riavviato riprenderà l’esecuzione dal principio.

OnPowerEvent

Viene eseguito quando cambia lo stato delle batterie (tipicamente ciò avviene quando un portatile entra in Suspended Mode).

OnShutdown

Viene eseguito immediatamente prima dello shutdown del sistema.

La classe Installer

Aver creato il nostro servizio non è sufficiente: per poterlo usare occorre creare il suo installatore: mentre siamo in Design Mode del servizio, clickiamo sulla piccola linklabel Add Installer a destra verso il basso.

Il pulsante per la creazione dell'installer

Verrà creato l’Installer del servizio, e da qui in poi le cose cominciamo a complicarsi…

Il designer per la creazione del servizio Windows con VB.NET
Il designer per la creazione del servizio Windows con VB.NET

Notiamo come siano stati automaticamente aggiunti due componenti:

  • ServiceInstaller1: si occupa di installare un servizio (già che ci siamo rinominiamolo tmFirstServiceInstaller).
  • ServiceProcessInstaller1: si occupa di installare un processo di servizio(già che ci siamo rinominiamolo tmFirstProcessInstaller).

Sembra tutto chiaro? Lasciate fare a me… Ricapitoliamo un attimo: abbiamo

  • Un assembly (tmTestService.exe)
  • Un servizio (tmFirst)
  • Un installer (tmFirstInstaller) dentro al quale abbiamo:
    • Un process installer (tmFirstProcessInstaller)
    • Un service installer (tmFirstServiceInstaller)

Attenzione: nel service installer è meglio specificare la proprietà Account=LocalService.

Impostando l’account a User ci si espone ad un doppio rischio: innanzitutto che qualcuno sufficientemente esperto possa recuperare la password (rischio particolarmente elevato se l’utente scelto è Administrator); inoltre, alla prima modifica della password dell’utente, il servizio smetterebbe di funzionare. In effetti, Microsoft sconsiglia di utilizzare un account utente, soprattutto se amministratore, ma di usare LocalService oppure, nel caso il servizio debba accedere a risorse negate a LocalService, di usare LocalSystem.

Ok, ricompiliamo e prepariamoci al test. Solo che non possiamo fare F5, come faremmo con qualsiasi Windows Application…

Test di un servizio

Se scrivere un servizio non è molto diverso dallo scrivere un’applicazione o una dll, testarlo lo è. Tanto vale preparare prima gli strumenti:

  1. Prompt di comandi. Dal menu Start | Tutti i programmi | Visual Studio .NET 2003 | Visual Studio .NET 2003 Tools | Visual Studio .NET 2003 Command Prompt; da qui a colpi di CD spostatevi nella directory dove viene generato l’assembly del servizio: ci servirà per installare e disinstallare il servizio
  2. Service Control Program. Dal Pannello di Controllo | Strumenti di Amministrazione | Servizi: ci servirà per avviare e fermare il servizio (è il famoso SCP che avevamo incontrato tra i componenti di un servizio).
  3. Visualizzatore Eventi. Dal Pannello di Controllo | Strumenti di Amministrazione | Visualizzatore eventi: ci servirà per vedere gli eventi scritti dal nostro servizio

Installazione del servizio

Nel prompt dei comandi, inserire il comando

InstallUtil tmTestService.exe

e premere il tasto [Invio].

Se tutto è andato per il verso giusto, dovremmo vedere il messaggio:

Fase di commit completata
Installazione transazionale completata

Avvio del servizio

Se l’installazione è avvenuta con successo, nell’elenco visualizzato dall’applicazione Servizi, cerchiamo il nostro nuovo servizio; molto probabilmente non lo vedremo e altrettanto probabilmente la soluzione sarà molto semplice: premendo il tasto F5 la lista verrà aggiornata e ora dovremmo riuscire a localizzare il nostro eroe.

Una volta trovato, selezioniamolo, premiamo sul tasto destro del mouse e, dal menu contestuale che apparirà scegliamo la voce Avvia.

Test del servizio

Nel Visualizzatore eventi cominceremo a vedere i nuovi eventi registrati dal nostro servizio (anche in questo caso, premendo F5 si aggiorna l’elenco degli eventi)

Arresto del servizio

Analogamente a quanto visto per l’avvio, selezionando il nostro servizio nell’applicazione Servizi e clickando con il tasto destro del mouse, apparirà il menù contestuale, dal quale dovremo scegliere la voce Arresta.

Disinstallazione del servizio

Nel prompt dei comandi, inserire il comando

InstallUtil /u tmTestService.exe

e premere il tasto [Invio].

Se tutto è andato per il verso giusto, dovremmo vedere il messaggio:

Disinstallazione completata

Debug di un servizio

Debuggare un servizio è un esperienza che può far tornare indietro nel tempo di qualche lustro, quando le dita frullavano veloci per sparare agli alieni… il fatto è che un servizio non può essere lanciato dall’IDE come se fosse una normale applicazione; il solo provare a premere il tasto F5 provocherà il seguente messaggio d’errore:

Appena tentiamo di avviare il nuovo servizio appare una form di errore
L’errore al tentativo di avvio del servizio

Ma dài, l’abbiamo appena detto che per eseguire un servizio bisogna prima installarlo e poi avviarlo… Beh, prima di farlo, nell’IDE di Visual Studio scegliamo dal menu Tools la voce Debug Processes:

Il tool Processes che ci permette di avviare il servizio da debuggare
Il tool Processes che ci permette di avviare il servizio da debuggare

(nel caso il nostro servizio appena creato non dovesse apparire nell’elenco, sarà bene verificare che il check Show system processes sia spuntato come da esempio).

A questo punto torniamo bambini e, veloci come il lampo, dapprima installiamo ed avviamo il servizio, poi a colpi di ALT+TAB torniamo su questa form, cerchiamo il nostro servizio lo evidenziamo, clickiamo su Attach... nella form che appare selezioniamo (se non è già selezionata) la voce Common Language Runtime (come da esempio), quindi clickiamo su Close della form precedente: siamo ufficialmente in debug del servizio.

Per debuggare il servizio occorre attaccarsi al processo
Per debuggare il servizio occorre attaccarsi al processo

C’è un solo neo, in tutto questo: se il nostro servizio esegue del codice di inizializzazione in avvio (come effettivamente deve fare il mio servizio) e dobbiamo debuggare proprio questo codice di inizializzazione, abbiamo il lusso di poter scegliere tra due alternative (l’ironia sarà più chiara dopo aver letto le due alternative):

  1. inseriamo una lussuosa Sleep (tratta dalla namespace System.Threading.Current­Thread) di circa 20-25 mila millisecondi: il parametro non è facile da tarare: troppo basso e non farete in tempo a passare dall’avvio del servizio all’IDE di Visual Studio; troppo alto e il servizio non risponderà in tempo all’SCM (ha 30 secondi di tempo per farlo) e verrà abortito con un errore.
  2. come i bravi programmatori di una volta, inseriamo un sfilza di scritture su un file di testo che riportano messaggi del tipo “sono qui”, “ora sono qui” e così via.

Una volta debuggato il codice di inizializzazione, il resto è una passeggiata di salute, del tutto simile ad un debugging standard di un’applicazione Windows.

One Reply to “Guida per Autostoppisti alla Programmazione di un Servizio Windows in Visual Basic.NET”

  1. Pingback: Scrivere un’interfaccia per un Servizio in Visual Basic.NET – Il Gatto Informatico

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

*