18. Mär 2024
Lesedauer 6 Min.
Systemdiagnose
ListView für die Registry, Teil 9
Das Steuerelement RegEditCtl wird durch Funktionen zur Systemdiagnose ergänzt.

Diese Serie zeigt, wie Sie Kontrollelemente erzeugen, die sich auf die Bearbeitung der Systemregistrierung von Windows verstehen. Die Grundfunktionen sind dabei in den vergangenen Teilen der Serie entstanden. In dieser Folge kommen noch Funktionen zur Systemdiagnose hinzu.Hierzu gehört die Methode ShowLastSystemDiagnosticReport. Sie stellt zwei optionale Parameter bereit. GenerateReport bestimmt, ob ein Bericht anzulegen ist oder nicht. Standardmäßig ist der Wert dieses Parameters auf False gesetzt. Die Berichtsanlage wird dementsprechend standardmäßig unterbunden.Über den zweiten Parameter kann ein Berichtsname ohne Pfadangabe übergeben werden. Der Pfad der Berichte ist
mit PerfLogs\System\Diagnostics\ vom Windows-System fest vorgegeben und befindet sich auf dem Systemlaufwerk, das hier über die benutzerdefinierte Funktion GetSystemDrive ermittelt wird.In der Methode wird zunächst der Berichtspfad in die Variable reportPath übernommen, um dann das Unterverzeichnis für den eigentlichen Bericht zu bestimmen und in die Variable reportSubPath zu übernehmen. Das Unterverzeichnis ist eine Kombination aus Maschinenname (Environment.MachineName) und Datumsinformationen (Jahr, Monat und Tag), die zu einer Zeichenkette verbunden werden (Listing 1). Der Pfadname endet mit einem Bindestrich und wird später mit einer fortlaufenden Tagesnummer verkettet, die über die benutzerdefinierte Funktion GetHighestReportNumberOf-ThisDay ermittelt wird.
mit PerfLogs\System\Diagnostics\ vom Windows-System fest vorgegeben und befindet sich auf dem Systemlaufwerk, das hier über die benutzerdefinierte Funktion GetSystemDrive ermittelt wird.In der Methode wird zunächst der Berichtspfad in die Variable reportPath übernommen, um dann das Unterverzeichnis für den eigentlichen Bericht zu bestimmen und in die Variable reportSubPath zu übernehmen. Das Unterverzeichnis ist eine Kombination aus Maschinenname (Environment.MachineName) und Datumsinformationen (Jahr, Monat und Tag), die zu einer Zeichenkette verbunden werden (Listing 1). Der Pfadname endet mit einem Bindestrich und wird später mit einer fortlaufenden Tagesnummer verkettet, die über die benutzerdefinierte Funktion GetHighestReportNumberOf-ThisDay ermittelt wird.
Listing 1: Systemdiagnosebericht anlegen und anzeigen
<span class="hljs-keyword">Sub</span> ShowLastSystemDiagnosticReport(<br/> <span class="hljs-keyword">Optional</span> GenerateReport <span class="hljs-keyword">As</span> <span class="hljs-built_in">Boolean</span> = <span class="hljs-literal">False</span>, <br/> <span class="hljs-keyword">Optional</span> ReportName <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span> = <span class="hljs-string">""</span>)<br/> <span class="hljs-keyword">Dim</span> reportPath <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span> = GetSystemDrive() &amp; <br/> <span class="hljs-string">"PerfLogs\System\Diagnostics\"</span><br/> <span class="hljs-keyword">Dim</span> reportSubPath <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span> = <br/> Environment.MachineName &amp; <span class="hljs-string">"_"</span> &amp; <br/> Now.Year.ToString &amp; Now.Month.ToString(<span class="hljs-string">"00"</span>) &amp; <br/> Now.Day.ToString(<span class="hljs-string">"00"</span>) &amp; <span class="hljs-string">"-"</span><br/> <span class="hljs-keyword">If</span> GenerateReport <span class="hljs-keyword">Then</span><br/> GenerateReportMessageDialog(ReportName)<br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">If</span><br/> <span class="hljs-keyword">Dim</span> CheckPath <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span> = reportPath &amp; <br/> reportSubPath &amp; <br/> GetHighestReportNumberOfThisDay().ToString(<span class="hljs-string">"000000"</span>)<br/> <span class="hljs-keyword">Dim</span> ReportURL <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span> = CheckPath &amp; <span class="hljs-string">"\report.html"</span><br/> <span class="hljs-keyword">If</span> File.Exists(ReportURL) <span class="hljs-keyword">Then</span><br/> <span class="hljs-keyword">Dim</span> frm <span class="hljs-keyword">As</span> <span class="hljs-keyword">New</span> frmBrowser<br/> frm.Show()<br/> frm.BrowserCtl.NavigateTo(<span class="hljs-string">"file:///"</span> &amp; ReportURL)<br/> <span class="hljs-keyword">Else</span><br/> NewMessageDialog(<span class="hljs-string">"Letzten heutigen Bericht zur "</span> &amp;<br/> <span class="hljs-string">"Systemdiagnose anzeigen"</span>, <br/> <span class="hljs-string">"Die Berichtsdatei "</span> &amp; ReportURL &amp; <span class="hljs-string">" wurde nicht"</span> &amp;<br/> <span class="hljs-string">" gefunden!"</span>, eIcons.<span class="hljs-keyword">Error</span>, <br/> eButtons.OK, <span class="hljs-string">"Systemdiagnose"</span>)<br/> <span class="hljs-keyword">End</span> If<br/>End <span class="hljs-keyword">Sub</span>
Ist die Reportanlage erwünscht (GenerateReport = True), blendet die Methode mit GenerateReportMessageDialog zunächst einen Dialog mit Fortschrittsanzeige ein. Die aufwendige Pfadanlage und -verwaltung ist erforderlich, da der Name der Berichtsdatei immer (!) report.html lautet. Die Unterverzeichnisse erlauben die Anlage und Verwaltung mehrerer Berichte mit einheitlichem Dateinamen.Für die Neuanlage von Berichten ist jeweils die höchste Berichtsnummer des Tages zu berücksichtigen. Die Berichtsanlage dauert zwischen zehn und 20 Sekunden.Pfad und Dateiname werden in der Variablen ReportURL verkettet, anschließend wird die Datei auf Existenz geprüft. Ist die Datei vorhanden, wird Sie im Dialog des Typs frmBrowser im HTML-Format angezeigt.Das Formular frmBrowser beinhaltet ein Webbrowser-Steuerelement BrowserCtl. Über die zugehörige NavigateTo-Anweisung wird dann zur lokalen HTML-Berichtsdatei navigiert und einhergehend damit der zugehörige Inhalt angezeigt (Bild 1). Existiert die ermittelte Berichtsdatei nicht, wird dies über die Methode NewMessageDialog in einem Meldungsdialog angezeigt.

Für die Ausgabe des Systemdiagnoseberichts kommt das benutzerdefinierte Webbrowser-Steuerelement BrowserCtl der Steuerelementbibliothek ExtendedControlsLib zum Einsatz (Bild 1)
Autor
Die Funktion GetSystemDrive ermittelt über das Windows-Systemverzeichnis WinSysDir das Systemlaufwerk und liefert es als Zeichenkette zurück.
Function GetSystemDrive() As String
Return WinSysDir.Substring(0, 3)
End Function
Wie zuvor beschrieben werden Protokollverzeichnisse datumsbasiert im Verbund mit einer fortlaufenden tagesspezifischen Berichtsnummer benannt. Die zu verwendende höchste Berichtsnummer des aktuellen Tages ermittelt die Funktion GetHighestReportNumberOfThisDay. Diese liefert den numerischen Wert als Ganzzahl zurück.Zunächst wird das Berichtsverzeichnis reportPath und das tagesspezifische Unterverzeichnis reportSubPath bestimmt, um dann die Berichtsnummern schrittweise hochzuzählen. Die maximale Nummer ist mit 999 999 vorgegeben und wird per For-Next-Schleife schrittweise hochgezählt. Zur aktuell verarbeiteten Nummer wird der zugehörige Verzeichnisname generiert und in die Variable CheckPath übernommen.Existiert der Pfad, ist die höchste Berichtsnummer nicht erreicht, und der Zähler wird um den Wert eins erhöht. Existiert der Pfad aber nicht, ist die höchste Berichtsnummer ermittelt. Dann wird die For-Next-Schleife beendet und der höchste Wert zurückgegeben (Listing 2).
Listing 2: Höchste Berichtsnummer des aktuellen Tages ermitteln
Function GetHighestReportNumberOfThisDay() As Integer<br/> Dim NumberFound As Boolean = False<br/> Dim HighestNumber As Integer = 0<br/> Dim reportPath As String = GetSystemDrive() &amp; <br/> "PerfLogs\System\Diagnostics\"<br/> Dim reportSubPath As String = <br/> Environment.MachineName &amp; "_" &amp; <br/> Now.Year.ToString &amp; Now.Month.ToString("00") &amp; <br/> Now.Day.ToString("00") &amp; "-"<br/> Dim ReportExist As Boolean = False<br/> For x = 1 To 999999<br/> Dim CheckPath As String = <br/> reportPath &amp; reportSubPath &amp; x.ToString("000000")<br/> If Directory.Exists(CheckPath) Then<br/> NumberFound = True<br/> HighestNumber = x<br/> If Directory.Exists(reportPath &amp; reportSubPath &amp; <br/> (x + 1).ToString("000000")) = False Then <br/> Exit For<br/> End If<br/> Next<br/> Return HighestNumber <br/>End Function
Um die Berichtsanlage zu veranschaulichen, wird ein Dialog mit Fortschrittsanzeige angezeigt, der über das Formular frmGenerateReportMessageDialog definiert ist, mit der Methode GenerateReportMessageDialog angezeigt wird und den über den Parameter ReportName übergebenen Berichtsnamen ausgibt. Die Anzeige des Dialoges erfolgt nicht modal. Wird die Berichtsanlage beendet, wird auch der Dialog ausgeblendet.
Sub GenerateReportMessageDialog(ReportName)
Dim frm As New frmGenerateReportMessageDialog
frm.ReportFile = ReportName
frm.ShowDialog()
End Sub
Mit den Befehl StartSysMonitor starten Sie das Windows-Programm zur Leistungsüberwachung. Dazu übergeben Sie an das Programm perfmon den Parameter /sys.
Sub StartSysMonitor()
Shell("perfmon /sys",
AppWinStyle.NormalFocus)
End Sub
Windows liefert über die Systemsteuerung themenbasierte Lösungen an, die Sie über die Methode ResolveProblems und die darin angeführte Shell-Anweisung aufrufen. Daraufhin öffnet sich der zugehörige Dialog zu den Einstellungen zur Problembehandlung (Bild 2). Darin wählen Sie bestimmte Themenbereiche an und erhalten weiterführende Hinweise zur Problembehandlung (zum Beispiel Probleme beim Drucken, Lösen von Audioaufzeichnungsproblemen, Wartungsaufgaben durchführen).

Systemfunktionen für Problemlösungen per Systemsteuerung abrufen (Bild 2)
Autor
Sub ResolveProblems()
Shell("explorer shell:::" &
"{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}",
AppWinStyle.NormalFocus)
End Sub
Systeminterne Diagnose-Tools starten
Die Systemdiagnose-Tools werden über das Dienstprogramm msdt (Microsoft Support-Diagnosetool) bereitgestellt und auch im Verbund mit Supportmitarbeitern von Microsoft genutzt. Das Programm erlaubt die gezielte Anwahl von Themen. Jedem Thema ist dabei ein bestimmter Schlüssel zugeordnet, der später beim Programmaufruf als Kommandozeilenschalter übergebbar ist.Geben Sie keinen Schalter an, müssen Sie diesen Hauptschlüssel selbst eintippen. An dieser Stelle werden alle Themen mitsamt den Schlüsseln über den Enumerationsdatentyp DiagnosticTools zusammengefasst:
Enum DiagnosticTools
AeroDiagnostic
NetworkDiagnosticsDA
DeviceDiagnostic
HomeGroupDiagnostic
NetworkDiagnosticsInbound
NetworkDiagnosticsWeb
IEBrowseWebDiagnostic
IESecurityDiagnostic
...
End Enum
Um die Diagnose-Tools gezielt zu starten, kommt die Methode StartDiagnosticTool zum Einsatz, der Sie das Schlüsselwort für das zu verwendende Thema über den Parameter Tool übergeben. Über Optionalparameter geben Sie später an, ob mehr (MoreOptions), erweiterte (AdvancedStr) und eigene Optionen anzugeben sind (CustomStr) sowie abschließende Meldungen angezeigt werden sollen. Die Optionalparameter erfordern Zusatzschalter, die intern zwar bereits codiert sind, derzeit aber nicht genutzt werden. Dementsprechend wird der Befehlsaufruf des Programms msdt.exe immer nur mit einem Themenbezeichner verkettet, der selbst über den Schalter /id angegeben wird (Listing 3).
Listing 3: Befehlszuweisung für die Diagnose-Tools
Sub StartDiagnosticTool(ByVal Tool As DiagnosticTools, <br/> Optional MoreOptions As Boolean = True, <br/> Optional Advanced As Boolean = True,<br/> Optional Custom As Boolean = True, <br/> Optional ShowMessage As Boolean = True)<br/> Dim AdvancedStr As String = ""<br/> Dim CustomStr As String = ""<br/> Dim MoreOptionsStr As String = ""<br/> If MoreOptions Then <br/> MoreOptionsStr = "/moreoptions true"<br/> If Advanced Then AdvancedStr = " /advanced"<br/> If Custom Then CustomStr = " /custom"<br/> Dim MSDTcommand As String = ""<br/> Select Case Tool<br/> Case DiagnosticTools.AeroDiagnostic 'nur Windows 7<br/> MSDTcommand = "msdt.exe /id AeroDiagnostic"<br/> Case DiagnosticTools.NetworkDiagnosticsDA <br/> 'alle anderen Windows 8 und höher<br/> MSDTcommand = "msdt.exe /id NetworkDiagnosticsDA"<br/> Case DiagnosticTools.DeviceDiagnostic<br/> MSDTcommand = "msdt.exe /id DeviceDiagnostic"<br/> Case DiagnosticTools.HomeGroupDiagnostic<br/> MSDTcommand = "msdt.exe /id HomeGroupDiagnostic"<br/> Case DiagnosticTools.NetworkDiagnosticsInbound<br/> MSDTcommand = <br/> "msdt.exe /id NetworkDiagnosticsInbound"<br/> ...<br/> End Select<br/> ...<br/>End Sub
Der festgelegte Befehl für den Aufruf einer bestimmten
Diagnosefunktion wird mit der Methode StartDiagnosticTool realisiert (Listing 4) und per Shell-Anweisung im normalen Fenstermodus abgesetzt. Das Ergebnis wird über den Wert result zurückgegeben. Nur wenn der Parameter ShowMessage auf True gesetzt ist, wird der Dialog zur Meldungsausgabe angezeigt. Setzen Sie dShowMessage auf False, können Sie die abschließende Meldungsausgabe unterbinden.
Diagnosefunktion wird mit der Methode StartDiagnosticTool realisiert (Listing 4) und per Shell-Anweisung im normalen Fenstermodus abgesetzt. Das Ergebnis wird über den Wert result zurückgegeben. Nur wenn der Parameter ShowMessage auf True gesetzt ist, wird der Dialog zur Meldungsausgabe angezeigt. Setzen Sie dShowMessage auf False, können Sie die abschließende Meldungsausgabe unterbinden.
Listing 4: Diagnose-Tools ausführen
Sub StartDiagnosticTool(ByVal Tool As DiagnosticTools, <br/> Optional MoreOptions As Boolean = True, <br/> Optional Advanced As Boolean = True,<br/> Optional Custom As Boolean = True, <br/> Optional ShowMessage As Boolean = True)<br/> ...<br/> Dim result As Integer = Shell(MSDTcommand, <br/> AppWinStyle.NormalFocus)<br/> If ShowMessage Then<br/> Dim msg As String = "Die gewählte Komponente '" &amp; <br/> Tool.ToString &amp; "' wurde mit folgendem Ergebnis " &amp; <br/> "ausgeführt: " &amp; vbCrLf<br/> Select Case result<br/> Case -1<br/> msg += "Die Funktion zur Problemsuche und " &amp;<br/> "-lösung wurde vorzeitig beendet!"<br/> Case 0<br/> msg += "Die Funktion wurde erfolgreich " &amp;<br/> "abgeschlossen und es wurde mindestens " &amp;<br/> "ein Problem gefunden und behoben!"<br/> Case 1<br/> msg += "Die Funktion hat ein oder mehrere " &amp;<br/> Fehler gefunden, die nicht in der " &amp; <br/> "Gesamtheit behoben werden konnten!"<br/> Case 2<br/> msg += "Die Funktion hat im gewählten " &amp;<br/> "Funktionsbereich keine Probleme gefunden!"<br/> Case Else<br/> End Select<br/> If result = -1 Or result = 0 Or result = 1 Or <br/> result = 2 Then<br/> Dim msgdata As New MessageData<br/> With msgdata<br/> .Buttons = eShowButtons.OK<br/> .MainInstruction = "Probleme suchen und beheben" &amp; <br/> " [" &amp; Tool.ToString &amp; "]"<br/> .Icon = eIcons.Information<br/> .MessageContent = msg<br/> .WindowTitle = "Windows-Troubleshooting"<br/> End With<br/> NewMessageDialog(msgdata)<br/> End If<br/> End If<br/>End Sub
ZIP-Archiv zu Diagnoseberichten generieren
Sie können jeden Tag eine Vielzahl an Diagnoseberichten anlegen. Optional lassen sich die generierten Berichte auch in einem ZIP-Archiv sichern. Dafür kommt die benutzerdefinierte Methode GenerateDiagnosticsZIP zum Einsatz. Innerhalb der Routine wird zunächst das Diagnoseberichtsverzeichnis in der Variablen DataPath gesichert, um dann den ZIP-Archivdateinamen Diagnostics.zip zu setzen und in der Variablen ZipArchive zu sichern.Im Anschluss daran wird über den Dialog FolderDlg des Typs FolderBrowserDialog das Zielverzeichnis für dieses Archiv abgefragt. Nur wenn Sie einen Ordner in diesem Dialog auswählen und mit OK bestätigen, wird dieser in die Variable DestinationPath übernommen. Unterscheidet sich dieser Zielordner vom Datenverzeichnis DataPath, wird das ZIP-Archiv zum Datenverzeichnis generiert. Auftretende Fehler und die Archivanlage werden nach Bedarf in Meldungsdialogen angezeigt (Listing 5).Listing 5: Archiv mit ZIP-Archiven anlegen
Sub GenerateDiagnosticsZIP()<br/> Dim DataPath As String = GetSystemDrive() &amp; <br/> "PerfLogs\System\Diagnostics"<br/> Dim ZipArchive As String = ""<br/> Dim DestinationPath As String = ""<br/> ZipArchive = DataPath.Substring(<br/> DataPath.LastIndexOf("\") + 1) &amp; ".zip"<br/> Dim FolderDlg As New FolderBrowserDialog<br/> With FolderDlg<br/> .ShowNewFolderButton = True<br/> .Description = "Wählen Sie das Zielverzeichnis " &amp; <br/> "für das anzulegende Berichtsarchiv mit dem " &amp;<br/> "Namen '" &amp; ZipArchive &amp; "'aus, in den das " &amp;<br/> "Berichtsverzeichnis '" &amp; DataPath &amp; <br/> "' verpackt werden soll!"<br/> If .ShowDialog = DialogResult.OK And <br/> .SelectedPath &lt;&gt; "" Then<br/> DestinationPath = .SelectedPath<br/> Else<br/> Exit Sub<br/> End If<br/> End With<br/> If DestinationPath &lt;&gt; DataPath Then<br/> Try<br/> ZipFile.CreateFromDirectory(<br/> DataPath, DestinationPath &amp; "\" &amp; ZipArchive, <br/> CompressionLevel.Optimal, False)<br/> Catch ex As Exception<br/> NewMessageDialog("Berichtsarchiv anlegen", <br/> "Das Berichtsarchiv konnte nicht angelegt " &amp;<br/> "werden! " &amp; ex.Message, <br/> eIcons.SecurityShieldError, eButtons.OK, <br/> "Berichtsarchiv")<br/> Exit Sub<br/> End Try<br/> NewMessageDialog("Berichtsarchiv anlegen", <br/> "Das Berichtsarchiv wurde unter dem Namen '" &amp; <br/> DestinationPath &amp; "\" &amp; ZipArchive &amp; "' angelegt!", <br/> eIcons.SecurityShieldBlueBack, <br/> eButtons.OK, "Berichtsarchiv")<br/> Else<br/> NewMessageDialog("Berichtsarchiv anlegen", <br/> "Das Berichtsarchiv kann nicht im zu " &amp;<br/> "komprimierenden Datenverzeichnis abgelegt " &amp;<br/> "werden. Wählen Sie für das ZIP-Archiv ein " &amp;<br/> "anderes Zielverzeichnis aus!", <br/> eIcons.SecurityShieldError, eButtons.OK, <br/> "Berichtsarchiv")<br/> End If<br/>End Sub
Diagnoseberichte löschen
Um angelegte Diagnoseberichte in der Gesamtheit aus dem Datenverzeichnis zu löschen, nutzen Sie die Methode Kill-DiagnosticReports. Diese entfernt alle Unterverzeichnisse und Dateien. Gegebenenfalls sollten Sie, um die Daten später wiederherzustellen, vorab die zuvor beschriebene Funktion zur Anlage eines ZIP-Sicherungsverzeichnisses aufrufen. Bevor das Löschen erfolgt, wird zunächst eine entsprechende Sicherheitsabfrage eingeblendet. Bestätigen Sie diese mit OK, erfolgt das Löschen über die Methode Delete des DirectoryInfo-Objekts dInfo, das zum Pfad reportPath angelegt wurde. Das Löschen selbst wird durch eine abschließende Meldung angezeigt (Listing 6).
Listing 6: Diagnoseberichte löschen
Sub KillDiagnosticReports()<br/> Dim msg As New MessageData<br/> With msg<br/> .Buttons = eShowButtons.YesNo<br/> .Icon = eIcons.SecurityShieldBlueBack<br/> .MainInstruction = "Diagnoseberichte löschen"<br/> .MessageContent = "Wollen Sie jetzt alle " &amp;<br/> "vorhandenen Diagnoseberichte endgültig " &amp;<br/> "löschen?" &amp; "Legen Sie sich gegebenenfalls " &amp;<br/> "für spätere Berichtszugriffe ein " &amp;<br/> "Sicherungsarchiv an!" <br/> .WindowTitle = "Windows-System-Diagnoseberichte"<br/> End With<br/> If NewMessageDialog(msg) = eButtons.YES Then<br/> Dim reportPath As String = GetSystemDrive() &amp; <br/> "PerfLogs\System\Diagnostics"<br/> Dim dInfo As New DirectoryInfo(reportPath)<br/> With dInfo<br/> For Each dInfoObj In dInfo.EnumerateDirectories<br/> dInfoObj.Delete(True)<br/> Next<br/> NewMessageDialog("Alle Systemdiagnoseberichte " &amp;<br/> "löschen", "Aus dem Berichtsverzeichnis " &amp;<br/> "wurden alle Systemdiagnosberichte gelöscht!", <br/> eIcons.SecurityShieldBlueBack, eButtons.OK, <br/> "Berichtsarchiv")<br/> End With<br/> End If<br/>End Sub
Fazit
Damit sind die Funktionen zur Systemdiagnose, Leistungsüberwachung, Fehlerkennung und Fehlerbehebung codiert. Bleibt noch der Quelltext für das eigentliche RegEditCtl-Steuerelement zu ergänzen. Doch das ist eine Thematik, die in den abschließenden Teilen dieser Serie behandelt wird.Fussnoten
- Andreas Maslo, Controls für die Registry, dotnetpro 2/2023, Seite 122 ff., http://www.dotnetpro.de/A2302Registry