Es gibt Tage da freut es einen mehr als sonst, dass man ein Softwareentwickler ist. Neulich beispielsweise war so ein Tag an denen man eben mal schnell ein Problem lösen kann und sich damit das Leben – zumindest ein wenig – erleichtern kann.
Mein heutiges Problem war folgendes. Ich bin häufig mit meinem Notebook unterwegs und dann auch auf lange Akkulaufzeiten angewiesen. Nun habe ich aber festgestellt, dass es manche Programme rein gar nicht stört wenn man im Akkubetrieb ist und diese fröhlich weiterlaufen ohne eine Chance zu haben, das per Settings zu ändern.
Mein heutiger Kandidat war Microsoft Windows Search. An sich ein gutes Programm was viel (Such-)Arbeit ersparen kann. Aber eben auch ein Programm welches ziemlich regelmäßig die Festplatte indiziert und damit Strom verbraucht. Wenn man unterwegs ist, ist das meines Erachtens völlig unnötig, eine Indizierung kann durchaus auch nur dann stattfinden, wenn man an der Steckdose hängt.
Nun gibt es zugegebenermaßen die Option die Indizierung anzuhalten, was aber den Nachteil hat, dass man das immer wieder machen muss, wenn man die Steckdose verlässt. Vergisst man das mal, läuft die Indizierung wieder. Also musste eine andere Lösung her und mein Quick and Dirty-Attempt führte mich zu folgendem Algorithmus:
1. Prüfe alle x Sekunden ob Notebook im Akkubetrieb ist oder nicht
2. Wenn in Akkubetrieb, dann Stoppe den Windows Search Indexing Dienst
3. Wenn in Netzbetrieb, dann Starte den Windows Search Indexing Dienst, sofern er nicht schon läuft
Da das ganze im Hintergrund laufen soll, sollte das Programm seinerseits ein Dienst werden. Obendrauf sollte noch eine kleine Anwendung für die Tray um die Einstellungen für den Dienst zu verändern.
Der Service selber steuert sich über einen Timer in Form eines Delegates. Das Abfrageintervall (Polling) sowie alle anderen Einstellungen werden in der Registry abgelegt. das geht flott und einfach mit Bordmitteln des .net-Frameworks und bedarf hier nur einem einzelnen Auftruf.
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.
MainStart()
End Sub
Private Sub MainStart()
SetPollInterval()
InitTimer()
End Sub
Private Sub SetPollInterval()
'Setze einen Standardwert (Klassen-Konstante)
_iPollInterval = POLL_INTERVAL_DEFAULT
Try
' hier wird auf die Registry zugegriffen und aus einem
' speziellen Bereich die Einstellungen geholt.
' In dem Fall alles durch Klassen-Konstanten definiert.
_iPollInterval = GetSetting(_SETTINGS_APPLICATION, _SETTINGS_SECTION, "PollInterval", POLL_INTERVAL_DEFAULT.ToString)
Catch ex As Exception
' Im fall von einem Zugriffsfehler, schreibe in EventLog
WriteEventLog("[WiSIM.SetPollInterval] Could not access Registry Settings. Using default values. " & ex.Message, 3)
_iPollInterval = POLL_INTERVAL_DEFAULT
End Try
End Sub
#Region "Timer"
Private Sub InitTimer()
Try
' Timer in Form eines Delegates erstellen.
KeepAliveDelegate = New TimerCallback(AddressOf ControlTimer)
KeepAliveTimer = New Timer(KeepAliveDelegate, Nothing, New TimeSpan(0), New TimeSpan(0, 0, _iPollInterval))
Catch ex As Exception
'couldn't init Delegates
WriteEventLog("[WiSIM.InitTimer] " & ex.Message, 3)
End Try
End Sub
' Hier die Methode, die bei Ablauf eines Timerintervalls aufgerufen
' wird. Hier spielt sich also alles ab und ist sozusagen die
' Main()-Methode des Dienstes
Private Sub ControlTimer(ByVal State As Object)
Try
SetPollInterval()
' Wir prüfen noch ein wenig ob nicht noch ein früherer
' Durchlauf aktiv ist. Eine Karenz von 2 Sekunden sollte
' in dem Fall dafür reichen
If LastPolling.AddSeconds(_iPollInterval - 2) < Now Then
LastPolling = Now
_bIsRunning = True
Try
' Wieder Zugriff auf die Registry.
_bIsRunning = CType(GetSetting(_SETTINGS_APPLICATION, _SETTINGS_SECTION, "IsRunning", True), Boolean)
Catch ex As Exception
WriteEventLog("[WiSIM.ControlTimer] Could not access Registry Settings. Using default values. " & ex.Message, 3)
_bIsRunning = True
End Try
If _bIsRunning Then
WriteEventLog("WiSIM is enabled.", 1)
' Hier der Aufruf um die eigentliche Funktionalität
' zu starten. Also die Prüfung des Energiestatus etc.
Process()
Else
WriteEventLog("WiSIM was disabled.", 1)
End If
Else
WriteEventLog("[WiSIM.ControlTimer] Thread tries to run wild. Attempt caught.", 2)
End If
Catch ex As Exception
WriteEventLog("[WiSIM.ControlTimer] " & ex.Message, 3)
End Try
End Sub
#End Region
So, nun müssen wir also den Power Status von unserem Computer rausfinden. Dafür bedienen wir uns einem Kernel32.dll-Aufruf
Private Declare Auto Function GetSystemPowerStatus Lib "kernel32.dll" (ByRef lpSystemPowerStatus As SYSTEM_POWER_STATUS) As Integer
darüber hinaus nehmen wir noch ein paar Definitionen mit dazu, um das ganze ein wenig strukturierter werden zu lassen. Die Sektion erklärt sich wohl von selber:
#Region "Structures and Enumerations"
Public Structure SYSTEM_POWER_STATUS
Public ACLineStatus As ACLineStatus
Public BatteryFlag As BatteryFlag
Public BatteryLifePercent As Byte
Public Reserved1 As Byte
Public BatteryLifeTime As Integer
Public BatteryFullLifeTime As Integer
End Structure
Public Enum BatteryFlag As Byte
High = 1
Low = 2
Critical = 4
Charging = 8
NoSystemBattery = 128
Unknown = 255
End Enum
Public Enum ACLineStatus As Byte
Offline = 0
Online = 1
Unknown = 255
End Enum
Public Enum ServiceRunCommand As Byte
StartCommand = 1
StopCommand = 2
End Enum
#End Region
Nun haben wir im Prinzip alles was wir brauchen. Mit GetPowerStatus() können wir den Status unserer Energieversorgung abfragen und später komfortabel abfragen.
Private Sub Process()
Dim oPowerState As SYSTEM_POWER_STATUS
oPowerState = New SYSTEM_POWER_STATUS
oPowerState = GetPowerStatus()
If IsNothing(oPowerState) Then
WriteEventLog("[WiSIM.Process] Could not decide wether to Stop or Start Windows Search Service due to unknown AC Status. Step was skipped.", 2)
Exit Sub
End If
sEventLogEntry = ""
bWriteSuccessEntry = False
If oPowerState.ACLineStatus = ACLineStatus.Online Then
'start
WriteEventLog("[WiSIM.Process] ACLineStatus is ONLINE", 1)
ControlIndexingService(ServiceRunCommand.StartCommand)
Else
'stop
WriteEventLog("[WiSIM.Process] ACLineStatus is OFFLINE", 1)
ControlIndexingService(ServiceRunCommand.StopCommand)
End If
If bWriteSuccessEntry And Len(sEventLogEntry) > 0 Then
WriteEventLog(sEventLogEntry, 1)
End If
End Sub
Die Funktion “GetPowerStatus” besteht seinerseits nur aus einem Aufruf der oben definierten Kernel-Funktion:
Public Function GetPowerStatus() As SYSTEM_POWER_STATUS
Dim oSystemPowerStatus As SYSTEM_POWER_STATUS
oSystemPowerStatus = New SYSTEM_POWER_STATUS
Try
GetSystemPowerStatus(oSystemPowerStatus)
Catch ex As Exception
oSystemPowerStatus = Nothing
WriteEventLog("[WiSIM.GetPowerStatus] " & ex.Message, 3)
End Try
Return oSystemPowerStatus
End Function
Nun müssen wir nur noch abfragen ob der Windows Search Indexer aktiv ist oder nicht und darauf reagieren. Hierzu stellt .net das ServiceController-Objekt (Namespace System.ServiceProcess) zur Verfügung, das alle Funktionen mitbringt, die wir brauchen. Hier fragen wir einfach über den Dienstnamen (der heißt bei Windows Indexing Service „WSearch“) den Index-Service ab. Das Argument „RunCommand“ gibt an, ob der Service den Start, Stop oder den Pause-Befehl erhalten soll. Entsprechend wird darauf reagiert und der Windows-Dienst gesteuert.
Public Function ControlIndexingService(ByVal RunCommand As ServiceRunCommand) As Boolean
Dim oServiceController As ServiceController
Dim bReturn As Boolean
bReturn = True
oServiceController = New ServiceController(_INDEXING_SERVICE_NAME, System.Environment.MachineName)
oServiceController.Refresh()
bWriteSuccessEntry = False
Try
Select Case RunCommand
Case ServiceRunCommand.StartCommand
'is already running ?
bWriteSuccessEntry = True
sEventLogEntry = "Windows Search is already running."
If oServiceController.Status <> ServiceControllerStatus.Running Then
oServiceController.Start()
sEventLogEntry = "Windows Search is starting."
End If
Case ServiceRunCommand.StopCommand
If oServiceController.CanStop Then
oServiceController.Stop()
bWriteSuccessEntry = True
sEventLogEntry = "Windows Search is stopping."
End If
End Select
Catch ex As Exception
WriteEventLog("[WiSIM.ControlIndexingService] " & ex.Message, 3)
bReturn = False
End Try
Return bReturn
End Function
Das ist auch schon alles. Wie gesagt, es ist ein Quick-and-Dirty-Ansatz der ein wenig im Powercoding-verfahren (zu deutsch: alles schnell, schnell) entstanden ist, der aber sein Dienst klagenfrei verrichtet und mich wieder ein wenig fauler gemacht hat...
Wie oben schon erwähnt, habe ich es mir nicht nehmen lassen, diesen Service um ein Settings-Progrämmchen zu erweitern. Das stelle ich dann in den kommenden Tagen vor.