16. Okt 2023
Lesedauer 7 Min.
Windows Document History
Ein eigenes Steuerelement für Verlaufsdaten, Teil 6
Nicht nur Webbrowser zeichnen Verlaufsdaten auf, sondern auch Windows.

Das Benutzersteuerelement HistoryCtl stellt alle angewählten URL-Verlaufsdaten der Webbrowser Edge, Google Chrome sowie dem in der vorangegangenen Serie aufgebauten Steuerelement BrowserCtl in einer geordneten Strukturansicht zusammen. Den bislang letzten Stand der Arbeiten finden Sie unter [1] und dort wiederum Verweise auf die vorangegangenen Folgen der Serie. Im nächsten Schritt sollen auch die von Windows aufgezeichneten Verlaufsdaten angebunden werden. Sie werden an unterschiedlichen Stellen des Systems verwaltet und sind mitunter verborgen. Fündig werden Sie sowohl im Windows-Dateisystem als auch in der Systemregistrierung (Registry). Weniger bekannt sind die Aufzeichnungsfunktionen des Windows Explorers. Dieser legt Daten über sogenannte Shell Bags in der Registry ab. Beim Auslesen dieser Informationen hilft ein in C++ definiertes API. Um dieses in VB.NET nutzen zu können, sind alle API-Funktionen, Enums, Datenstrukturen, Eigenschaften, Schnittstellen sowie COM-Objektanbindungen zunächst in die Syntax von Visual Basic .NET zu überführen. Danach lässt sich die Funktionalität des API im Verlaufsdatensteuerelement nutzen. Die zu implementierende Gesamtfunktionalität sehen Sie in Bild 1.

Gesamtfunktionalität des benutzerdefinierten Verlaufsdatensteuerelements (Bild 1)
Autor
Den Recent-Ordner auswerten
Windows verwaltet die jeweils zuletzt angewählten Ordner und/oder Dateien eines bestimmten Benutzers im Recent-Ordner unter C:\Users\Benutzername\AppData\Roaming\Microsoft\Windows\Recent. Für jeden Benutzer werden folglich unterschiedliche Ergebnisse zurückgegeben.Zunächst sollen Enumerationen, Datenstrukturen und Methoden realisiert werden, die das Auslesen der besagten Informationen vereinfachen und bei der Anzeige im Verlaufsdatensteuerelement helfen. Erinnern Sie sich zunächst daran, dass alle Informationen durch eindeutige Symbole gekennzeichnet werden. So lassen sich Browser- und Systemverlaufsdaten leicht unterscheiden. Die Symbole werden über das Steuerelement ImgLst verwaltet, das in den Entwurfsbereich des Benutzersteuerelements platziert und mit den benötigten Icons gefüllt wurde (Bild 2). Innerhalb der Quelltexte werden die Icons per Index an die Eigenschaften ImageIndex und SelectedImageIndex übergeben.
Icons im Bildsammlungs-Editor (Bild 2)
Autor
Bei den angewählten Elementen wird zwischen Ordnern (Folder) und Dateien (File) unterschieden.Die Typen selbst werden über die Enumeration eLinkType zusammengefasst. Kann kein Verbindungstyp zugeordnet werden, wird diesem der Typ NoFolderFile zugewiesen.
<span class="hljs-keyword">Enum</span> eLinkType
Folder
<span class="hljs-keyword">File</span>
NoFolderFile
End <span class="hljs-keyword">Enum</span>
Alle Daten zu angewählten Ordnern und/oder Dateien werden im Recent-Ordner über Verknüpfungsdateien (Link Files) verwaltet. Diese enthalten alle wichtigen Daten, um später auf den tatsächlichen Pfad oder die tatsächliche Datei zugreifen zu können.Um die Informationen zu bündeln, wird zuerst die Datenstruktur RecentLinkFileInfo definiert. Sie fasst den Namen (Name), die Verknüpfungsdatei (LinkFileName), den Ordner (Folder), das Arbeitsverzeichnis (WorkingFolder), eine Beschreibung (Description), das zugeordnete Bildsymbol (IconLocation), den aktiven Stil (WindowsStyle), das Anlagedatum (CreationDate) sowie den Verknüpfungstyp (eLinkType) zusammen.
<span class="hljs-keyword">Structure</span> RecentLinkFileInfo
<span class="hljs-keyword">Dim</span> Name <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> LinkFileName <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> Folder <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> WorkingFolder <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> Description <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> IconLocation <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> WindowStyle <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> CreationDate <span class="hljs-keyword">As</span> <span class="hljs-built_in">Date</span>
<span class="hljs-keyword">Dim</span> Type <span class="hljs-keyword">As</span> eLinkType
End <span class="hljs-keyword">Structure</span>
Mit der Funktion GetLastRecentFilesAndFolders können Sie die zehn zuletzt geöffneten Ordner und Dateien auslesen (Listing 1). Die Funktion fasst die ermittelten Einträge zusammen und liefert diese als Datenfeld des Typs RecentLinkFileInfo zurück. Funktionsintern wird das Datenfeld unter dem Namen LnkInfos eingerichtet und auf Nothing gesetzt. Die Zählvariable Counter wird auf -1 gesetzt. Danach wird der aktuelle Benutzername (ActualUser) ermittelt und damit der zugehörige Recent-Ordner (DataFolder) bestimmt. Nur wenn das Verzeichnis existiert, werden die darin enthaltenen lnk-Dateien mit Directory.GetFiles ausgelesen und über eine For-Each-Schleife verarbeitet. Für jedes verarbeitete Element wird der Zähler Counter erhöht und das Datenfeld mit einer ReDim-Preserve-Anweisung neu dimensioniert. Die Verknüpfungsinformationen werden über die Funktion GetLinkFileInfos unter Angabe der verarbeiteten Verknüpfungsdatei ermittelt und an das neue Datenelement LnkInfos(Counter) übergeben. Sind alle Einträge verarbeitet, wird das Datenfeld LnkInfos mit Array.Sort aufsteigend nach Datum sortiert. Die Sortierroutine CompareRecentLinkFileInfos wird dabei als Adresse übergeben. Mit Array.Reverse wird die Sortierung umgekehrt (absteigende Sortierung) und die Liste an das aufrufende Programm übergeben.
Listing 1: Zuletzt geöffnete Ordner und Dateien ermitteln
Function GetLastRecentFilesAndFolders() <span class="hljs-keyword">As</span> _ RecentLinkFileInfo()<br/> <span class="hljs-keyword">Dim</span> LnkInfos <span class="hljs-keyword">As</span> RecentLinkFileInfo() = Nothing<br/> <span class="hljs-keyword">Dim</span> Counter <span class="hljs-keyword">As</span> Integer = -1<br/> ' bezogen auf angemeldeten Benutzer<br/> <span class="hljs-keyword">Dim</span> ActualUser <span class="hljs-keyword">As</span> String = Environment.UserName<br/> ' Datenverzeichnis mit den Informationen<br/> <span class="hljs-keyword">Dim</span> DataFolder <span class="hljs-keyword">As</span> String = <span class="hljs-string">"C:\Users\"</span> &amp; <br/> ActualUser &amp; <span class="hljs-string">"\AppData\Roaming\Microsoft\ Windows\Recent"</span><br/> <span class="hljs-keyword">If</span> Directory.Exists(DataFolder) Then<br/> ' Verweise sind als Verknüpfungen gespeichert<br/> <span class="hljs-keyword">Dim</span> RecentFiles <span class="hljs-keyword">As</span> String() = Directory.GetFiles(<br/> DataFolder, <span class="hljs-string">"*.lnk"</span>, SearchOption.AllDirectories)<br/> <span class="hljs-keyword">For</span> Each rFile In RecentFiles<br/> Counter = Counter + 1<br/> <span class="hljs-keyword">ReDim</span> Preserve LnkInfos(Counter)<br/> LnkInfos(Counter) = GetLinkFileInfo(rFile)<br/> <span class="hljs-keyword">Next</span><br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">If</span><br/> ' Datenfeld nach Datum sortieren (aufsteigend)<br/> Array.Sort(LnkInfos, AddressOf CompareRecentLinkFileInfos)<br/> Array.Reverse(LnkInfos) ' Sortierung umkehren<br/> <span class="hljs-keyword">Return</span> LnkInfos<br/>End Function
Das Auswerten der Informationen in einer Verknüpfungsdatei erledigt die Funktion GetLinkFileInfo. Dieser wird der Dateiname mit Pfad über den Parameter LinkFileWithPath übergeben. Das Ergebnis wird als RecentLinkFileInfo zurückgegeben. In der Funktion werden die Verknüpfungsinformationen über die neu instanzierte Variable LnkInfo (Typ RecentLinkFileInfo) verwaltet.Die Informationen werden dabei durch den Windows Scripting Host ausgelesen verarbeitet. Folglich ist ein entsprechender Verweis in der Steuerelementbibliothek ExtendedControlsLib erforderlich (Bild 3).

Den Windows Scripting Host per COM-Verweis anbinden (Bild 3)
Autor
Nach der Verweiserstellung können Sie die Funktion codieren. Zuerst instanzieren Sie ein neues Objekt des Typs IWshRuntimeLibrary.WshShell, das hier der Variablen wShell zugeordnet wird. Mit wShell.CreateShortcut legen Sie dann unter Angabe der bestehenden Verknüpfungsdatei LinkFileWithPath ein ShortCut-Objekt an (Typ IWshShortcut) und füllen dieses mit den zugeordneten Daten. Die Daten werden dann schrittweise in die Datenstruktur LnkInfo (Typ RecentLinkFileInfo) kopiert. Lediglich das Anlagedatum ist nicht direkt verfügbar. Es wird über die Datei und ein zugehöriges FileInfo-Objekt ermittelt. Beim Anlagedatum werden Datum und Zeit des letzten Zugriffs vermerkt. Ob ein Eintrag als Ordner oder Datei gekennzeichnet wird, ist abhängig davon, ob das jeweilige Element noch existiert oder nicht. Existiert es nicht mehr, wird der Verknüpfungstyp eLinkType.NoFolderFile zugeordnet (Listing 2).
Listing 2: Verknüpfungsinfos auslesen
<span class="hljs-keyword">Function</span> GetLinkFileInfo(<span class="hljs-keyword">ByVal</span> LinkFileWithPath _ <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>) <span class="hljs-keyword">As</span> RecentLinkFileInfo<br/> <span class="hljs-keyword">Dim</span> LnkInfo <span class="hljs-keyword">As</span> <span class="hljs-keyword">New</span> RecentLinkFileInfo<br/> <span class="hljs-keyword">Try</span><br/> <span class="hljs-keyword">Dim</span> wShell <span class="hljs-keyword">As</span> IWshShell = <span class="hljs-keyword">New</span> IWshRuntimeLibrary.WshShell()<br/> <span class="hljs-keyword">Dim</span> wLink <span class="hljs-keyword">As</span> IWshShortcut = <span class="hljs-built_in">DirectCast</span>(<br/> wShell.CreateShortcut(LinkFileWithPath), IWshShortcut)<br/> <span class="hljs-keyword">With</span> wLink<br/> LnkInfo.Name = .FullName.Substring(<br/> .FullName.LastIndexOf(<span class="hljs-string">"\"</span>) + <span class="hljs-number">1</span>).Replace(<br/> <span class="hljs-string">".lnk"</span>, <span class="hljs-string">""</span>)<br/> LnkInfo.LinkFileName = .FullName<br/> LnkInfo.Folder = .TargetPath<br/> LnkInfo.WorkingFolder = .WorkingDirectory<br/> LnkInfo.Description = .Description<br/> LnkInfo.IconLocation = .IconLocation<br/> LnkInfo.WindowStyle = .WindowStyle<br/> <span class="hljs-keyword">Dim</span> fi <span class="hljs-keyword">As</span> <span class="hljs-keyword">New</span> FileInfo(LinkFileWithPath)<br/> LnkInfo.CreationDate = fi.LastWriteTime<br/> LnkInfo.Type = eLinkType.NoFolderFile<br/> <span class="hljs-keyword">If</span> .TargetPath &lt;&gt; <span class="hljs-string">""</span> <span class="hljs-keyword">Then</span><br/> <span class="hljs-keyword">If</span> Directory.Exists(.TargetPath) <span class="hljs-keyword">Then</span><br/> LnkInfo.Type = eLinkType.Folder<br/> <span class="hljs-keyword">ElseIf</span> IO.File.Exists(.TargetPath) <span class="hljs-keyword">Then</span><br/> LnkInfo.Type = eLinkType.File<br/> <span class="hljs-keyword">Else</span><br/> LnkInfo.Type = eLinkType.NoFolderFile<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">Return</span> LnkInfo<br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">With</span><br/> <span class="hljs-keyword">Catch</span> ex <span class="hljs-keyword">As</span> Exception<br/> <span class="hljs-keyword">Return</span> <span class="hljs-literal">Nothing</span><br/> <span class="hljs-keyword">End</span> Try<br/>End <span class="hljs-keyword">Function</span>
Die Gültigkeit jedes Elements ist nachzuweisen, denn nicht jeder Link muss noch gültig sein. So könnte etwa ein externes Laufwerk, auf das der Link zeigt, getrennt worden sein.Für den Datumsvergleich wird die Methode CompareRecentLinkFileInfos (Strukturvergleichsdelegat für Datenstrukturen mit Datumselement) definiert, der zwei Informationen zu den Verknüpfungsinfos des Typs RecentLinkFileInfo übergeben werden. Diese Methode vergleicht die Anlagedaten CreationDate via CompareTo und liefert das Ergebnis als Zahlenwert zurück.
<span class="hljs-keyword">Private</span> <span class="hljs-keyword">Function</span> CompareRecentLinkFileInfos(
<span class="hljs-keyword">ByVal</span> LnkFileInfo1 <span class="hljs-keyword">As</span> RecentLinkFileInfo,
<span class="hljs-keyword">ByVal</span> LnkFileInfo2 <span class="hljs-keyword">As</span> RecentLinkFileInfo) <span class="hljs-keyword">As</span> <span class="hljs-built_in">Integer</span>
<span class="hljs-keyword">Return</span> LnkFileInfo1.CreationDate.CompareTo(
LnkFileInfo2.CreationDate)
<span class="hljs-keyword">End</span> <span class="hljs-keyword">Function</span>
Infos im Recent-Ordner anzeigen
Nachdem die Informationen des Recent-Ordners ausgelesen sind, können Sie diese zeitbasiert in das Verlaufsdatensteuerelement übernehmen. Dafür nutzen Sie die Methode ShowFilesAndFolders, der Sie den Hauptknoten DateTimeNode für die Datumsausgaben als Parameter übergeben. Die Methode ermittelt zuerst die zuletzt geöffneten Ordner und Dateien über die Funktion GetLastRecentFilesAndFolders und übernimmt die Daten in das Datenfeld rlfi. Die Elemente des Typs RecentLinkFileInfo werden dann per For-Each-Schleife durchlaufen und bezogen auf das letzte Schreibdatum datumsbasiert eingeordnet. Die Datumsknoten werden im kurzen Datumsformat verwendet und bei Nichtexistenz mit CreateNewDateNode zuerst angelegt. Mit der Methode DateNodeExist prüfen Sie, ob ein Datumsknoten existiert. Wenn ja, wird er mit GetDateNode ermittelt. Der Zielknoten wird über die Variable DestinationNode vom Typ HistoryTreeNode verwaltet und um neue, mit einem typspezifischen Symbol gekennzeichnete Eintragsknoten (NewEntryNode) erweitert (Listing 3). Eine exemplarische Inhaltsausgabe zeigt Bild 4.Listing 3: Zuletzt geöffnete Ordner und Dateien ausgeben (Teil 1)
Sub ShowFilesAndFolders( ByVal DateTimeNode As HistoryTreeNode)<br/> Dim rlfi As RecentLinkFileInfo() = <br/> GetLastRecentFilesAndFolders()<br/> <span class="hljs-keyword">For</span> Each <span class="hljs-keyword">Entry</span> As RecentLinkFileInfo <span class="hljs-keyword">In</span> rlfi<br/> <span class="hljs-keyword">With</span> <span class="hljs-keyword">Entry</span><br/> Dim EntryDate As String = <br/> .CreationDate.ToShortDateString<br/> <span class="hljs-keyword">If</span> .Folder &lt;&gt; <span class="hljs-string">""</span> <span class="hljs-keyword">Then</span><br/> ' Zielknoten wählen/anlegen<br/> Dim DestinationDateNode As HistoryTreeNode = Nothing<br/> <span class="hljs-keyword">If</span> DateNodeExist( DateTimeNode, EntryDate) = <span class="hljs-literal">False</span> <span class="hljs-keyword">Then</span><br/> DestinationDateNode = <br/> CreateNewDateNode(DateTimeNode, EntryDate)<br/> <span class="hljs-keyword">Else</span><br/> DestinationDateNode = <br/> GetDateNode(DateTimeNode, EntryDate)<br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">If</span><br/> Dim NewEntryNode As <span class="hljs-keyword">New</span> HistoryTreeNode<br/> <span class="hljs-keyword">If</span> .<span class="hljs-keyword">Type</span> <span class="hljs-type">= </span>eLinkType.Folder <span class="hljs-keyword">Then</span> ' Ordner<br/> <span class="hljs-keyword">With</span> NewEntryNode<br/> .Text = <span class="hljs-keyword">Entry</span>.Name<br/> .ImageIndex = <span class="hljs-number">3</span><br/> .SelectedImageIndex = <span class="hljs-number">3</span><br/> Dim di As <span class="hljs-keyword">New</span> DirectoryInfo(<span class="hljs-keyword">Entry</span>.Folder)<br/> .Description = <span class="hljs-string">"Gültiger Verlaufseintrag [Ordner]"</span><br/> .NodeDate = di.LastAccessTime.ToShortDateString<br/> .NodeTime = di.LastAccessTime.ToShortTimeString<br/> .NodeType = <span class="hljs-string">"Verzeichnis"</span><br/> .UrlOrFileOrFolder = <span class="hljs-keyword">Entry</span>.Folder<br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">With</span><br/> ElseIf .<span class="hljs-keyword">Type</span> <span class="hljs-type">= </span>eLinkType.File <span class="hljs-keyword">Then</span> ' Datei<br/> <span class="hljs-keyword">With</span> NewEntryNode<br/> .Text = <span class="hljs-keyword">Entry</span>.Name<br/> .ImageIndex = <span class="hljs-number">4</span><br/> .SelectedImageIndex = <span class="hljs-number">4</span><br/> .Tag = <span class="hljs-keyword">Entry</span>.Folder<br/> Dim fi As <span class="hljs-keyword">New</span> FileInfo(<span class="hljs-keyword">Entry</span>.Folder)<br/> .Description = <span class="hljs-string">"Gültiger Verlaufseintrag [Datei]"</span><br/> .NodeDate = fi.LastAccessTime.ToShortDateString<br/> .NodeTime = fi.LastAccessTime.ToShortTimeString<br/> .NodeType = <span class="hljs-string">"Datei"</span><br/> .UrlOrFileOrFolder = <span class="hljs-keyword">Entry</span>.Folder<br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">With</span><br/> ElseIf .<span class="hljs-keyword">Type</span> <span class="hljs-type">= </span>eLinkType.NoFolderFile <span class="hljs-keyword">Then</span><br/> <span class="hljs-keyword">With</span> NewEntryNode<br/> .Text = <span class="hljs-keyword">Entry</span>.Name<br/> .ImageIndex = <span class="hljs-number">8</span><br/> .SelectedImageIndex = <span class="hljs-number">8</span><br/> .Tag = <span class="hljs-keyword">Entry</span>.Folder<br/> Dim fDateTime As Date = Nothing<br/> Dim fType As eNameType = TypeOfName(<span class="hljs-keyword">Entry</span>.Folder)<br/> Dim fTypeName As String = Nothing<br/> <span class="hljs-keyword">If</span> fType = eNameType.Folder <span class="hljs-keyword">Then</span><br/> Dim di As <span class="hljs-keyword">New</span> DirectoryInfo( <span class="hljs-keyword">Entry</span>.Folder)<br/> fDateTime = di.LastAccessTime<br/> fTypeName = <span class="hljs-string">"Verzeichnis"</span><br/> ElseIf fType = eNameType.File <span class="hljs-keyword">Then</span><br/> Dim fi As <span class="hljs-keyword">New</span> FileInfo(<span class="hljs-keyword">Entry</span>.Folder)<br/> fDateTime = fi.LastAccessTime<br/> fTypeName = <span class="hljs-string">"Datei"</span><br/> <span class="hljs-keyword">Else</span><br/> fTypeName = <span class="hljs-string">"unbekannt"</span><br/> fDateTime = DateTime.Now<br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">If</span><br/> .Description = <span class="hljs-string">"Derzeit ungültiger Verlaufseintrag [Ordner/Datei]"</span><br/> .NodeDate = fDateTime.ToShortDateString<br/> .NodeTime = fDateTime.ToShortTimeString<br/> .NodeType = fTypeName<br/> .UrlOrFileOrFolder = <span class="hljs-keyword">Entry</span>.Folder<br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">With</span><br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">If</span><br/> <span class="hljs-keyword">If</span> ShowNoAccessableFilesAndFolders <span class="hljs-keyword">And</span> <br/> .<span class="hljs-keyword">Type</span> <span class="hljs-type">= </span>eLinkType.NoFolderFile <span class="hljs-keyword">Or</span> .<span class="hljs-keyword">Type</span> <span class="hljs-type">= </span>eLinkType.Folder <span class="hljs-keyword">Or</span><br/> .<span class="hljs-keyword">Type</span> <span class="hljs-type">= </span>eLinkType.File <span class="hljs-keyword">Then</span><br/> <span class="hljs-keyword">If</span> NewHistoryNodeAlreadyExists(<br/> DestinationDateNode, NewEntryNode) = <span class="hljs-literal">False</span> <span class="hljs-keyword">Then</span><br/> DestinationDateNode.Nodes.Add( NewEntryNode)<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">End</span> <span class="hljs-keyword">If</span><br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">With</span><br/> Next<br/>End Sub

Die zuletzt geöffneten Ordner im Verlaufssteuerelement (Bild 4)
Autor
Im Rahmen der Anzeige wird die Existenz der anzuzeigenden Elemente erneut geprüft. Die Typen werden durch die Enumeration eNameType vordefiniert.
<span class="hljs-keyword">Enum</span> eNameType
Folder
<span class="hljs-keyword">File</span>
None
End <span class="hljs-keyword">Enum</span>
Die Funktion TypeOfName ermittelt anhand des übergebenen Suchpfads FileOrFolderName, ob es sich um einen Ordner, eine Datei oder ein nicht existierendes Element handelt.
<span class="hljs-keyword">Function</span> TypeOfName(
<span class="hljs-keyword">ByVal</span> FileOrFolderName <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>) <span class="hljs-keyword">As</span> eNameType
<span class="hljs-keyword">Dim</span> Result <span class="hljs-keyword">As</span> eNameType = eNameType.None
<span class="hljs-keyword">If</span> IO.File.Exists(FileOrFolderName) <span class="hljs-keyword">Then</span>
Result = eNameType.File
<span class="hljs-keyword">ElseIf</span> Directory.Exists(FileOrFolderName) <span class="hljs-keyword">Then</span>
Result = eNameType.Folder
<span class="hljs-keyword">End</span> <span class="hljs-keyword">If</span>
<span class="hljs-keyword">Return</span> Result
End <span class="hljs-keyword">Function</span>
Registry-Verlaufsdaten ermitteln
Neben den Verlaufsdaten im Recent-Ordner des aktuellen Benutzers finden sich weitere Verlaufsdaten in der Registry. Diese sind binär verschlüsselt und deshalb nur mit viel Aufwand auszulesen und anzuzeigen. Wie die binäre Entschlüsselung und Dateninterpretation erfolgt, soll an dieser Stelle detailliert beschrieben werden.Im ersten Schritt wird dafür eine Datenstruktur definiert, welche die übergeordneten Informationen zu den Binäreinträgen verwaltet. Diese Einträge sind mit Verknüpfungsdateien von Windows verbunden, die als Strukturelement LinkFileInfo des Typs RecentLinkFileInfo mitverwaltet werden. Ansonsten werden ein Zähler für die interne binäre Datenverwaltung (Counter), ein Indexwert (IndexValue), ein Indexname (IndexName), ein Dokumentname (DocName), eine Verknüpfungsdatei (LinkFileName), eine binäre Zeichenkette (BinaryString) sowie die letzte Zugriffszeit (LastAccessTime) in die Datenstruktur aufgenommen.
<span class="hljs-keyword">Public</span> <span class="hljs-keyword">Structure</span> RecentDocFolderInfos
<span class="hljs-keyword">Dim</span> Counter <span class="hljs-keyword">As</span> <span class="hljs-built_in">Integer</span>
<span class="hljs-keyword">Dim</span> IndexValue <span class="hljs-keyword">As</span> UInt32
<span class="hljs-keyword">Dim</span> IndexName <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> DocName <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> LinkFileName <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> BinaryString <span class="hljs-keyword">As</span> <span class="hljs-built_in">String</span>
<span class="hljs-keyword">Dim</span> LastAccessTime <span class="hljs-keyword">As</span> <span class="hljs-built_in">Date</span>
<span class="hljs-keyword">Dim</span> LinkFileInfo <span class="hljs-keyword">As</span> RecentLinkFileInfo
End <span class="hljs-keyword">Structure</span>
Nun lokalisieren Sie die Daten in der Registry. Sie werden benutzerspezifisch im Zweig Computer\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer
\RecentDocs verwaltet. Die vorhandenen Einträge sind zwar nicht geordnet, aber fortlaufend nummeriert. Die Daten sind binär verschlüsselt. Im Registrierschlüssel MRUListEx werden – ebenfalls binär verschlüsselt – die zuletzt angewählten Einträge zusammengestellt, und zwar in der Reihenfolge des letzten Zugriffs, vergleiche Bild 5.
\RecentDocs verwaltet. Die vorhandenen Einträge sind zwar nicht geordnet, aber fortlaufend nummeriert. Die Daten sind binär verschlüsselt. Im Registrierschlüssel MRUListEx werden – ebenfalls binär verschlüsselt – die zuletzt angewählten Einträge zusammengestellt, und zwar in der Reihenfolge des letzten Zugriffs, vergleiche Bild 5.

Binär verschlüsselte Informationen zu den zuletzt geöffneten Ordnern und Dateien (Bild 5)
Autor
Mit diesem Grundwissen können Sie sich nun der eigentlichen Analyse zuwenden.Jetzt werden die MRU-Dokumente analysiert (MRU = Most Recently Used). Als String-Parameter CurrentUserSubKey wird der Registrierzweig an die Funktion übergeben. Funktionsergebnis ist ein Ganzzahldatenfeld, hinter dem sich die Schlüsselnamen der Einträge verbergen (Listing 4).
Listing 4: Binäre MRU-Listen auswerten
<span class="hljs-keyword">Function</span> GetMRUListEx(<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">If</span> RecentDocs_UserKey <span class="hljs-keyword">IsNot</span> <span class="hljs-literal">Nothing</span> <span class="hljs-keyword">Then</span><br/> <span class="hljs-keyword">Dim</span> EntryList <span class="hljs-keyword">As</span> <span class="hljs-built_in">Byte</span>() = <br/> RecentDocs_UserKey.GetValue( <span class="hljs-string">"MRUListEx"</span>, <span class="hljs-literal">Nothing</span>)<br/> <span class="hljs-keyword">If</span> EntryList <span class="hljs-keyword">IsNot</span> <span class="hljs-literal">Nothing</span> <span class="hljs-keyword">Then</span><br/> <span class="hljs-keyword">Dim</span> Entries <span class="hljs-keyword">As</span> <span class="hljs-built_in">Integer</span> = EntryList.Length / <span class="hljs-number">4</span><br/> <span class="hljs-keyword">Dim</span> StartIndex <span class="hljs-keyword">As</span> <span class="hljs-built_in">Integer</span> = <span class="hljs-number">0</span><br/> <span class="hljs-keyword">For</span> x = <span class="hljs-number">0</span> <span class="hljs-keyword">To</span> Entries - <span class="hljs-number">1</span><br/> Counter = Counter + <span class="hljs-number">1</span><br/> <span class="hljs-keyword">Dim</span> FourBytes(<span class="hljs-number">0</span> <span class="hljs-keyword">To</span> <span class="hljs-number">3</span>) <span class="hljs-keyword">As</span> <span class="hljs-built_in">Byte</span><br/> Array.Copy(EntryList, StartIndex, FourBytes, <span class="hljs-number">0</span>, <span class="hljs-number">4</span>)<br/> <span class="hljs-keyword">Dim</span> Index <span class="hljs-keyword">As</span> UInt32 = <br/> BitConverter.ToUInt32(FourBytes, <span class="hljs-number">0</span>)<br/> <span class="hljs-keyword">If</span> Index &lt; <span class="hljs-number">4294967295</span> <span class="hljs-keyword">Then</span><br/> <span class="hljs-keyword">ReDim</span> <span class="hljs-keyword">Preserve</span> Elements(Counter)<br/> Elements(Counter) = Index<br/> <span class="hljs-keyword">End</span> <span class="hljs-keyword">If</span><br/> StartIndex = StartIndex + <span class="hljs-number">4</span><br/> <span class="hljs-keyword">Next</span><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">Return</span> Elements<br/>End <span class="hljs-keyword">Function</span>
Nachdem der Registrierschlüssel CurrentUserSubKey mit CurrentUser.OpenSubKey geöffnet wurde, wird der binäre Wert zum Schlüsselnamen MRUListEx in das Byte-Datenfeld EntryList übernommen, um dann die detaillierte Analyse des Datenfelds zu starten. Über die MRU-Liste werden die Nummern für die Reihenfolge bestimmt, wobei jede einzelne Nummer in vier Bytes verschlüsselt ist (DWord). Die Anzahl der Einträge ist die Byte-Anzahl geteilt durch den Wert 4. Die letzten vier Byte (FFFF) markieren das Ende der Auflistung. Anschließend werden alle Einträge per For-Schleife durchlaufen. Die aktuell für einen Eintrag benötigten Bytes werden mit Array.Copy aus dem gesamten Byte-Datenfeld in das Byte-Datenfeld FourBytes kopiert.Die abgespaltenen Bytes werden dann in eine Ganzzahl des Typs UInt32 umgewandelt und in die Variable Index übernommen. Wird dieser Wert später wieder in eine Zeichenkette konvertiert, entspricht er dem Schlüsselnamen des Eintrags. Jedes neue Element wird dem Datenfeld Elements per Anweisung Redim Preserve ohne Wertverlust angefügt, Bei jedem Wert wird geprüft, ob die Ende-Kennung (FFFF = 4294967295) bereits erreicht wurde. In diesem Fall wird die Verarbeitung abgebrochen. Das Datenfeld mit allen Indexwerten wird letztlich an das aufrufende Programm zurückgegeben.Nicht alle Einträge, die sich im Registrierschlüssel befinden, werden auch in der MRUListEx-Zusammenstellung aufgeführt. Aus diesem Grund wird die Funktion GetNonMRUListExElements definiert, welche die nicht berücksichtigten Einträge offenlegt. Wie das umgesetzt wird, erfahren Sie in der kommenden Ausgabe der dotnetpro. Dann wird auch gezeigt, wie Sie die in der Registry vermerkten Daten zu den zuletzt geöffneten Programmen auslesen und anzeigen.
Fussnoten
- Andreas Maslo, User History Management, dotnetpro 10/2023, Seite 134 ff., http://www.dotnetpro.de/A2310BasicInstinct