GERBELOTBARILLON.COM

Parce qu'il faut toujours un commencement...

Service Windows avec Python

Ecrire un service Windows, un vrai, celui qui se manipule avec les commandes usuelles de Windows et qui se retrouve dans la mmc services.msc de Windows n'est pas toujours aisé si l'on n'utilise pas du C# ou du C++. Pour des raisons d'efficacité et de rapidité de mise en place, utiliser Python apporte une véritable valeur ajoutée et lui adjoindre la bibliothèque pywin32 en fait un candidat de choix pour l'écriture d'outils pour Windows.

Définition du service

Si vous ne disposez pas déjà des packages requis :

Un service avec Python est composé d'une classe décrivant le programme et les actions qu'il effectue. Par défaut, un service est capable de passer par plusieurs états qui sont généralement Started, Stopped, Running, Pending, Enabled, Disabled... Un exemple de classe de base peut être celle-ci :

class MyService:
"""Ne fait rien de particulier à part disposer de méthodes basiques"""
def stop(self):
   """Arrêt du service"""
   self.running = False

def run(self):
   """Démarrage du service et boucle principale. Tout se passe ici."""
   self.running = True
   while self.running:
      time.sleep(10)  # Un peu de temps pour les autres et accessoirement l'emplacement où vont se dérouler les opérations
      servicemanager.LogInfoMsg("Service running...")

Ce morceau de code servira de base de classe pour déclarer les services :

class MyServiceFramework(win32serviceutil.ServiceFramework):

_svc_name_ = 'MyService'
_svc_display_name_ = 'My Service display name'

def SvcStop(self):
    """Commande d'arrêt du service"""
    self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
    self.service_impl.stop()
    self.ReportServiceStatus(win32service.SERVICE_STOPPED)

def SvcDoRun(self):
  """Lance le démarrage du service. Ne rend pas la main tant que le service est en route."""
    self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
    self.service_impl = MyService()
    self.ReportServiceStatus(win32service.SERVICE_RUNNING)
    # Run the service
    self.service_impl.run()

Pour que le service puisse être géré il faut inclure un certain nombre de bibliothèques :

import time # pour la gestion du temps d'attente notamment

import win32serviceutil  # ServiceFramework et helper de commandline
import win32service  # Gestion des événements relatifs aux services
import servicemanager  # Setup et logging du service

...
...
...

# Fonction appelée en mode stand-alone
# Si argv == 1 cela veut dire que le programme souhaite exécuter le Service Dispatcher pour
# permettre à Windows de gérer le service. S'il y a d'autres arguments, alors on considère que ce sont des
# arguments pour générer une action sur le service. Dans tous les cas le paramètre MyServiceFramework est passé
# ServiceManager va interagir avec le SCM de Windows pour Service Control Manager
# Wind32ServiceUtil va gérer les arguments de la ligne de commande.
# pour que la classe soit disponible.
def init():
   if len(sys.argv) == 1:
      servicemanager.Initialize()
      servicemanager.PrepareToHostSingle(MyServiceFramework)
      servicemanager.StartServiceCtrlDispatcher()
   else:
      win32serviceutil.HandleCommandLine(MyServiceFramework)

if __name__ == '__main__':
   init()

Création de l'exécutable

La transformation du script de service python en fichier exécutable Windows va se faire assez facilement par l'intermédiaire de l'outil pyinstaller. La commande globale sera la suivante :

pyinstaller --runtime-tmpdir=. --onefile --hidden-import win32timezone myservice.py
Les options sont les suivantes :

Manipuler le service

Le plus simple est d'ouvrir une commande MS-DOS en mode Administrateur. Il faut absolument disposer de privilèges élevés pour installer des services...

Sources documentaires :