.Net 2.0 et les services Windows

Ronald VASSEUR

De nombreuses applications reposent sur des services Windows, ils sont un élément essentiel de Windows, et à ce titre, il est important de connaître leurs spécificités et leur fonctionnement. L'environnement .Net et son Framework en version 2.0, nous offrent de nombreuses classes pour interagir et gérer des services Windows très facilement. Il ne faut donc pas se priver de toutes ces fonctionnalités. Enfin, il est également possible de créer ses propres services Windows avec .Net, j'espère vous montrer au cours de cet article à quel point ces différentes tâches sont aisées. ♪

- Titre : .Net 2.0 et les services Windows

- Auteur : Ronald VASSEUR

- Parution : 23 novembre 2006

- Mise à jour : 4 avril 2020

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Ronald VASSEUR. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Présentation des services Windows

- II-A. Qu'est-ce qu'un service Windows ?

- II-B. Gestion des services sous Windows

- III. Gérer les services Windows depuis .Net

- III-A. Quelle interaction avec les services Windows ?

- III-B. Gestion de services par le code

- III-B-1. Interactions de base avec les services Windows

- III-B-2. Autres interactions avec les services Windows

- IV. Créer un service Windows avec .Net

- IV-A. Création d'un service Windows avec Visual Studio 2005

- IV-A-1. Création du service

- IV-A-2. Installation du service avec InstallUtil

- IV-A-3. Installation du service avec un package MSI

- IV-B. Déboguer un service Windows avec Visual Studio 2005

- V. Ressources

- VI. Conclusion

I - Introduction

Les services Windows sont souvent, à tort, un peu oubliés, et l'on n'y songe pas toujours lorsque se présentent des besoins spécifiques qui pourraient être satisfaits par leur emploi. De plus, pensant être confrontés à une complexité trop grande, de nombreux développeurs n'utilisent pas ou peu les services Windows ; que ce soit pour de simples interactions, et encore plus lorsqu'il s'agit de réaliser un service complet en .Net. Au cours de cet article, je vais essayer de vous présenter ce que sont les services, comment ils s'intègrent à Windows, puis comment interagir avec eux depuis une application .Net. Nous terminerons enfin, par le développement et l'intégration dans Windows d'un service développé en .Net avec Visual Studio 2005.

II - Présentation des services Windows

II-A - Qu'est-ce qu'un service Windows ?

Un service Windows, n'est ni plus ni moins qu'une application qui est lancée au démarrage de Windows, et qui fonctionne en tâche de fond sans intervention de l'utilisateur. On peut donc caractériser un service de la manière suivante :

- une application qui démarre en même temps que Windows ;

- une application qui fonctionne en arrière-plan sans intervention de l'utilisateur.

Par ailleurs, de nombreux services sont en fait des composants de Windows, qui sont configurés lors de l'installation du système d'exploitation. Un service démarre généralement en même temps que Windows, mais pour être exact, il existe trois types de démarrages :

- automatique : le service démarre en même temps que Windows ;

- manuel : le service démarre suite à l'action d'un utilisateur ou d'une application ;

- désactivé : le service est désactivé et ne peut pas démarrer.

Un service, qui en fait est une application, le plus souvent dépourvue d'interface graphique, peut fonctionner de manière autonome, mais peut également dépendre d'autres services, on parle dans cette situation de « dépendances ». Maintenant que nous avons défini rapidement ce qu'est un service, voyons comment il est possible de les gérer sous Windows.

II-B - Gestion des services sous Windows

Pour illustrer mes propos, j'utiliserai Windows XP Pro SP2, mais sachez que, quelle que soit votre version (depuis la version 2000 Pro/Server) de Windows, le principe sera plus ou moins le même.

Tout d'abord Windows possède une console dédiée à la gestion des services, elle est accessible depuis les outils d'administration du panneau de configuration, ou depuis l'invite de commandes en tapant « services.msc » (MSC, pour Microsoft Console, qui est en fait la console contenant tous les logiciels enfichables, tels que Services, Gestionnaire d'événements ou encore Gestion de l'ordinateur. Voici une capture d'écran de cette console :

La console Services.msc a principalement une fonction de gestion des services, c'est-à-dire gérer leur exécution, les configurer (compte utilisé, paramètre de récupération…) et de voir leur dépendance vis-à-vis d'autres services.

Nous allons donc voir comment gérer un service simplement en étudiant de près ses caractéristiques, du moins celles accessibles et modifiables depuis la console Services.msc.

Statut d'un service : il s'agit en fait de l'état dans lequel se trouve actuellement un service, il peut être démarré (en cours d'exécution), arrêté ou suspendu. Le statut d'un service est défini par défaut, mais l'utilisateur (administrateur de la machine évidemment) peut à tout moment changer le statut d'un service, en démarrant, arrêtant ou suspendant son exécution. Il va sans dire qu'un service arrêté ou suspendu n'est plus en état d'assurer la fonction qui est la sienne.

Type de démarrage : le type de démarrage est en fait la façon dont va démarrer un service, il en existe trois différents. Tout d'abord « automatique » c'est-à-dire qu'il sera lancé automatiquement par Windows lors du démarrage de ce dernier ; ensuite il y a « manuel », le service est par défaut arrêté et nécessitera l'intervention d'un utilisateur ou d'un programme ayant des permissions administratives sur la machine, et enfin, le dernier type de démarrage est « désactivé », en l'état un service ne peut pas être démarré que ce soit manuellement ou automatiquement.

Compte utilisé : un service s'exécutant sur une machine Windows doit tout naturellement disposer de droits pour cela, ainsi il va employer un compte (identifiant et mot de passe) ayant les autorisations requises. Majoritairement les services utilisent le compte système local, pour faire simple, celui-ci dispose de droit équivalent au compte administrateur, mais il ne peut pas être utilisé pour ouvrir une session Windows classique, il est utilisé « en interne » par Windows. D'ailleurs, ce compte n'apparaît pas dans la console « Groupes et Utilisateurs ».

Dépendances du service : un service peut fonctionner seul de manière autonome, ou alors nécessiter aussi d'autres services, ainsi lorsqu'un service dépend d'un autre, on appelle cela une dépendance. Un service peut d'ailleurs avoir besoin de plusieurs autres services, cela n'est pas limité à une unité. L'onglet « dépendances » affiche les services dont dépend le service concerné, mais aussi les services dépendant de ce service, voici l'exemple du service « Gestionnaire de comptes de sécurité » qui illustre ce propos :

Voilà, j'espère qu'au travers de cette rapide présentation des services Windows vous avez pu mieux appréhender leur utilité et leur fonctionnement, ainsi de comment ils peuvent être gérés par un administrateur Windows. Passons maintenant au cœur du sujet qui nous intéresse, les interactions avec les services depuis .Net.

III - Gérer les services Windows depuis .Net

III-A - Quelle interaction avec les services Windows ?

Les interactions depuis du code .Net avec des services sont relativement simples, elles se limitent principalement à récupérer l'état d'un service, l'installer, le démarrer, l'arrêter, le suspendre ou encore lui passer une commande. On voit donc que cela n'est en soi pas très complexe, mais l'on ne souhaite généralement pas interagir plus avec un service.

Un Namespace est dédié aux interactions avec les services Windows, il se nomme System.ServiceProcess, il contient en tout 10 classes. La classe que nous utiliserons le plus au cours de cet article est sans conteste ServiceController, c'est elle en effet qui permet, comme son nom l'indique, de contrôler un service.

Remarque : les exemples de code que je donnerai ici sont en Visual Basic 2005, ils s'appuieront sur le Framework .Net 2.0, mais devraient pouvoir fonctionner avec le Framework .Net 1.1 sans grande adaptation.

Passons, sans plus tarder, à des exemples concrets de gestion de services Windows depuis une application .Net 2.0.

III-B - Gestion de services par le code

Information : tout d'abord pour utiliser le Namespace System.ServiceProcess il faut ajouter une référence dans votre projet Visual Studio pour l'utiliser. Pour cela, faites un clic droit sur votre projet dans l'explorateur de solution, puis « ajouter une référence », dans l'onglet « .Net » choisissez System.ServiceProcess. Une fois cela, ajoutez dans l'entête de votre fichier de code « Imports System.ServicePorcess ».

III-B-1 - Interactions de base avec les services Windows

Arrêter un service Windows

Pour arrêter un service, rien de compliqué, il faut tout d'abord instancier un objet de type ServiceController, lui indiquer le nom du service souhaité, et utiliser la méthode Stop(). Voyons cela concrètement :

Arrêter un service Windows

Private Sub StopService()

' Instanciation d'un objet ServiceController
Dim myService As New ServiceController()

' Définition de la machine et du service concerné
myService.MachineName = "OSCAR"
myService.ServiceName = "VMAuthdService"

' Si le service peut être arrêté, alors on l'arrête
If myService.CanStop Then
myService.Stop()
End If

End Sub

Vous voyez que tout d'abord nous créons une instance de ServiceController, puis nous définissons les deux propriétés qui respectivement représentent le nom de la machine où s'exécute le service, et le nom du service lui-même. Une surcharge du constructeur vous permet d'y passer directement ces deux propriétés, mais j'ai préféré, pour des raisons de clarté, découper cela en deux étapes.

La propriété MachineName est une chaîne de caractères, qui peut être un « . » pour représenter l'ordinateur local où s'exécute l'application, le nom NetBIOS de la machine, ou encore son nom DNS complet si vous êtes dans un domaine Active Directory.

La propriété ServiceName est le nom du service, il ne faut pas le confondre avec le nom complet.

La propriété CanStop est en lecture seule, elle définit si un service peut être arrêté une fois qu'il a été démarré, ce n'est pas toujours le cas, un simple If permet d'avoir un code plus robuste, en effet, pourquoi essayer d'arrêter un service qui ne peut pas l'être !

Ce que nous venons de voir pour arrêter un service va être à peu de chose près le code que nous allons utiliser pour les autres interactions de base, à l'exception de la méthode utilisée.

Remarque : l'état d'un service étant par définition passager et appelé à évoluer dans le temps, je vous indique qu'il existe la méthode .Refresh() qui permet de rafraîchir l'état d'un service, c'est-à-dire être sûr de bien avoir l'état actuel d'un service et non l'état détecté il y a cinq minutes et qui depuis a peut-être changé.

Démarrer un service

Ici rien de particulier, sauf que l'on vérifie si le service n'est pas déjà démarré avant de vouloir le faire, ce qui somme toute semble logique.

Démarrer un service

Private Sub StartService()

' Instanciation d'un objet ServiceController
Dim myService As New ServiceController("OSCAR", "VMAuthdService")

' Si le service n'est pas démarré, alors on le démarre
If Not myService.Status = ServiceControllerStatus.Running Then
myService.Start()
End If

End Sub

Nous utilisons ici la propriété Statuts pour vérifier que le service n'est pas déjà démarré, nous l'utiliserons encore, notamment pour récupérer l'état d'un service.

Suspendre un service

Suspendre un service revient à mettre en pause un service, à suspendre son exécution provisoirement dans l'optique de la reprendre par la suite. La méthode .Pause() permet de réaliser cela. Ici, j'ai employé un Try/Catch pour varier le code par rapport aux deux exemples précédents.

Suspendre un service

Private Sub SuspendService()

' Instanciation d'un objet ServiceController
Dim myService As New ServiceController("OSCAR", "VMAuthdService")

' Suspend l'exécution du service
Try
myService.Pause()
Catch ex As Exception
MessageBox.Show("Impossible de suspendre le service")
End Try

End Sub

Récupérer l'état d'un service

Comme nous l'avons vu au début de cet article, un service peut être démarré, arrêté ou suspendu, voyons maintenant comment depuis du code .Net détecter l'état d'un service donné. Avec .Net rien de plus facile, voici un exemple :

Récupérer l'état d'un service

Private Function GetServiceState() As ServiceProcess.ServiceControllerStatus

' Instanciation d'un objet ServiceController
Dim myService As New ServiceController()

' Définition de la machine et du service concerné
myService.MachineName = "OSCAR"
myService.ServiceName = "VMAuthdService"

' Récupération de l'état actuel du service
Return myService.Status

End Function

Un service peut se trouver dans sept états différents, qui sont les suivants :

Nom de membre

Description

ContinuePending

Le service est en attente.

Paused

Le service est suspendu.

PausePending

La suspension du service est en attente.

Running

Le service est en cours d'exécution.

StartPending

Le service est en cours de démarrage.

Stopped

Le service n'est pas en cours d'exécution.

StopPending

Le service est en cours d'arrêt.

Ce tableau est extrait de la MSDN Library.

Après avoir vu des interactions de base avec un service Windows depuis du code .Net, voyons d'autres types d'interactions, elles ne sont pas spécialement plus complexes, mais elles sortent du cadre du simple démarrer/arrêter comme nous l'avons vu précédemment.

III-B-2 - Autres interactions avec les services Windows

Nous allons ici employer d'autres classes et quelques processus légèrement plus compliqués, mais rien d'insurmontable, évidemment puisque nous sommes en .Net :)

Lister les services présents

Une opération bien utile dans de nombreuses situations peut être de récupérer la liste des services présents sur une machine donnée, par exemple pour établir un inventaire ou même réaliser un petit gestionnaire de services « fait maison ».

Lister les services présents

Private Function ListServices() As ServiceController()

' Tableau qui va contenir les services de la machine
Dim myServices() As ServiceController

' Récupère la liste des services de la machine locale
myServices = ServiceController.GetServices()

Return myServices

End Function

La méthode .GetServices() nous renvoie donc un tableau contenant tous les services présents sur la machine locale. Un simple For Each permet donc de parcourir le tableau et de récupérer les services. L'exemple suivant affiche sur la sortie du débogueur le nom complet (pas le nom du service qui nous sert pour interagir avec un service) de tous les services. (Évidemment afficher cette liste à cet endroit n'est pas vraiment utile… cela n'est valable que dans le cadre de ma démonstration).

Afficher la liste des services

Private Sub DisplayServicesList()

For Each Service As ServiceController In ListServices()
Debug.WriteLine(Service.DisplayName)
Next

End Sub

Info : si l'on ne passe pas d'argument à la méthode .GetServices() alors la liste retournée est celle de la machine locale, pour récupérer la liste des services d'une machine distante, il faut passer le nom de cette dernière directement dans la méthode.

Services dépendant d'un service

Nous allons voir ici qu'il est possible de récupérer la liste éventuelle des services dépendant d'un service donné. Cette information peut être notamment très utile lorsqu'il s'agit d'arrêter un service proprement ou encore d'en comprendre le fonctionnement et l'organisation. Une propriété est spécialement dédiée à cette tâche, il s'agit de DependentServices qui contient un tableau d'instances de ServiceController, dont chacune représente en fait un service dépendant. On peut, à partir de ces instances, récupérer des informations sur chaque service dépendant, par exemple son nom, ou même encore, les services dépendant de ce service dépendant (relisez la phrase doucement, vous verrez, ce n'est pas si confus que cela !).

Voici un exemple permettant de lister les services dépendant d'autres, en quelques lignes seulement :

Services dépendant d'un service

Private Function GetDependentServices() As ServiceController()

' Instanciation d'un objet ServiceController
Dim myService As New ServiceController()

' Définition de la machine et du service concerné
myService.MachineName = "OSCAR"
myService.ServiceName = "winmgmt"

Return myService.DependentServices

End Function

Et voici comment en afficher les noms dans la sortie du débogueur :

Afficher les services dépendants

Public Sub DisplayDependentServices()
For Each Service As ServiceController In GetDependentServices()
Debug.WriteLine(Service.DisplayName)
Next
End Sub

Après avoir vu comment interagir avec des services Windows existants, sur une machine locale, ou distante, nous allons voir comment créer un service Windows en .Net avec Visual Studio 2005.

IV - Créer un service Windows avec .Net

Nous allons voir qu'il est possible de créer très facilement un service depuis Visual Studio 2005, nous verrons également qu'ils sont différents des applications Windows classiques.

IV-A - Création d'un service Windows avec Visual Studio 2005

IV-A-1 - Création du service

Pour créer un service Windows avec Visual Studio 2005, vous allez voir que c'est un jeu d'enfant, en effet notre IDE préféré intègre déjà un type d'application « Service Windows » qui va en fait créer les bases d'un service, avec le squelette de code nécessaire. Après avoir lancé Visual Studio 2005, allez dans le menu « Fichier », « Nouveau », « Projet », puis dans la fenêtre qui s'ouvre colonne de gauche dans « Types de projets », « Visual Basic », « Windows », sélectionnez « Service Windows » dans la colonne de droite, et enfin saisissez le nom du projet que vous souhaitez. L'image ci-dessous correspond à ce que vous devez avoir à l'écran :

Visual Studio crée alors un certain nombre de fichiers, dont le fichier Service1.vb qui va contenir le code de notre service Windows. Voici le squelette par défaut de ce fichier :

Service1.vb

Public Class Service1

Protected Overrides Sub OnStart(ByVal args() As String)
' Ajoutez ici le code pour démarrer votre service. Cette méthode doit démarrer votre service.
End Sub

Protected Overrides Sub OnStop()
' Ajoutez ici le code pour effectuer les destructions nécessaires à l'arrêt de votre service.
End Sub

End Class

Vous voyez que par défaut il y a deux méthodes, OnStop() et OnStart() qui, vous l'aurez compris, seront exécutées respectivement à l'arrêt et au démarrage du service.

Le but de cet article n'étant pas de créer un service complexe, mais simplement de voir comment en créer, notre service effectuera une tâche simple, il écrira la date et l'heure dans un fichier à chaque fois qu'il démarrera ou s'arrêtera. Pour atteindre cet objectif hautement technique et complexe, nous allons ajouter une méthode à notre classe Service1, il s'agit de la méthode WriteToFile(), en voici le code :

Méthode pour écrire dans un fichier

Private Sub WriteToFile(ByVal FileFolder As String, _
ByVal FileName As String, _
ByVal LineToAdd As String)

' FilePath : répertoire où se trouve le fichier à écrire
' FileName : nom complet du fichier (exemple.txt)
' LineToAdd : ligne de texte à écrire dans le fichier

' Chemin complet de notre fichier texte
Dim FullFilePath As String = FileFolder & "\" & FileName

' On écrit dans le fichier, s'il n'existe pas il sera créé
' automatiquement (Merci le My !), le booléen correspond au fait que
' l'on souhaite ajouter le texte au texte existant, au lieu de l'écraser.
My.Computer.FileSystem.WriteAllText(FullFilePath, LineToAdd, True)

End Sub

Il suffit d'appeler WriteToFile dans les méthodes OnStart() et OnStop(), et à chaque fois que notre service démarrera et s'arrêtera, il écrira dans le fichier texte, la date et l'heure. Voici les paramètres à passer à notre méthode :

Dans OnStart() :

Méthode OnStart()

WriteToFile(Environment.SpecialFolder.Desktop.ToString, "MonPremierService.txt", "Démarrage :_
" & Date.Now.ToShortDateString & " -- " & Date.Today.ToShortTimeString)

Dans OnStop() :

Méthode OnStop()

WriteToFile(Environment.SpecialFolder.Desktop.ToString, "MonPremierService.txt", "Arrêt :_
" & Date.Now.ToShortDateString & " -- " & Date.Today.ToShortTimeString)

Le code complet de votre classe Service1.vb doit ressembler à ce qui suit :

Service1.vb

Public Class Service1

Protected Overrides Sub OnStart(ByVal args() As String)
' Ajoutez ici le code pour démarrer votre service. Cette méthode doit
' démarrer votre service.

WriteToFile(Environment.SpecialFolder.Desktop.ToString, "MonPremierService.txt", "Démarrage :_
" & Date.Now.ToShortDateString & " -- " & Date.Today.ToShortTimeString)

End Sub

Protected Overrides Sub OnStop()
' Ajoutez ici le code pour effectuer les destructions nécessaires à l'arrêt de votre service.

WriteToFile(Environment.SpecialFolder.Desktop.ToString, "MonPremierService.txt", "Arrêt :_
" & Date.Now.ToShortDateString & " -- " & Date.Today.ToShortTimeString)

End Sub

' Écrit dans un fichier texte
Private Sub WriteToFile(ByVal FileFolder As String, ByVal FileName As String, ByVal LineToAdd As String)
' FilePath : répertoire où se trouve le fichier à écrire
' FileName : nom complet du fichier (exemple.txt)
' LineToAdd : ligne de texte à écrire dans le fichier

' Chemin complet de notre fichier texte
Dim FullFilePath As String = FileFolder & "\" & FileName

' On écrit dans le fichier, s'il n'existe pas il sera créé automatiquement (Merci le My !),
' le booléen correspond au fait que l'on souhaite ajouter le texte au texte existant, au
' lieu de l'écraser.
My.Computer.FileSystem.WriteAllText(FullFilePath, LineToAdd, True)

End Sub

End Class

Ici, l'écriture du code du service à proprement parler est terminée, il ne reste plus qu'à le « compiler ». Mais nous allons voir qu'à ce stade notre service n'est pas utilisable en l'état, il va falloir encore ajouter un élément pour installer ce service sur une machine. Voyons comment procéder.

IV-A-2 - Installation du service avec InstallUtil

Une fois de plus Visual Studio ne nous laisse pas tomber, et prévoit le scénario relativement fréquent où, quand un développeur crée un service, il souhaite l'installer. Il va falloir ajouter une autre classe à notre service qui va contenir les données et paramètres d'installation. Pour générer automatiquement une partie de ce code, il faut se rendre dans le designer de Visual Studio 2005 pour notre fichier Service1.vb (Visual Studio 2005 affiche alors dans l'onglet Service1.vb [Design]), puis faire un clic droit dans la zone du designer, et sélectionner « Ajouter le programme d'installation » comme nous le montre l'image ci-dessous :

À ce stade, de nouveaux fichiers sont ajoutés à votre solution Visual Studio, on voit apparaître ProjectInstaller.vb dans l'explorateur de solutions. Si l'on affiche le code de fichier, voici ce que vous devez voir :

ProjectInstaller.vb

Imports System.ComponentModel
Imports System.Configuration.Install

Public Class ProjectInstaller

Public Sub New()
MyBase.New()

'Cet appel est requis par le Concepteur de composants.
InitializeComponent()

'Ajoutez le code d'initialisation après l'appel de InitializeComponent

End Sub

End Class

Vous pouvez voir que deux composants ont été déposés par le designer de Visual Studio 2005, il s'agit d'un composant ServiceProcessInstaller et d'un composant ServiceInstaller. Nous allons ici devoir ajouter un certain nombre d'informations au code de la classe ProjectInstaller ci-dessus.

Remarque : le code que nous allons ajouter ici pourrait être généré par Visual Studio, car en fait il va s'agir de renseigner des propriétés des deux composants déposés par le designer, mais pour que cela soit plus ludique nous allons le « faire à la main ».

Les informations que nous souhaitons ajouter sont en grande partie des informations sur le service, par exemple celles qui s'affichent pour n'importe quel service dans la console Services.msc que nous avons vu au début de cet article. Sans plus attendre, le code :

Configuration du ser

' Compte utilisé par le service pour s'exécuter, ici le compte Système Local disposant de
' privilèges importants.
ServiceProcessInstaller1.Account = ServiceProcess.ServiceAccount.LocalSystem

' Description du service
ServiceInstaller1.Description = "Service exemple, créer un fichier MonPremierService.txt sur le bureau."

' Nom complet du service
ServiceInstaller1.DisplayName = "Developpez.com"

' Nom du service, utilisé en interne par Windows
ServiceInstaller1.ServiceName = "DvpCom"

' Mode de démarrage du service, ici mode manuel
ServiceInstaller1.StartType = ServiceProcess.ServiceStartMode.Manual

Si l'exécution de notre service dépend d'autres services, on l'indique également lors de l'installation, pour cela il existe la propriété ServicesDependedOn. On lui attribue un tableau de String contenant les noms des services dont dépend le service que nous souhaitons installer. Voici ci-dessous l'exemple d'un service dépendant de deux services : Service1 et Service2.

Dépendances du service

Dim serviceDependantOn() As String = Nothing
serviceDependantOn(0) = "Service1"
serviceDependantOn(1) = "Service2"

' Liste des services dont dépend le service à installer
ServiceInstaller1.ServicesDependedOn = serviceDependantOn

Remarque : si lors de l'installation et du démarrage de notre service, les services contenus dans le tableau de chaînes ne sont pas démarrés, ils vont être lancés, même s'ils sont en démarrage manuel.

Le code de votre classe ProjectInstaller doit donc ressembler à cela :

ProjectInstaller.vb

Public Class ProjectInstaller

Public Sub New()
MyBase.New()

'Cet appel est requis par le Concepteur de composants.
InitializeComponent()

'Ajoutez le code d'initialisation après l'appel de InitializeComponent

' Compte utilisé par le service pour s'exécuter, ici le compte Système Local disposant de
' privilèges importants.
ServiceProcessInstaller1.Account = ServiceProcess.ServiceAccount.LocalSystem

' Description du service
ServiceInstaller1.Description = "Service exemple, créer un fichier MonPremierService.txt sur le bureau."

' Nom complet du service
ServiceInstaller1.DisplayName = "Developpez.com"

' Nom du service, utilisé en interne par Windows
ServiceInstaller1.ServiceName = "DvpCom"

' Mode de démarrage du service, ici mode manuel
ServiceInstaller1.StartType = ServiceProcess.ServiceStartMode.Manual

End Sub

End Class

Les propriétés du code un peu plus en détail

ServiceAccount : c'est le compte qui va être utilisé pour exécuter le service. Il existe quatre possibilités offrant une sécurité et des privilèges variables en fonction des besoins. Vous pouvez même spécifier directement l'utilisateur que vous souhaitez en fournissant son identifiant et son mot de passe. Voici un tableau issu de la documentation MSDN Library vous montrant les possibilités qui vous sont offertes :

Nom de membre

Description

LocalService

Compte qui agit en tant qu'utilisateur non privilégié sur l'ordinateur local et présente des informations d'identification anonymes à n'importe quel serveur distant.

LocalSystem

Compte, utilisé par le Gestionnaire de contrôle des services, qui a des privilèges étendus sur l'ordinateur local et agit comme l'ordinateur sur le réseau.

NetworkService

Compte qui fournit des privilèges locaux extensifs et présente les informations d'identification de l'ordinateur à n'importe quel serveur distant.

User

Compte défini par un utilisateur spécifique sur le réseau. Lorsque User est spécifié pour le membre ServiceProcessInstaller.Account, le système demande un nom d'utilisateur et un mot de passe valides lorsque le service est installé, à moins que vous ne définissiez des valeurs pour les propriétés Username et Password de votre instance de ServiceProcessInstaller.

Description : il s'agit de la description du service, ce texte apparaîtra dans la console services.msc lorsqu'un utilisateur regardera les propriétés de votre service. Attention : cette propriété est une nouveauté du Framework .Net 2.0. pour avoir une finalité équivalente avec des versions antérieures de .Net vous devez directement éditer la bonne clé dans la base de registre.

DisplayName : il s'agit du nom complet du service, choisissez un nom explicite. Il peut comporter des espaces si nécessaires.

ServiceName : le nom de service est le nom employé par Windows en interne, il doit être simple et explicite.

StartType : c'est le type de démarrage du service : manuel, automatique ou désactivé.

Une fois tout cela terminé vous devez générer votre projet dans Visual Studio 2005 en mode Release (puisque le développement de votre service est terminé). L'exécutable de votre service se trouve donc dans le répertoire \Bin\Release\ de votre projet de service Windows. Déplacez alors cet exécutable vers son emplacement définitif, il ne reste alors plus qu'à employer l'utilitaire InstallUtil pour installer correctement votre service Windows.

En effet, le Framework .Net (1.1 ou 2.0) fournit un petit utilitaire nommé InstallUtil qui se charge de créer les clés nécessaires dans le registre pour faire en sorte que votre application devienne un véritable service Windows qui apparaîtra dans la console Services.msc. Cet utilitaire est en ligne de commande, il faut procéder comme suit pour installer un service Windows.

Démarrer l'invite de commande de Visual Studio 2005, ou alors avec l'invite de commande Windows déplacez-vous jusqu'au répertoire C:\WINDOWS\Microsoft.NET\Framework\votre_Framework où se trouve InstallUtil.exe. Saisissez alors la ligne suivante :

InstallUtil chemin_complet_de_l'exécutable_du_service
Par exemple : InstallUtil c:\monServiceDeveloppez.exe

Si tout se passe bien, vous devez voir s'afficher les informations suivantes dans votre invite de commande :

Invite de commande

Microsoft (R) .NET Framework Installation utility Version 2.0.50727.42
Copyright (c) Microsoft Corporation. Tous droits réservés.

Exécution d'une installation traitée avec transaction.

Début de la phase d'installation de l'installation.
Consultez le contenu du fichier journal pour l'avancement de l'assembly c:\monServiceDeveloppez.exe.
Le fichier se trouve à c:\monServiceDeveloppez.InstallLog.
Installation de l'assembly 'c:\monServiceDeveloppez.exe'.
Les paramètres affectés sont :
logtoconsole =
assemblypath = c:\monServiceDeveloppez.exe
logfile = c:\monServiceDeveloppez.InstallLog
Installation du service DvpCom en cours...
Le service DvpCom a été installé avec succès.
Création d'une source EventLog DvpCom dans le journal Application...

La phase d'installation est terminée et la phase de validation a commencé.
Consultez le contenu du fichier journal pour l'avancement de l'assembly c:\monServiceDeveloppez.exe.
Le fichier se trouve à c:\monServiceDeveloppez.InstallLog.
Validation de l'assembly 'c:\monServiceDeveloppez.exe'.
Les paramètres affectés sont :
logtoconsole =
assemblypath = c:\monServiceDeveloppez.exe
logfile = c:\monServiceDeveloppez.InstallLog

La phase de validation est terminée.

L'installation traitée avec transaction est terminée.

L'installation de votre service Windows est terminée, et vous le voyez désormais apparaître dans la console Services.msc, essayez de le démarrer et de l'arrêter quelques fois, puis allez voir le contenu du fichier MonServiceDeveloppez.txt à la racine du disque C, il doit contenir des lignes indiquant le démarrage et l'arrêt du service.

Remarque : le processus d'installation d'un service Windows consiste en fait simplement en la création de clés dans le registre contenant les informations et paramètres du service, ainsi si vous le souhaitez, vous pouvez vous passer de l'utilitaire InstallUtil et faire tout vous-même, mais pourquoi vouloir absolument réinventer la roue ? Les clés créées dans le registre Windows se trouvent à cet emplacement HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\nom_du_service.

Pour désinstaller le service, l'utilitaire InstallUtil est également votre allié, il suffit de saisir la commande suivante :

InstallUtil /U chemin_complet_de_l'exécutable_du_service
Par exemple : InstallUtil /U c:\monServiceDeveloppez.exe

Comme lors de l'installation, des informations d'installation sont affichées dans l'invite de commande et vous confirment la désinstallation du service. Attention cependant, même après la désinstallation du service, l'exécutable de votre service se trouve toujours à son emplacement sur le disque dur, la désinstallation correspond à la désinscription de l'application en tant que service Windows.

Remarque : le fait de devoir utiliser l'invite de commande de Visual Studio ou de Windows n'est pas à la portée de l'utilisateur standard, et peut en rebuter plus d'un. Pour contourner ce problème n'oubliez pas que vous pouvez créer des fichiers batch contenant les commandes nécessaires pour installer (on devrait plutôt dire « enregistrer ») votre application en tant que service Windows. Ci-dessous, un exemple simpliste et non robuste (pas de gestion des erreurs, pas de détection de la version du Framework) d'un fichier bat.

Pour installer un service

Installation d'un service

ECHO Installation  du service Developpez.com...

ECHO OFF
SET ExeService="C:\monServiceDeveloppez.exe"
SET ToolPath=%WINDIR%"\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe"

ECHO Installation du service %ExeService% en cours...

%ToolPath% %ExeService%

Pour désinstaller un service

Désinstallation d'un service

ECHO Suppression du service Developpez.com...

ECHO OFF
SET ExeService="C:\monServiceDeveloppez.exe"
SET ToolPath=%WINDIR%"\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe"

%ToolPath% /U %ExeService%

Remarque : créez deux fichiers (install.bat et uninstall.bat par exemple) que vous exécuterez respectivement lors de l'installation et de la désinstallation du service. Ainsi, vous automatiserez la configuration du service Windows. De plus, je vous rappelle aussi la commande Net Start nom_du_service qui permet de démarrer un service Windows depuis la ligne de commande, cela pourrait s'avérer utile si vous souhaitez démarrer le service Windows sans intervention de l'utilisateur. La commande Net Stop nom_du_service quant à elle permet d'arrêter un service Windows.

Voilà, j'espère vous avoir fait découvrir suffisamment en détail comment créer et installer un service Windows avec .Net et InstallUtil. Passons maintenant à une technique beaucoup plus professionnelle dans son fonctionnement, je veux parler des packages Windows Installer (MSI).

IV-A-3 - Installation du service avec un package MSI

La solution précédente peut convenir dans de nombreux cas, mais dans des situations de déploiements à grande échelle, par GPO (Active Directory), où même lorsque les utilisateurs doivent réaliser cette tâche eux-mêmes, la relative complexité peut bloquer certaines personnes. Il faut aussi dire que la solution proposée ci-dessous n'est pas forcément très esthétique et dans les règles de l'art. C'est pour cela que nous allons voir ici comment créer un projet d'installation et de déploiement dans Visual Studio pour générer un installer MSI.

Tout d'abord, il est important à mon avis de décrire rapidement ce qu'est un package MSI, c'est loin d'être un simple exécutable pour installer une application, un service ou autre… C'est un package qui possède des fonctions avancées : installation, réparation, modification et suppression ; et, qui plus est, est entièrement paramétrable et personnalisable, permet de générer des logs d'installation, de réaliser tout cela de manière interactive ou totalement silencieuse, ce qui en fait le partenaire idéal des déploiements automatisés et/ou à grande échelle. La technologie MSI est présente en standard dans les OS de Microsoft depuis la version Millenium de Windows. Elle s'appuie sur un service nommé « Windows Installer » dont l'exécutable Msiexec.exe va gérer l'action des packages MSI. Il faut donc voir un fichier MSI comme un conteneur, qui va empaqueter une application, et qui va entièrement gérer les grandes étapes de « sa vie » que sont l'installation, la modification, la réparation et la suppression. Je ne pense pas qu'il soit utile d'entrer plus avant dans la description des packages MSI, même si bien trop de développeurs sous-estiment fortement leur potentiel, qui est d'ailleurs l'objet de nombreux ouvrages.

Nous allons voir maintenant comment générer ce fameux package MSI. Tout d'abord, je tiens à préciser que de nombreux logiciels permettent de créer des installer MSI, et que cela va de l'outil en ligne de commande, à l'atelier complet de repackaging de logiciel tel « Wise Studio » pour n'en citer qu'un des plus connus. Ici, j'utiliserai l'outil qui nous semble évident dans le cadre de ce tutoriel, je veux bien évidemment parler de Visual Studio 2005, qui possède en la matière des possibilités très importantes, et qui couvriront nombre de scénarios parmi les plus fréquents.

Pour créer notre Installer nous allons repartir de la fin de « 3.1.1 Création du service », cela signifie que notre service est terminé, et que nous devons maintenant le paramétrer de sorte qu'il puisse être exécuté par Windows. Tout d'abord, il faut se rendre dans le designer de Visual Studio 2005 pour notre fichier Service1.vb (Visual Studio 2005 affiche alors dans l'onglet Service1.vb [Design]), puis faire un clic droit dans la zone du designer, et sélectionner « Ajouter le programme d'installation » comme nous le montre l'image ci-dessous :

Comme signalé un peu plus haut dans cet article, cette action ajoute de nouveaux fichiers à la solution, et celui qui nous intéresse maintenant se nomme ProjectInstaller.vb, c'est ici que nous allons paramétrer notre service. Comme j'ai déjà expliqué tout cela plus haut je n'entrerai pas dans les détails, voici donc à quoi doit ressembler le contenu de ce fichier :

ProjectInstaller.vb

Public Class ProjectInstaller

Public Sub New()
MyBase.New()

'Cet appel est requis par le Concepteur de composants.
InitializeComponent()

'Ajoutez le code d'initialisation après l'appel de InitializeComponent

' Compte utilisé par le service pour s'exécuter, ici le compte Système Local disposant de
' privilèges importants.
ServiceProcessInstaller1.Account = ServiceProcess.ServiceAccount.LocalSystem

' Description du service
ServiceInstaller1.Description = "Service exemple, créer un fichier MonPremierService.txt sur le bureau."

' Nom complet du service
ServiceInstaller1.DisplayName = "Developpez.com"

' Nom du service, utilisé en interne par Windows
ServiceInstaller1.ServiceName = "DvpCom"

' Mode de démarrage du service, ici mode manuel
ServiceInstaller1.StartType = ServiceProcess.ServiceStartMode.Manual

End Sub

End Class

C'est maintenant que nous allons à proprement parler faire en sorte de générer un installer MSI. Pour cela, placez-vous sur la solution courante dans l'explorateur de solutions, puis faites « Ajouter », « Nouveau projet », « Configuration et déploiement » et enfin « Projet d'installation », ici je lui donnerai le nom « MonSetup ».

Cliquez sur OK, un nouveau projet est ajouté à votre solution. À ce moment-là, l'éditeur « Système de fichiers » s'ouvre. En sélectionnant MonSetup dans l'explorateur de solution, vous avez accès à de nombreuses propriétés qu’évidemment vous pouvez configurer depuis la fenêtre du même nom. Je vous laisse parcourir et découvrir par vous-même ces propriétés.

Retournez maintenant dans l'éditeur « Système de fichiers » qui s'est ouvert lors de l'ajout du projet d'installation à notre solution. Il est comporte deux colonnes, et dans celle de gauche se trouvent trois dossiers. Sélectionnez le deuxième « Dossier de l'application », puis, faites un clic droit, « Ajouter » et enfin « Sortie de projet ». Normalement vous n'avez rien à modifier dans la fenêtre qui s'ouvre, elle doit être conforme à l'image ci-dessous :

Cliquez sur « OK ». Nous venons ici de signaler à notre package MSI qu'il devra contenir l'application « MonServiceDevelopez », ainsi elle sera gérée par cet Installer. On voit apparaître dans l'explorateur de solution la sortie principale qui correspond au service à installer, et de plus nous voyons qu'une dépendance a été détectée, celle au Framework .Net, puisqu’évidemment notre service est codé en .Net il faudra que le Framework soit installé pour qu'il puisse s'exécuter. Cette dépendance signifie que l'installeur vérifiera la présence du Framework avant de procéder à l'installation du service. La version du Framework à détecter, ainsi que l'action à effectuer en fonction de cette condition sont paramétrables depuis l'éditeur de « Conditions de lancement » disponible depuis « Afficher », « Éditeur ». Par défaut la version minimale du Framework est bien évidemment celle qui a été utilisée pour créer notre service. Sur ma machine cette version est la 2.0.50727, et le MSI est paramétré pour faire en sorte de n'accepter que cette version précise, ce qui est logique pour une application .Net.

En l'état notre service va s'installer sur la machine, mais ne sera pas exécuté et paramétré. Pour cela, il faut ajouter une action personnalisée (ou « Custom action ») à notre projet de déploiement. C'est d'ailleurs à partir de ces actions que nous pouvons très finement personnaliser un installer MSI, mais ici rien de cela, nous voulons juste un installer « de base ». Allez dans le menu « Affichage », « Éditeur », « Actions personnalisées ». S'affiche alors l'éditeur d'actions personnalisées qui contient les quatre grandes étapes qui vont être gérées. En haut, dans le menu « Action », sélectionnez « Ajouter une action personnalisée… », puis dans la liste « Regardez dans », choisissez « Dossier de l'application », sélectionnez « Sortie principale de MonServiceDeveloppez (Actif) ». Cliquez sur « OK ». Dans l'éditeur d'actions personnalisées, vous voyez qu'une action a été ajoutée pour chacune des quatre étapes.

Voilà, notre Installer est prêt, enfin presque, il ne reste plus qu'à le générer, pour cela il faut aller dans le menu du même nom, puis choisir « Générer MonSetup ». La génération démarre, et dans le dossier release ou setup, en fonction du mode dans lequel vous êtes, va être créé un fichier MonSetup.msi et un fichier Setup.exe. Lancez le Setup.exe, qui exécutera alors le package MSI MonSetup.msi. Vous seront alors demandés un certain nombre de renseignements, par exemple, pour qui installer l'application, et à quel emplacement. Une fois l'installation terminée, lancez la console « services.msc » depuis « Démarrer », « Exécuter », notre service Developpez.com a été correctement installé et paramétré. Exécutez de nouveau Setup.exe, cette fois-ci, il vous propose de réparer ou de supprimer le service.

J'espère vous avoir montré à quel point installer un service est aisé avec cette méthode. Bien évidemment nous avons à peine survolé l'utilisation des packages MSI, mais gardez à l'esprit qu'ils sont très puissants et permettent de couvrir de nombreux scénarios, ainsi que de personnaliser fortement un processus d'installation.

IV-B - Déboguer un service Windows avec Visual Studio 2005

Une chose qui, à première vue, peut sembler complexe est le débogage de service Windows. Mais vous allez voir que cela est beaucoup plus simple qu'il n'y parait. Tout d'abord, pour pouvoir déboguer votre service Windows, vous devez l'avoir installé comme je l'ai expliqué un peu plus haut. De plus, lors de la génération du service, vous devez être en mode Debug, sans cela, déboguer une application générée en mode Release n'a pas de sens en soi.

Voici comment déboguer un service Windows grâce à Visual Studio 2005 :

- ouvrez la solution contenant votre projet dans Visual Studio, de manière à disposer des sources de votre service ;

- placez maintenant, ou plus tard les points d'arrêt et les autres éléments vous permettant d'effectuer votre débogage ;

- vérifiez que le service est en cours d'exécution sur votre machine ;

- dans le menu Déboguer de Visual Studio, cliquez sur « Attacher au processus ». Notre service utilisant le compte local système, il est normal qu'il n'apparaisse pas dans la liste des processus utilisant votre compte utilisateur, pensez donc, si besoin est, à cocher la case « Afficher les processus de tous les utilisateurs », puis à cliquer sur le bouton « Actualiser ». Dès lors, le processus monServiceDeveloppez.exe correspondant à notre service se trouve dans la liste. Sélectionnez-le, puis cliquez sur le bouton « Attacher ».

Vous pouvez maintenant librement profiter des capacités de débogage de Visual Studio 2005, même sur un service Windows que vous venez de créer. Pour faciliter le débogage, n'oubliez pas d'utiliser la console services.msc pour forcer le démarrage, l'arrêt, la pause ou tout autre événement sur votre service à déboguer.

Remarque : en procédant de la sorte, vous devez garder à l'esprit que vous ne pouvez pas déboguer le code se trouvant dans le OnStart() puisque, pour attacher le processus du service, celui-ci doit être déjà démarré et, par conséquent, avoir exécuté le code du OnStart(). Pour contourner ce problème, il existe plusieurs manières de procéder, mais qui sont un peu du « bricolage », qui consiste à faire réexécuter le code du OnStart() une fois le processus attaché, ou encore à déplacer le code du OnStart() pour avoir le temps d'attacher le processus au débogueur. Une autre solution un peu plus « propre » est de placer le code du service dans un thread séparé, puis d'appeler ce thread depuis le OnStart() du service, il suffit alors d'appeler le débogueur depuis le code (avec la classe Debugger du namespace Systeme.Diagnostics), et il faut aussi penser à positionner un point d'arrêt dans le code, sinon l'exécution du code ne sera pas suspendue. Merci à Abelman pour cette dernière solution.

Vous l'aurez compris, ce qui pose problème dans le débug des services c'est le code du OnStart(), mais il y a toujours moyen de ruser pour y arriver. Quant au reste du code, une fois le débogueur lancé, cela devient du débogage standard.

V - Ressources

Namespace System.Serviceprocess
La classe Debugger du namespace System.Diagnotics

Mon blog
Mes articles
Les articles des autres rédacteurs
La rubrique .Net de Developpez.com
La rubrique Sécurité de Developpez.com

VI - Conclusion

Au cours de cet article, nous avons vu que gérer un service Windows avec .Net et Visual Studio 2005 ne présente pas de difficulté particulière, que ce soit en local ou même en distant. De plus, la création de services Windows est un jeu d'enfant grâce aux modèles d'applications intégrés dans notre IDE préféré. Le débogage et l'installation de services réalisés avec .Net sont facilités, respectivement par les capacités de VS 2005 et la simplicité de l'utilitaire InstallUtil du Framework .Net ou même la création d'Installer Windows (MSI). J'espère qu'au travers de cet article vous aurez compris le fonctionnement et la spécificité des services Windows, et que désormais, vous n'hésiterez plus à les mettre en œuvre avec vos applications .Net.

Un grand merci à Khany pour la relecture de cet article, ainsi qu'à l'équipe .Net pour son aide.