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
Function GetNonMRUListExElements(
ByVal CurrentUserSubKey As String) As Integer()
Dim Counter As Integer = -1
Dim Elements As Integer() = Nothing
Dim RecentDocs_UserKey As RegistryKey =
CurrentUser.OpenSubKey(CurrentUserSubKey, True)
Dim SortedElements As Integer() =
GetMRUListEx(CurrentUserSubKey)
For Each ElementName As String _
In RecentDocs_UserKey.GetValueNames
If ElementName <> "MRUListEx" Then
If IsMRUListExElement(SortedElements,
ElementName) = False Then
Counter = Counter + 1
ReDim Preserve Elements(Counter)
Elements(Counter) = CInt(ElementName)
End If
End If
Next
Return Elements
End Function
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(
ByVal EntryList As Integer(),
ByVal CurrentUserSubKey As String) _
As RecentDocFolderInfos()
If EntryList IsNot Nothing Then
Dim Counter As Integer = -1
Dim RecentDocs As RecentDocFolderInfos() = Nothing
Dim RecentDocs_UserKey As RegistryKey =
CurrentUser.OpenSubKey(
CurrentUserSubKey, True)
If RecentDocs_UserKey IsNot Nothing Then
For Each Element As Integer In EntryList
Counter = Counter + 1
ReDim Preserve RecentDocs(Counter)
With RecentDocs(Counter)
.Counter = Counter
.IndexValue = Element
.IndexName = .IndexValue.ToString
Dim EntryData As Byte() =
RecentDocs_UserKey.GetValue(
.IndexName, Nothing)
Dim Data As String =
GetStringFromBinary(EntryData)
.BinaryString = Data
Dim dLen As Integer = Data.Length
.DocName = Data.Substring(
0, Data.IndexOf(vbNullChar))
Dim DataStart As Integer =
.DocName.Length
Dim LinkFileStart As Integer =
Data.IndexOf(.DocName, DataStart)
If LinkFileStart = -1 And
InStr(.DocName, ".") > 0 Then
Dim DotPosition As Integer =
.DocName.IndexOf(".")
Dim MainFileName As String =
.DocName.Substring(0, DotPosition)
LinkFileStart = Data.IndexOf(
MainFileName, DataStart)
ElseIf LinkFileStart = -1 And
InStr(.DocName, " ") > 0 Then
Dim DotPosition As Integer =
.DocName.IndexOf(" ")
Dim MainFileName As String =
.DocName.Substring(0, DotPosition)
LinkFileStart = Data.IndexOf(
MainFileName, DataStart)
End If
Try
Dim LinkFileEnd As Integer =
Data.IndexOf(vbNullChar, LinkFileStart)
Dim LinkFile As String =
"C:\Users\" &Environment.UserName &
"\Recent\" & Data.Substring(
LinkFileStart, LinkFileEnd -
LinkFileStart)
If IO.File.Exists(LinkFile) Then
Dim fi As New FileInfo(LinkFile)
.LinkFileName = LinkFile
.LinkFileInfo = GetLinkFileInfo(
.LinkFileName)
.LastAccessTime = fi.LastAccessTime
Else
.LinkFileName = LinkFile
End If
Catch ex As Exception
ReDim Preserve RecentDocs(Counter -1)
End Try
End With
Next
End If
Return RecentDocs
Else
Return Nothing
End If
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(
ByVal DateTimeNode As HistoryTreeNode)
Dim SubKey As String = "Software\Microsoft\Windows\
CurrentVersion\Explorer\RecentDocs"
Dim SortedElements As Integer() =
GetMRUListEx(SubKey)
Dim rdi As RecentDocFolderInfos() =
GetRecentDocFolderInfos(SortedElements, SubKey)
ShowRecentDocs(DateTimeNode, rdi, "MRUListEx", True)
Dim UnSortedElements As Integer() =
GetNonMRUListExElements(SubKey)
Dim rdi2 As RecentDocFolderInfos() =
GetRecentDocFolderInfos(UnSortedElements, SubKey)
ShowRecentDocs(DateTimeNode, rdi2,
"kein MRUListEx", False)
Dim RecentDocs_UserKey As RegistryKey =
CurrentUser.OpenSubKey(SubKey, True)
For Each ValueName As String In
RecentDocs_UserKey.GetSubKeyNames
SortedElements = GetMRUListEx(
SubKey & "\" & ValueName)
rdi = GetRecentDocFolderInfos(
SortedElements, SubKey & "\" & ValueName)
ShowRecentDocs(DateTimeNode, rdi,
"MRUListEx [" & ValueName & "]", True)
UnSortedElements = GetNonMRUListExElements(
SubKey & "\" & ValueName)
rdi2 = GetRecentDocFolderInfos(
UnSortedElements, SubKey & "\" & ValueName)
ShowRecentDocs(DateTimeNode, rdi2,
"kein MRUListEx [" & ValueName & "]", False)
Next
SubKey = "Software\Microsoft\Windows\CurrentVersion\
Explorer\RecentDocs\Folder"
SortedElements = GetMRUListEx(SubKey)
rdi = GetRecentDocFolderInfos(
SortedElements, SubKey)
ShowRecentDocs(DateTimeNode, rdi, "MRUListEx", True)
UnSortedElements = GetNonMRUListExElements(SubKey)
rdi2 = GetRecentDocFolderInfos(
UnSortedElements, SubKey)
ShowRecentDocs(DateTimeNode, rdi2,
"kein MRUListEx", False)
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,
ByVal rdi As RecentDocFolderInfos(),
ByVal Info As String,
Optional MruListEntries As Boolean = True)
If rdi IsNot Nothing Then
Dim NodeSymbol As Integer
If MruListEntries = True Then
NodeSymbol = 12
Else
NodeSymbol = 13
End If
For Each Element As RecentDocFolderInfos In rdi
If Element.LinkFileInfo.LinkFileName _
IsNot Nothing Then
With Element
Dim EntryDate As String =
.LastAccessTime.ToShortDateString
Dim DestinationDateNode As _
HistoryTreeNode = Nothing
If DateNodeExist(DateTimeNode,
EntryDate) = False Then
DestinationDateNode = CreateNewDateNode(
DateTimeNode, EntryDate)
Else
DestinationDateNode = GetDateNode(
DateTimeNode, EntryDate)
End If
Dim NewEntryNode As New HistoryTreeNode
With NewEntryNode
.Text = Element.DocName
.ImageIndex = 12
.SelectedImageIndex = 12
.Description = "Verlaufseintrag
[Registry - " & Info & "]"
.NodeDate = Element.LastAccessTime
.ToShortDateString
.NodeTime = Element.LastAccessTime
.ToShortTimeString
If Element.LinkFileInfo.Type =
eLinkType.File Then
.NodeType = "Datei"
ElseIf Element.LinkFileInfo.Type =
eLinkType.Folder Then
.NodeType = "Verzeichnis"
Else
.NodeType = "URL"
End If
.UrlOrFileOrFolder = Element.LinkFileName
End With
If NewHistoryNodeAlreadyExists(
DestinationDateNode,
NewEntryNode) = False Then
DestinationDateNode.Nodes.Add(
NewEntryNode)
End If
End With
End If
Next
End If
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()
Dim Counter As Integer = -1
Dim RunMRUs As RunCommandInfos() = Nothing
Dim RunMRU_UserKey As RegistryKey =
CurrentUser.OpenSubKey("Software\Microsoft\
Windows\CurrentVersion\Explorer\RunMRU", True)
If RunMRU_UserKey IsNot Nothing Then
With RunMRU_UserKey
Dim EntryList As String = .GetValue("MRUList", "")
If EntryList.Trim <> "" Then
For x As Integer = 1 To Len(EntryList)
Dim Letter As String = Mid(EntryList, x, 1)
Counter = Counter + 1
ReDim Preserve RunMRUs(Counter)
RunMRUs(Counter).Command =
.GetValue(Letter).Replace("\1", "")
RunMRUs(Counter).RegistryName = Letter
Next
End If
End With
End If
Return RunMRUs
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)
If IsInDesignMode() = False Then
Dim rci As RunCommandInfos() = GetRunMRUs()
Dim AppSubNode As New HistoryTreeNode
With AppSubNode
.Text = "Programmaufrufe"
.ImageIndex = 6
.SelectedImageIndex = 6
.Description = "In der Systemregistrierung
gesicherte Ausführungsbefehle"
.NodeDate = DateTime.Now.ToShortDateString
.NodeTime = DateTime.Now.ToShortTimeString
.NodeType = "Wurzelelement für Ausführungsbefehle"
.UrlOrFileOrFolder = "HKCU\Software\Microsoft\
Windows\CurrentVersion\Explorer\RunMRU"
rNode.Nodes.Add(AppSubNode)
End With
For Each entry As RunCommandInfos In rci
Dim ExecuteNode As New HistoryTreeNode
With ExecuteNode
.Text = entry.Command
.ImageIndex = 7
.SelectedImageIndex = 7
.Description = "In der Systemregistrierung
gesicherte Ausführungsbefehle"
.NodeDate = DateTime.Now.ToShortDateString
.NodeTime = DateTime.Now.ToShortTimeString
.NodeType = "Startbefehl"
.UrlOrFileOrFolder = entry.Command
AppSubNode.Nodes.Add(ExecuteNode)
End With
Next
End If
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.,