13. Nov 2023
Lesedauer 5 Min.
MRU-Programme ermitteln
Ein eigenes Steuerelement für Verlaufsdaten, Teil 7
In der Windows-Registry gespeicherte Dokumente und Programme auslesen.

Wie man die in der Registry binär abgelegten Infos zu MRU-Dokumenten (Most Recently Used) ausliest, haben Sie in der vorangegangenen Folge dieser Serie [1] erfahren. Allerdings werden nicht alle Einträge, die sich im Registrierschlüssel befinden, auch in der MRUListEx-Zusammenstellung aufgeführt. Aus diesem Grund wird nun die Funktion GetNonMRUListExElements definiert, die auch die noch nicht berücksichtigten Einträge ermittelt. Auch diese Funktion liefert die Kennungen der Elemente über ein Ganzzahldatenfeld zurück. Entsprechend der Funktion GetMRUListEx werden die Elemente über Indexwerte zusammengestellt. Dafür werden in GetNonMRUListExElements die Elemente der MRUListEx-Zusammenstellung ausgewertet, um im Anschluss daran alle im Registrierschlüssel vorhandenen Schlüsselnamen (RecentDocs_UserKey.GetValueNames) über eine For-Each-Schleife zu durchlaufen. Ob ein Name ein Element der MRUListEx-Aufstellung ist, prüft die Funktion IsMRUListExElement. Nur wenn dies nicht der Fall ist, wird das Element dem Datenfeld Elements hinzugefügt. Das Ergebnis wird an das aufrufende Programm zurückgegeben, vergleiche Listing 1.
Listing 1: Weitere Einträge auslesen
<span class="hljs-keyword">Function</span> GetNonMRUListExElements(<br/> <span class="hljs-keyword">ByVal</span> CurrentUserSubKey <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>) <span class="hljs-keyword">As</span> <span class="hljs-built_in">Integer</span>()<br/> <span class="hljs-keyword">Dim</span> Counter <span class="hljs-keyword">As</span> <span class="hljs-built_in">Integer</span> = <span class="hljs-number">-1</span><br/> <span class="hljs-keyword">Dim</span> Elements <span class="hljs-keyword">As</span> <span class="hljs-built_in">Integer</span>() = <span class="hljs-literal">Nothing</span><br/> <span class="hljs-keyword">Dim</span> RecentDocs_UserKey <span class="hljs-keyword">As</span> RegistryKey = <br/> CurrentUser.OpenSubKey(CurrentUserSubKey, <span class="hljs-literal">True</span>)<br/> <span class="hljs-keyword">Dim</span> SortedElements <span class="hljs-keyword">As</span> <span class="hljs-built_in">Integer</span>() = <br/> GetMRUListEx(CurrentUserSubKey)<br/> <span class="hljs-keyword">For</span> <span class="hljs-keyword">Each</span> ElementName <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span> _<br/> <span class="hljs-keyword">In</span> RecentDocs_UserKey.GetValueNames<br/> <span class="hljs-keyword">If</span> ElementName &lt;&gt; <span class="hljs-string">"MRUListEx"</span> <span class="hljs-keyword">Then</span><br/> <span class="hljs-keyword">If</span> IsMRUListExElement(SortedElements, <br/> ElementName) = <span class="hljs-literal">False</span> <span class="hljs-keyword">Then</span><br/> Counter = Counter + <span class="hljs-number">1</span><br/> <span class="hljs-keyword">ReDim</span> <span class="hljs-keyword">Preserve</span> Elements(Counter)<br/> Elements(Counter) = <span class="hljs-built_in">CInt</span>(ElementName)<br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">If</span><br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">If</span><br/> <span class="hljs-keyword">Next</span><br/> <span class="hljs-keyword">Return</span> Elements<br/>End <span class="hljs-keyword">Function</span>
Mit der Funktion IsMRUListExElement prüfen Sie, ob ein numerisches MRUListEx-Element, das via IntegerKeynamed Bestandteil der über den Parameter MruElements übergebenen MRUListEx-Auflistung ist oder nicht. Dazu wird die gesamte Liste per For-Each-Schleife durchlaufen, mit dem Schlüsselwert verglichen und das Ergebnis als Wahrheitswert zurückgeliefert.
Function IsMRUListExElement(
ByVal MruElements As Integer(),
ByVal IntegerKeyname As String) As Boolean
Dim result As Boolean = False
For Each Element As Integer In MruElements
If CInt(IntegerKeyname) = Element Then
result = True
Exit For
End If
Next
Return result
End Function
Aufbauend auf die in der zurückliegenden Folge dieser Serie beschriebenen Funktionen können Sie sich nun der Eintragsanalyse zuwenden. Dazu definieren Sie die Funktion GetRecentDocFolderInfos, um ein Datenfeld mit Datei-/Ordnerinformationen abzurufen. Sie übergeben die Schlüsselnamen als Ganzzahldatenfeld (EntryList) sowie den HKCU-Datenunterschlüssel (CurrentUserSubKey) als Ausgangsknoten für die Analyse. Für die Verwaltung der Informationen wird das Datenfeld RecentDocs (Typ RecentDocFolderInfos) eingerichtet. Dann wird die Eintragsliste EntryList in einer For-Each-Schleife durchlaufen.Das Informationsdatenfeld wird dabei schrittweise mithilfe der Zählvariablen Counter ohne Wertverlust redimensioniert. Danach werden die Basis-Infos (Index, IndexName) und der Zähler Counter gesichert, um anschließend die binären Eintragsdaten mit GetValue in die Variable EntryData einzulesen, in eine binäre Zeichenkette umzuwandeln und in die Variable Data sowie über das Element BinaryString in die Datenstruktur zu übernehmen. Über die binären Zeichen wird dann der Dateiname DocName abgespalten und darüber der Bezug zu einer Verknüpfungsdatei hergestellt. Diese wird mit GetLinkFileInfo ausgewertet, um die zugehörigen Informationen in das aktiv verarbeitete RecentDocs-Element aufzunehmen (Listing 2).
Listing 2: Infos zu MRU-Ordnern/Dateien ermitteln
Function GetRecentDocFolderInfos(<br/> ByVal EntryList As Integer(), <br/> ByVal CurrentUserSubKey As String) _<br/> As RecentDocFolderInfos()<br/> If EntryList IsNot Nothing Then<br/> Dim Counter As Integer = -1<br/> Dim RecentDocs As RecentDocFolderInfos() = Nothing<br/> Dim RecentDocs_UserKey As RegistryKey = <br/> CurrentUser.OpenSubKey(<br/> CurrentUserSubKey, True)<br/> If RecentDocs_UserKey IsNot Nothing Then<br/> For Each Element As Integer In EntryList<br/> Counter = Counter + 1<br/> ReDim Preserve RecentDocs(Counter)<br/> With RecentDocs(Counter)<br/> .Counter = Counter<br/> .IndexValue = Element<br/> .IndexName = .IndexValue.ToString<br/> Dim EntryData As Byte() = <br/> RecentDocs_UserKey.GetValue(<br/> .IndexName, Nothing)<br/> Dim Data As String = <br/> GetStringFromBinary(EntryData)<br/> .BinaryString = Data<br/> Dim dLen As Integer = Data.Length<br/> .DocName = Data.Substring(<br/> 0, Data.IndexOf(vbNullChar))<br/> Dim DataStart As Integer = <br/> .DocName.Length<br/> Dim LinkFileStart As Integer = <br/> Data.IndexOf(.DocName, DataStart)<br/> If LinkFileStart = -1 And <br/> InStr(.DocName, ".") &gt; 0 Then<br/> Dim DotPosition As Integer = <br/> .DocName.IndexOf(".")<br/> Dim MainFileName As String = <br/> .DocName.Substring(0, DotPosition)<br/> LinkFileStart = Data.IndexOf(<br/> MainFileName, DataStart)<br/> ElseIf LinkFileStart = -1 And<br/> InStr(.DocName, " ") &gt; 0 Then<br/> Dim DotPosition As Integer = <br/> .DocName.IndexOf(" ")<br/> Dim MainFileName As String = <br/> .DocName.Substring(0, DotPosition)<br/> LinkFileStart = Data.IndexOf(<br/> MainFileName, DataStart)<br/> End If<br/> Try<br/> Dim LinkFileEnd As Integer = <br/> Data.IndexOf(vbNullChar, LinkFileStart)<br/> Dim LinkFile As String = <br/> "C:\Users\" &amp;Environment.UserName &amp;<br/> "\Recent\" &amp; Data.Substring(<br/> LinkFileStart, LinkFileEnd - <br/> LinkFileStart)<br/> If IO.File.Exists(LinkFile) Then<br/> Dim fi As New FileInfo(LinkFile)<br/> .LinkFileName = LinkFile<br/> .LinkFileInfo = GetLinkFileInfo(<br/> .LinkFileName)<br/> .LastAccessTime = fi.LastAccessTime<br/> Else<br/> .LinkFileName = LinkFile<br/> End If<br/> Catch ex As Exception<br/> ReDim Preserve RecentDocs(Counter -1)<br/> End Try<br/> End With<br/> Next<br/> End If<br/> Return RecentDocs<br/> Else<br/> Return Nothing<br/> End If<br/>End Function
Die Hilfsfunktion GetStringFromBinary übernimmt ein Byte-Datenfeld und konvertiert dieses mit Encoding.Unicode.GetString in eine binäre Zeichenkette und gibt das Ergebnis zurück.
Function GetStringFromBinary(
ByVal EntryData As Byte()) As String
Dim DocName As String = Nothing
If EntryData IsNot Nothing Then
DocName = Encoding.Unicode.GetString(
EntryData, 0, EntryData.Length)
End If
Return DocName
End Function
Registry-Verlaufsdaten ausgeben
Nachdem die Auswertungsfunktionen für die Recent-Einträge der Registry ausgelesen wurden, können Sie sich um die Datenausgabe kümmern (Bild 1). Für die vollständige Datenausgabe zeichnet die Methode GetAndShowRecentDocs verantwortlich. Der Methode wird der Ausgangsknoten für die Verlaufsdaten über den Parameter DateTimeNode übergeben (Listing 3). In der Routine wird der Ausgangsknoten der Registry für die Datenermittlung festgelegt, um dann mit GetMRUListEx die Schlüsselnamen der zuletzt verwendeten Dokumente/Ordner zu ermitteln und im Anschluss daran die zugehörigenInformationen mit GetRecentDocFolderInfos auszulesen. Diese Infos werden dann mit ShowRecentDocs in der Strukturansicht angezeigt.

Zuletzt geöffnete Dokumente und Ordner der Registry anzeigen lassen (Bild 1)
Autor
Listing 3: Registry-Verlaufsdaten anzeigen
Sub GetAndShowRecentDocs(<br/> ByVal DateTimeNode As HistoryTreeNode)<br/> Dim SubKey As String = "Software\Microsoft\Windows\<br/> CurrentVersion\Explorer\RecentDocs"<br/> Dim SortedElements As Integer() = <br/> GetMRUListEx(SubKey)<br/> Dim rdi As RecentDocFolderInfos() = <br/> GetRecentDocFolderInfos(SortedElements, SubKey)<br/> ShowRecentDocs(DateTimeNode, rdi, "MRUListEx", True)<br/> Dim UnSortedElements As Integer() = <br/> GetNonMRUListExElements(SubKey)<br/> Dim rdi2 As RecentDocFolderInfos() = <br/> GetRecentDocFolderInfos(UnSortedElements, SubKey)<br/> ShowRecentDocs(DateTimeNode, rdi2, <br/> "kein MRUListEx", False)<br/> Dim RecentDocs_UserKey As RegistryKey = <br/> CurrentUser.OpenSubKey(SubKey, True)<br/> For Each ValueName As String In <br/> RecentDocs_UserKey.GetSubKeyNames<br/> SortedElements = GetMRUListEx(<br/> SubKey &amp; "\" &amp; ValueName)<br/> rdi = GetRecentDocFolderInfos(<br/> SortedElements, SubKey &amp; "\" &amp; ValueName)<br/> ShowRecentDocs(DateTimeNode, rdi, <br/> "MRUListEx [" &amp; ValueName &amp; "]", True)<br/> UnSortedElements = GetNonMRUListExElements(<br/> SubKey &amp; "\" &amp; ValueName)<br/> rdi2 = GetRecentDocFolderInfos(<br/> UnSortedElements, SubKey &amp; "\" &amp; ValueName)<br/> ShowRecentDocs(DateTimeNode, rdi2, <br/> "kein MRUListEx [" &amp; ValueName &amp; "]", False)<br/> Next<br/> SubKey = "Software\Microsoft\Windows\CurrentVersion\<br/> Explorer\RecentDocs\Folder"<br/> SortedElements = GetMRUListEx(SubKey)<br/> rdi = GetRecentDocFolderInfos(<br/> SortedElements, SubKey)<br/> ShowRecentDocs(DateTimeNode, rdi, "MRUListEx", True)<br/> UnSortedElements = GetNonMRUListExElements(SubKey)<br/> rdi2 = GetRecentDocFolderInfos(<br/> UnSortedElements, SubKey)<br/> ShowRecentDocs(DateTimeNode, rdi2, <br/> "kein MRUListEx", False)<br/>End Sub
Der nächste Schritt kümmert sich um die Elemente, die nicht Bestandteil der Liste MRUListEx sind. Hier kommen die Funktionen GetNonMRUListExElements, GetRecentDocFolderInfos und ShowRecentDocs zum Einsatz. In einem gesonderten Arbeitsschritt wird der Unterschlüssel Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs\Folder verarbeitet, in dem die Ordner-Infos abgelegt sind.Die Ausgabe übernimmt die Methode ShowRecentDocs, der Sie den Ausgangsknoten für die Verlaufsdaten (DateTimeNode) sowie das Info-Datenfeld (Typ RecentDocFolderInfos) mit den zuletzt geöffneten Ordnern und/oder Dokumenten (rdi) übergeben. Zusatzinformationen können dabei über den String-Parameter Info übergeben werden.Ob eine Eintragsliste Bestandteil der MRUListEx-Zusammenstellung ist oder nicht, geben Sie über den Parameter MruListEntries an (True, False). Alle per For-Each-Schleife ausgewerteten Elemente werden mit den vorhandenen Informationen zur Anlage neuer Strukturknoten (Typ HistoryTreeNode) genutzt und datumsbezogen einsortiert. Gibt es einen Bezug zu einer Verknüpfungsdatei, wird daraus der Zeitstempel extrahiert. Der Zielknoten DestinationDateNode wird bei Vorhandensein direkt genutzt oder nach Bedarf neu angelegt. Das Bildsymbol mit dem Index 12 kennzeichnet einen Windows-Registry-Verlaufseintrag (Listing 4).
Listing 4: Registry-Infos ausgeben
Sub ShowRecentDocs(ByVal DateTimeNode As TreeNode, <br/> ByVal rdi As RecentDocFolderInfos(), <br/> ByVal Info As String,<br/> Optional MruListEntries As Boolean = True)<br/> <br/> If rdi IsNot Nothing Then<br/> Dim NodeSymbol As Integer<br/> If MruListEntries = True Then<br/> NodeSymbol = 12<br/> Else<br/> NodeSymbol = 13<br/> End If<br/> For Each Element As RecentDocFolderInfos In rdi<br/> If Element.LinkFileInfo.LinkFileName _<br/> IsNot Nothing Then<br/> With Element<br/> Dim EntryDate As String = <br/> .LastAccessTime.ToShortDateString<br/> Dim DestinationDateNode As _<br/> HistoryTreeNode = Nothing<br/> If DateNodeExist(DateTimeNode, <br/> EntryDate) = False Then<br/> DestinationDateNode = CreateNewDateNode(<br/> DateTimeNode, EntryDate)<br/> Else<br/> DestinationDateNode = GetDateNode(<br/> DateTimeNode, EntryDate)<br/> End If<br/> Dim NewEntryNode As New HistoryTreeNode<br/> With NewEntryNode<br/> .Text = Element.DocName<br/> .ImageIndex = 12<br/> .SelectedImageIndex = 12<br/> .Description = "Verlaufseintrag <br/> [Registry - " &amp; Info &amp; "]"<br/> .NodeDate = Element.LastAccessTime<br/> .ToShortDateString<br/> .NodeTime = Element.LastAccessTime<br/> .ToShortTimeString<br/> If Element.LinkFileInfo.Type = <br/> eLinkType.File Then<br/> .NodeType = "Datei"<br/> ElseIf Element.LinkFileInfo.Type = <br/> eLinkType.Folder Then<br/> .NodeType = "Verzeichnis"<br/> Else<br/> .NodeType = "URL"<br/> End If<br/> .UrlOrFileOrFolder = Element.LinkFileName<br/> End With<br/> If NewHistoryNodeAlreadyExists(<br/> DestinationDateNode,<br/> NewEntryNode) = False Then<br/> DestinationDateNode.Nodes.Add(<br/> NewEntryNode)<br/> End If<br/> End With<br/> End If<br/> Next<br/> End If<br/>End Sub
Ausgeführte Programme ermitteln
Über die Registry können Sie auch die zuletzt im Ausführen-Dialog eingegebenen Befehle abrufen. Eine zeitliche Einordnung der Befehle ist allerdings nicht möglich. Die Informationen werden daher im Hierarchiezweig Verlaufsdaten\Programmaufrufe ohne Datumszuweisung ausgegeben.Um die Informationen auszulesen, wird die Datenstruktur RunCommandInfos definiert. Die Struktur fasst den Registry-Namen RegistryName mit dem Aufrufbefehl Command zusammen.
Structure RunCommandInfos
Dim Command As String
Dim RegistryName As String
End Structure
Die Funktion GetRunMRU ermittelt die letzten Ausführungsbefehle und gibt diese als Zeichenkettenfeld zurück. Funktionsintern werden alle Befehle dem Datenfeld RunMRUs (Typ RunCommandInfos) zugeordnet. Die Informationen selbst sind im Registrierzweig HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU zu finden. Nachdem der Knoten mit OpenSubKey geöffnet wurde, kann die MRU-Liste über den Schlüsselnamen MRUList ermittelt und der Zeichenkette EntryList zugeordnet werden. Anders als bei den zuvor genutzten Listen ist die Liste MRUList nicht verschlüsselt. Sie enthält lediglich Buchstaben, die den Schlüsselnamen der einzelnen Befehle entsprechen. Dementsprechend kann diese Zeichenkette per For-Schleife verarbeitet werden. Dazu wird nacheinander jeder einzelne Buchstabe extrahiert, um dann den zugehörigen Wert im gleichnamigen Schlüsselnamen auszulesen. Der zugehörige Aufrufbefehl wird ebenfalls unverschlüsselt verwaltet. In den Zeichenketten enthaltene Abschlusszeichen („\1“) werden entfernt und die Ergebnisliste RunMRUs an das aufrufende Programm zurückgegeben, siehe Listing 5.
Listing 5: Zuletzt ausgeführte Programme auslesen
Function GetRunMRUs() As RunCommandInfos()<br/> Dim Counter As Integer = -1<br/> Dim RunMRUs As RunCommandInfos() = Nothing<br/> Dim RunMRU_UserKey As RegistryKey = <br/> CurrentUser.OpenSubKey("Software\Microsoft\<br/> Windows\CurrentVersion\Explorer\RunMRU", True)<br/> If RunMRU_UserKey IsNot Nothing Then<br/> With RunMRU_UserKey<br/> Dim EntryList As String = .GetValue("MRUList", "")<br/> If EntryList.Trim &lt;&gt; "" Then<br/> For x As Integer = 1 To Len(EntryList)<br/> Dim Letter As String = Mid(EntryList, x, 1)<br/> Counter = Counter + 1<br/> ReDim Preserve RunMRUs(Counter)<br/> RunMRUs(Counter).Command = <br/> .GetValue(Letter).Replace("\1", "")<br/> RunMRUs(Counter).RegistryName = Letter<br/> Next<br/> End If<br/> End With<br/> End If<br/> Return RunMRUs<br/>End Function
Ausgeführte Programme ausgeben
Die Methode ShowExecutedPrograms(Listing 6) übernimmt die Ausgabe der Ausführungsbefehle für Programme. Über den Parameter rNode wird ihr der Wurzelknoten mit dem Text Verlaufsdaten übergeben. Mit GetRunMRUs ermittelt die Methode die Befehle, legt einen neuen Unterknoten mit dem Titel Programmaufrufe an und platziert darunter alle Aufrufbefehle (rci) per For-Each-Schleife in der Strukturansicht für Verlaufsdaten.Listing 6: Zuletzt ausgeführte Programme ausgeben
Sub ShowExecutedPrograms(ByVal rNode As TreeNode)<br/> If IsInDesignMode() = False Then<br/> Dim rci As RunCommandInfos() = GetRunMRUs()<br/> Dim AppSubNode As New HistoryTreeNode<br/> With AppSubNode<br/> .Text = "Programmaufrufe"<br/> .ImageIndex = 6<br/> .SelectedImageIndex = 6<br/> .Description = "In der Systemregistrierung <br/> gesicherte Ausführungsbefehle"<br/> .NodeDate = DateTime.Now.ToShortDateString<br/> .NodeTime = DateTime.Now.ToShortTimeString<br/> .NodeType = "Wurzelelement für Ausführungsbefehle"<br/> .UrlOrFileOrFolder = "HKCU\Software\Microsoft\<br/> Windows\CurrentVersion\Explorer\RunMRU"<br/> rNode.Nodes.Add(AppSubNode)<br/> End With<br/> For Each entry As RunCommandInfos In rci<br/> Dim ExecuteNode As New HistoryTreeNode<br/> With ExecuteNode<br/> .Text = entry.Command<br/> .ImageIndex = 7<br/> .SelectedImageIndex = 7<br/> .Description = "In der Systemregistrierung <br/> gesicherte Ausführungsbefehle"<br/> .NodeDate = DateTime.Now.ToShortDateString<br/> .NodeTime = DateTime.Now.ToShortTimeString<br/> .NodeType = "Startbefehl"<br/> .UrlOrFileOrFolder = entry.Command<br/> AppSubNode.Nodes.Add(ExecuteNode)<br/> End With<br/> Next<br/> End If<br/>End Sub
Damit sind alle Informationen zu zuletzt geöffneten Ordnern, Dateien und ausgeführten Programmen über das Dateisystem und auch die Systemregistrierung ermittelt worden. In der kommenden Folge dieser Serie wird es darum gehen, weitere Verlaufsdaten auszulesen, die der Windows Explorer aufzeichnet.

Anzeige der zuletzt gestarteten Programme (Bild 2)
Autor
Ist auch das geschafft, fehlt nur noch ein wenig Feinschliff: Das Verlaufsdatensteuerelement wird abschließend mit Kontextmenüs und variablen Anzeigemodi optimiert.
Fussnoten
- Andreas Maslo, Windows Document History, dotnetpro 11/2023, Seite 132 ff., http://www.dotnetpro.de/A2311BasicInstinct