Developpez.com - Microsoft DotNET
X

Choisissez d'abord la catégorieensuite la rubrique :


.Net 2.0 et les services Windows

Date de publication : 23/11/2006

Par Ronald VASSEUR (autres articles)

MVP Visual Developer - Visual Basic
 

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 offre 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.

Introduction
1. Présentation des services Windows
1.1. Qu'est-ce qu'un service Windows ?
1.2. Gestion des services sous Windows
2. Gérer les services Windows depuis .Net
2.1. Quelle interaction avec les services Windows ?
2.2. Gestion de services par le code
2.2.1. Interactions de base avec les services Windows
2.2.2. Autres interactions avec les services Windows
3. Créer un service Windows avec .Net
3.1. Création d'un service Windows avec Visual Studio 2005
3.1.1. Création du service
3.1.2. Installation du service avec InstallUtil
3.1.3. Installation du service avec un package MSI
3.2. Déboguer un service Windows avec Visual Studio 2005
Ressources
Conclusion


Introduction

Les services Windows sont souvent, à tord, un peu oubliés, et l'on n'y songe pas toujours lorsque se présentent des besoins spécifiques qui pourraient être satisfait par leur emploi. De plus, pensant être confronté à 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 par la suite 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.

Microsoft .Net

1. Présentation des services Windows


1.1. 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 :

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émarrage :

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.


1.2. Gestion des services sous Windows

Pour illustrer mes propos j'utiliserai Windows XP Pro SP2, mais sachez que, quelque 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 par exemple Services, Gestionnaire d'événements ou encore Gestion de l'ordinateur. Voici une capture d'écran de cette console :

Console Services.msc


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'agît 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éfinit 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 :

Dépendances du service


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.


2. Gérer les services Windows depuis .Net


2.1. 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 soit 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 ne 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.

info 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.


2.2. Gestion de services par le code

info 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 clique 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".

2.2.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ère, 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.

warning 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 rafraichir l'état d'un service, c'est-à-dire être sur de bien avoir l'état actuel d'un service et non l'état détecté il y a 5 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 7 é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.


2.2.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 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épendants d'un service

Nous allons voir ici qu'il est possible de récupérer la liste éventuelle des services dépendants 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'agît 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, comme par exemple son nom, ou même encore, les services dépendants 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épendants, en quelques lignes seulement :
Services dépendants 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.


3. 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.


3.1. Création d'un service Windows avec Visual Studio 2005


3.1.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 colone 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 :

Créer un service Windows avec Visual Studio 2005


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 à 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 tache 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

    ' Ecrit 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.


3.1.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 clique droit dans la zone du designer, et sélectionner "Ajouter le programme d'installation" comme nous le montre l'image ci-dessous :

Ajouter le programme d'installation


A 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é 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.

info 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, comme 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 service
        ' 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
info 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'executer, 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étails :

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'agît de la description du service, ce texte apparaitra 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écessaire.

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 quelque 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.

info Remarque : le processus d'installation d'un service Windows consiste en fait simplement en la création de clé 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.
Clés dans la base de registre Windows


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.

idea 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 installez (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%
idea 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 quand à elle permet d'arrêter un service Windows.
Voilà, j'espère vous avoir fait découvrir suffisamment en détails 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).


3.1.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 certains 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énerer 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 suppresion. 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 connu. 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 nombres de scénarios parmis 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 clique droit dans la zone du designer, et sélectionner "Ajouter le programme d'installation" comme nous le montre l'image ci-dessous :

Ajouter le programme d'installation


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'executer, 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".

Ajouter projet d'installation


Cliquez sur Ok, un nouveau projet est ajouté à votre solution. A 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 que é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 trouve trois dossiers. Sélectionnez le deuxième "Dossier de l'application", puis, faitez un clique 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 :

Sortie de projet


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 voir 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, puisque évidemment notre service est codé en .Net il faudra que le Framework soit installé pour qu'il puisse s'éxé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 "Coniditions de lancement" disponible depuis "Afficher", "Editeur". 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", "Editeur", "Actions personalisées". S'affiche alors l'éditeur d'actions personalisées qui contient les quatres grandes étapes qui vont être gérée. 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 quatres é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, comme 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.

Fichiers générés par Visual Studio 2005


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.


3.2. 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.

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.

warning Remarque : en procédant de la sorte vous devez gardez à 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'appeller ce thread depuis le OnStart() du service, il suffit alors d'appeller 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. Quand au reste du code, une fois le débogueur lancé, cela devient du débogage standard.


Ressources

fr Namespace System.Serviceprocess
fr La classe Debugger du namespace System.Diagnotics.
fr Mon blog
fr Mes articles
fr Les articles des autres rédacteurs
fr La rubrique .Net de Developpez.com
fr La rubrique Sécurité de Developpez.com

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.



Valid XHTML 1.1!Valid CSS!

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 © 2006 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. Droits de diffusion permanents accordés à Developpez LLC.

Responsable bénévole de la rubrique Microsoft DotNET : Hinault Romaric -