Prozesse und Dienste

Einleitung

einige Basisbeispiele
    Beispiel 1a: Alle installierten Dienste anzeigen (get-service/ .Net)
    Beispiel 1b: Alle installierten Dienste anzeigen (WMIKlasse: win32_service)
    Beispiel 2: Monitoring und automatischer Neustart von Diensten

3    einen neuen Dienst erstellen
3.1 Einen Dienst in VisualBasic programmieren  


0 Einleitung

Dienste lokal und remote auf einem oder mehreren Rechnern zu verwalten, zu überwachen, zu starten, zu stoppen und zu erforschen ist der Gegenstand dieses Kapitels.

Zum Skripten stehen einige Ansätze zur Verfügung, die je nach Aufgabenstellung mal mehr mal weniger geeignet sind.

 

Beispiel

Vorteile

Nachteile

CodeBeispiel
(Anzeige aller Dienste auf PC1)

Powershell cmdlets 

Get-Help Service

Get-Service (Gsv)
Stop-Service (Spsv)
Start-Service (Sasv)
Suspend-Service
Resume-Service
Restart-Service
Set-Service
New-Service

Intuitives PowershellSkripting

OnlineHilfe

Nicht alle Diensteigenschaften verfügbar

 

Gsv -ComputerName PC1

.Net Klassen 

 MSDN:Servicecontroller

Vorteile wie Cmdlets

Methoden und Eigenschaften liegen in der MSDN

keine intuitive Syntax

$a=[ServiceProcess.ServiceController]
$a::GetServices("PC1")

WMI

 MSDN:Win32_Service

liefert die meisten Eigenschaften eines Dienstes

Besonders bei Remoteabfragen durch WQL flexible und resourcenschonende Programmierung möglich

Syntax etwas komplexer als Cmdlets

$Query = "Select * from Win32_Service"
Gwmi -query $Query -computer "PC1"

CommandlineTools

 Technet:SC.exe

weit verbreitetes CMD-Tool

läuft nicht in Powershell

SC  \\PC1 query

 

1 Umgang mit Get-Service und [ServiceProcess.ServiceController]

Die Cmdlets zur Verwaltung von Diensten sind recht einfach und verständlich.

 

Beispiel 1a: Alle installierten Dienste anzeigen (Get-Service/ .Net)

Set-StrictMode -Version "2.0"
Clear-Host
 
Get-Service -computername "." | Sort-Object Name -descending
#Gsv -computername "." | Sort Name -descending #Alias gsv und sort
#[ServiceProcess.ServiceController]::Getservices(".") | Sort status  #.Net
#Ausgabe gekürzt
  
Status   Name               DisplayName
------   ----               -----------
Running  RasMan             RAS-Verbindungsverwaltung
Running  RemotePersonali... Remote Personalisation
...
Stopped  Browser            Computerbrowser
Stopped  MSIServer          Windows Installer

Technet: Get-Service

Msdn: ServiceController - Class


Beispiel 1b: Alle installierten Dienste anzeigen (WMIKlasse: Win32_Service)

Set-StrictMode -Version "2.0"
Clear-Host

$Computername = "."
 
$Query = "Select * from Win32_Service"
Get-WmiObject -query $Query -computername $ComputerName | Sort-Object State `

    | Ft State,Name,Caption -auto

#Ausgabe gekürzt

state   name                           caption
-----   ----                           -------
Running RasMan                         RAS-Verbindungsverwaltung
Running RemotePersonalisation          Remote Personalisation
....
Stopped SysmonLog                      Leistungsdatenprotokolle und Warnungen
Stopped ClipSrv                        Ablagemappe

Wie man sieht, sind die Eigenschaftsnamen der .Net-Klasse und der Win32-Klasse nicht gleich. Die Win32-Klasse hat vor allem den Vorteil, dass die sie auch anzeigt, unter welchem Account ein Service läuft (Property: Startname)
The Scripting Wife: The Scripting Wife Uses PowerShell to Find Service Accounts

 

2 Monitoring von Services

Beispiel 1: Monitoring und automatischer Neustart von Diensten

Set-StrictMode -Version "2.0"
Clear-Host

$ServiceName="LENOVO.CAMMUTE"      #mein Beispiel Dienst
$TimeSpan = New-Object System.TimeSpan(0,2,0) # gibt in Stunden,Minuten,Sekunden an, wie oft der Monitor läuft

$Scope = New-Object System.Management.ManagementScope("\\.\root\cimV2") #. steht für den lokalen Rechner
$Query = New-Object System.Management.WQLEventQuery `
    ("__InstanceModificationEvent",$timespan, "TargetInstance ISA 'Win32_service'" )
$Watcher = New-Object System.Management.ManagementEventWatcher($Scope,$Query)

Do
    {
        $b = $watcher.WaitForNextEvent()
       
        $b.TargetInstance.Name
        $b.TargetInstance.caption     # Eigenschaftenn der WMI-Klasse: win32_service
        If ($(Get-Service -name $ServiceName).Status -eq "Stopped") {
          Start-Service -name $serviceName -force
          # emailfunktionalität + Protokollierung muß noch hinzugefügt werden
        }
     }
While ($b -ne 1) 

Dieses Skript bietet eine einfache und günstige Möglichkeit ein paar Dienste zu überwachen.

Beschreibung:
Das Skript läuft nach dem Start endlos und monitored alle 2 Minuten, ob die Dienste modifiziert wurden. Falls ja und der Dienst "LENOVO.CAMMUTE" auf "Stopped" steht, wird dieser Dienst neu gestartet.
Über $timespan und $servicename kann man diese beiden Parameter natürlich anpassen.

Erklärung: Das Herzstück des Skripts ist die Überwachung der Win32_service - Klasse mit  MSDN: __InstanceModificationEvent Class und dem ISA-Operator. Näher erklärt habe ich diese Technik im Kapitel WMI -> 2.5 Eventqueries

Beispiel 2: Abfragen mehrerer Dienste auf mehreren Rechnern

In diesem Beispiel frage ich den Zustand mehrerer Dienste auf mehreren Rechnern ab. Sowohl die Dienste wie auch die Computernamen sind in ini-Dateien abgelegt (Services.ini / ComputerNames.ini). Um das Beispiel praxisnah zu gestalten, stehen in der Services.ini die Dienste

  • kdc
  • DNS
  • NTDS
  • ADWS      (ab 2008R2)
  • Netlogon

Wie die AD Fachleute erkennen werden, sind das die lebensnotwendigen Dienste eines Windows2008R2 - DomainControllers und sollten daher regelmäßig überwacht werden.

Die beiden Rechner in meiner "ComputerNames.Ini" sind demzufolge Namen oder IPs von DCs (ein 2008 und ein 2008R2).

Set-StrictMode -Version "2.0"
Clear-Host

#Read Automatic Variables
  Try{
    $ThisScriptPath = Split-Path $($MyInvocation.InvocationName) -Parent #hier liegt das Skripts selbst
   }Catch{
     Write-Host "Skript bitte zuerst abspeichern!" -BackGroundColor Red
   Break
   }

#Read ini-file with ServiceNames
   $InputPath = "$ThisScriptPath\Services.ini "
   $Services =@()
   $(Get-Content $InputPath) | foreach{
   #Kommentare in der ini-Datei die mit # oder $null oder " " beginnen ignorieren
   if(($_[0] -ne "#") -and ($_[0] -ne $Null) -and ($_[0] -ne " ")) {
     $Services += $_
     }#if
   }#foreach
 
#Read ini-file with ComputerNames
 $InputPath = "$ThisScriptPath\Computers.ini "
   $ComputerNames =@()
   $(Get-Content $InputPath) | foreach{
   #Kommentare in der ini-Datei die mit # oder $null oder " " beginnen ignorieren
   if(($_[0] -ne "#") -and ($_[0] -ne $Null) -and ($_[0] -ne " ")) {
     $ComputerNames += $_
     }#if
   }#foreach
 
#Create TableHeader
$Header =  "{0,-15} {1,-18} {2,-26} {3,-10} {4} " -f "SystemName","ServiceName","Account","Started","Status"
Write-Host $Header -BackgroundColor DarkYellow

ForEach($ComputerName in $ComputerNames){
  ForEach($Service in $Services){
   $Query = "Select * from Win32_Service where Name = '$Service'"
   Get-Service -Name $Service -ComputerName $ComputerName -EA 0| Out-Null
   
   If($?){
      $WMIService = Get-WmiObject -Query $Query -ComputerName $ComputerName
       "{0,-15} {1,-18} {2,-26} {3,-10} {4} " -f `
         $WMIService.SystemName,$WMIService.Name,$WMIService.StartName,`
         $WMIService.Started,$WMIService.Status     
   }Else{
     Write-Host  "$($WMIService.SystemName):   $Service is not available" -BackGroundColor DarkRed
   } #If($?)/else
 }#ForEach($Service in $Services)
 "" #Leerzeile in der Ausgabe
}#ForEach($ComputerName in $ComputerNames)
#mögliche Ausgabe

SystemName      ServiceName        Account                    Started    Status
SERVER01        kdc                LocalSystem                True       OK
SERVER01        DNS                LocalSystem                True       OK
SERVER01        NTDS               LocalSystem                True       OK
SERVER01: ADWS is not available
SERVER01        Netlogon           LocalSystem                True       OK
SERVER01        LanmanWorkstation  NT AUTHORITY\LocalService  True       OK

SERVER02        kdc                LocalSystem                True       OK
SERVER02        DNS                LocalSystem                True       OK
SERVER02        NTDS               LocalSystem                True       OK
SERVER02:       ADWS               LocalSystem                True       OK
SERVER02        Netlogon           LocalSystem                True       OK
SERVER02        LanmanWorkstation  NT AUTHORITY\LocalService  True       OK

 Kap2 DCMonitoring.ps1.zip.txt

Der erste Teil des Skriptes besteht darin, die beiden Ini-Dateien auszulesen. Einträge die mit Leerzeichen oder Hash(#) beginnen werden ignoriert. Ich benutze das cmdlet "Get-Service", um festzustellen, ob der Dienst auf dem Rechner überhaupt existiert. Falls es hier keinen Fehler gibt ( If($?) -eq "True" ), schicke ich eine WMIAbfrage "Select * From ..." an den Rechner. Um die Performance zu steigern, könnte man anstelle des Sterns die gewünschten Properties eintragen.

 

2 einen neuen Dienst erstellen

Technet: New-Service

Mit New-Service ist ein leichtes einen neuen Dienst zu installieren, wenn man eine passende exe-Datei zu Verfügung hat.

Beispiel 1: Installation eines Dienstes

New-Service -name Telnet2 -binarypath C:\WINDOWS\system32\tlntsvr.exe -Displayname Telnet2 -description "Testsservice"
#Ausgabe

Status   Name               DisplayName
------   ----               -----------
Stopped  telnet2            telnet2

Den binarypath des Telnetservices habe ich mir in der services.msc imTelnetservice besorgt und hier einfach zu Demonstrationszwecken des new-service verwendet.

Die eigentliche Herausforderung liegt in der Programmierung einer geeigneten exe. Für Visualbasic habe ich dazu diese Anleitung gefunden

THE CODE PROJECT: Developing Windows Services using Visual Studio .NET Explained (insbesondere der zweite Teil!)

MSDN: System.ServiceProcess-Namespace

Mit dem Assistenten in VisualStudio ist die Aufgabe eine Exe als Dienst zu programmieren auch wieder nicht allzu schwierig. Im nächsten Kapitel gibts ein konkretes Beispiel dazu.

 

2.1 Einen Dienst in VisualBasic programmieren

In diesem Kapitel zeige ich, wie man einen Dienst unter VisualStudio/ VisualBasic  programmieren kann.
Wir programmieren einen Dienst, der beim Start sämtliche Treiber samt Treiberversion ins Windows Eventlog schreibt. Wenn plötzliche Treiberprobleme auftauchen, sind diese Events eine guter Einstiegspunkt zum Troubleshooting des Systems.

Die folgenden Screenshots habe ich unter VisulaStudio 2010 erstellt. Mit VisualStudio 2008 funktioniert eine Diensterstellung aber sehr ähnlich.

Der VBCode (2010) sieht so aus  (Der Projekttyp lautet "Windows Dienst" !)

'Unter Verweise bitte einen Verweis auf System.Management einrichten
Imports System.Management

Module Module1
    '
    Sub drivers()
        Dim objQuery As New ObjectQuery("SELECT * FROM Win32_systemdriver")
        Dim searcher As New ManagementObjectSearcher(objQuery)

        Dim myLog As New EventLog()
        myLog.Source = "Drivers"

        Dim eventoutput As String = "by blub@mcseboard.de" & vbCrLf
        eventoutput += "Alle mit win32_systemdriver erfassten Treiber am " & Now & ":" & vbCrLf & vbCrLf
        For Each member As ManagementObject In searcher.Get
            Dim DName As String = member("Displayname")
            Dim DPath As String = member("pathname")
            Dim Dversion As String = ""
            Dim DDescription As String = ""
            Dim DCompanyName As String = ""
            Dim tmp1 As String = getDriverinfo(DPath, Dversion, DDescription, DCompanyName)
            Try
                Dim output As String = DName & " - " & Dversion & vbCrLf '& ";" & DCompanyName & ";" & DPath
                eventoutput += output
            Catch
            End Try
        Next
        Write2Log(eventoutput, myLog)

    End Sub


    Function getDriverinfo(ByVal funDpath As String, ByRef funDriverversion As String, _
                           ByRef funFileDescription As String, ByRef funCompanyName As String)
        getDriverinfo = ""
        Try
            Dim funFileVersionInfo As FileVersionInfo = FileVersionInfo.GetVersionInfo(funDpath)
            funFileDescription = funFileVersionInfo.FileDescription
            funDriverversion = funFileVersionInfo.FileVersion
            funCompanyName = funFileVersionInfo.CompanyName
        Catch
        End Try
    End Function

    Public Sub Write2Log(ByVal SubOutput As String, ByRef subMyLog As EventLog)
        subMyLog.WriteEntry(SubOutput, EventLogEntryType.Information, 4711)
    End Sub
End Module


Public Class mydrivers02

    Protected Overrides Sub OnStart(ByVal args() As String)
        ' Code zum Starten des Dienstes hier einfügen. Diese Methode sollte Vorgänge
        ' ausführen, damit der Dienst gestartet werden kann.
        drivers()

    End Sub

    Protected Overrides Sub OnStop()
        ' Hier Code zum Ausführen erforderlicher Löschvorgänge zum Beenden des Dienstes einfügen.
        EventLog.WriteEntry("getDrivers Stopped at " & Now)
    End Sub

End Class

Download der .exe drivers2eventlog.zip.txt

mit diesem Powershellskript wird der zugehörige Dienst angelegt

Set-StrictMode -Version "2.0"
Clear-Host

$Path=  "C:\Programme\Drivers2Eventlog\drivers2Eventlog.exe"
$Name = "Drivers2Log"
$Description = "erzeugt im Anwendungslog ein Event mit allen Treibern"
New-Service -name $Name -binarypath $Path -Displayname $Name -description $Description"
#Ausgabe

Status   Name               DisplayName                          
------   ----               -----------                          
Stopped  Drivers2Log        Drivers2Log

In Kapitel WMI - 4.1 weitere Praxisbeispiele -> Filesystem habe ich im Beispiel 2 die Erfassung der Systemtreiber nur mit Powershell programmiert.