Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 7 Min.

Windows Document History

Nicht nur Webbrowser zeichnen Verlaufsdaten auf, sondern auch Windows.
© dotnetpro
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 Selected­ImageIndex ü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 eLink­Type zusammengefasst. Kann kein Verbindungstyp zugeordnet werden, wird diesem der Typ NoFolderFile zugewiesen.

Enum eLinkType
  Folder
  File
  NoFolderFile
End Enum 
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.

Structure RecentLinkFileInfo
  Dim Name As String
  Dim LinkFileName As String
  Dim Folder As String
  Dim WorkingFolder As String
  Dim Description As String
  Dim IconLocation As String
  Dim WindowStyle As String
  Dim CreationDate As Date
  Dim Type As eLinkType
End Structure 
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() As _  RecentLinkFileInfo()
  Dim LnkInfos As RecentLinkFileInfo() = Nothing
  Dim Counter As Integer = -1
  ' bezogen auf angemeldeten Benutzer
  Dim ActualUser As String = Environment.UserName
  ' Datenverzeichnis mit den Informationen
  Dim DataFolder As String = "C:\Users\" & 
    ActualUser & "\AppData\Roaming\Microsoft\    Windows\Recent"
  If Directory.Exists(DataFolder) Then
    ' Verweise sind als Verknüpfungen gespeichert
    Dim RecentFiles As String() = Directory.GetFiles(
      DataFolder, "*.lnk",       SearchOption.AllDirectories)
    For Each rFile In RecentFiles
      Counter = Counter + 1
      ReDim Preserve LnkInfos(Counter)
      LnkInfos(Counter) = GetLinkFileInfo(rFile)
    Next
  End If
  ' Datenfeld nach Datum sortieren (aufsteigend)
  Array.Sort(LnkInfos,     AddressOf CompareRecentLinkFileInfos)
  Array.Reverse(LnkInfos) ' Sortierung umkehren
  Return LnkInfos
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
Function GetLinkFileInfo(ByVal LinkFileWithPath _  As String) As RecentLinkFileInfo
  Dim LnkInfo As New RecentLinkFileInfo
  Try
    Dim wShell As IWshShell =       New IWshRuntimeLibrary.WshShell()
    Dim wLink As IWshShortcut = DirectCast(
      wShell.CreateShortcut(LinkFileWithPath),       IWshShortcut)
    With wLink
      LnkInfo.Name = .FullName.Substring(
        .FullName.LastIndexOf("\") + 1).Replace(
        ".lnk", "")
      LnkInfo.LinkFileName = .FullName
      LnkInfo.Folder = .TargetPath
      LnkInfo.WorkingFolder = .WorkingDirectory
      LnkInfo.Description = .Description
      LnkInfo.IconLocation = .IconLocation
      LnkInfo.WindowStyle = .WindowStyle
      Dim fi As New FileInfo(LinkFileWithPath)
      LnkInfo.CreationDate = fi.LastWriteTime
      LnkInfo.Type = eLinkType.NoFolderFile
      If .TargetPath <> "" Then
        If Directory.Exists(.TargetPath) Then
          LnkInfo.Type = eLinkType.Folder
        ElseIf IO.File.Exists(.TargetPath) Then
          LnkInfo.Type = eLinkType.File
        Else
          LnkInfo.Type = eLinkType.NoFolderFile
        End If
      End If
      Return LnkInfo
    End With
  Catch ex As Exception
    Return Nothing
  End Try
End Function 
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.

Private Function CompareRecentLinkFileInfos( 
  ByVal LnkFileInfo1 As RecentLinkFileInfo,
  ByVal LnkFileInfo2 As RecentLinkFileInfo) As Integer
  Return LnkFileInfo1.CreationDate.CompareTo(
    LnkFileInfo2.CreationDate)
End Function 

Infos im Recent-Ordner anzeigen

Nachdem die Informationen des Recent-Ordners ausgelesen sind, können Sie diese zeitbasiert in das Verlaufsdatensteuer­element ü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 Date­NodeExist prüfen Sie, ob ein Datumsknoten existiert. Wenn ja, wird er mit GetDateNode ermittelt. Der Zielknoten wird über die Variable DestinationNode vom Typ HistoryTree­Node verwaltet und um neue, mit einem typspezifischen Symbol gekennzeichnete Eintragsknoten (NewEntryNode) erweitert (Listing 3). ­Eine exem­plarische Inhaltsausgabe zeigt Bild 4.
Listing 3: Zuletzt geöffnete Ordner und Dateien ausgeben (Teil 1)
Sub ShowFilesAndFolders(  ByVal DateTimeNode As HistoryTreeNode)
  Dim rlfi As RecentLinkFileInfo() = 
    GetLastRecentFilesAndFolders()
  For Each Entry As RecentLinkFileInfo In rlfi
    With Entry
      Dim EntryDate As String = 
        .CreationDate.ToShortDateString
      If .Folder <> "" Then
        ' Zielknoten wählen/anlegen
        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
        If .Type = eLinkType.Folder Then  ' Ordner
          With NewEntryNode
            .Text = Entry.Name
            .ImageIndex = 3
            .SelectedImageIndex = 3
            Dim di As New DirectoryInfo(Entry.Folder)
            .Description =               "Gültiger Verlaufseintrag [Ordner]"
            .NodeDate =               di.LastAccessTime.ToShortDateString
            .NodeTime =               di.LastAccessTime.ToShortTimeString
            .NodeType = "Verzeichnis"
            .UrlOrFileOrFolder = Entry.Folder
          End With
        ElseIf .Type = eLinkType.File Then  ' Datei
          With NewEntryNode
            .Text = Entry.Name
            .ImageIndex = 4
            .SelectedImageIndex = 4
            .Tag = Entry.Folder
            Dim fi As New FileInfo(Entry.Folder)
            .Description =               "Gültiger Verlaufseintrag [Datei]"
            .NodeDate =               fi.LastAccessTime.ToShortDateString
            .NodeTime =               fi.LastAccessTime.ToShortTimeString
            .NodeType = "Datei"
            .UrlOrFileOrFolder = Entry.Folder
          End With
        ElseIf .Type = eLinkType.NoFolderFile Then
          With NewEntryNode
            .Text = Entry.Name
            .ImageIndex = 8
            .SelectedImageIndex = 8
            .Tag = Entry.Folder
            Dim fDateTime As Date = Nothing
            Dim fType As eNameType =               TypeOfName(Entry.Folder)
            Dim fTypeName As String = Nothing
            If fType = eNameType.Folder Then
              Dim di As New DirectoryInfo(                Entry.Folder)
              fDateTime = di.LastAccessTime
              fTypeName = "Verzeichnis"
            ElseIf fType = eNameType.File Then
              Dim fi As New FileInfo(Entry.Folder)
              fDateTime = fi.LastAccessTime
              fTypeName = "Datei"
            Else
              fTypeName = "unbekannt"
              fDateTime = DateTime.Now
            End If
            .Description = "Derzeit ungültiger               Verlaufseintrag [Ordner/Datei]"
            .NodeDate = fDateTime.ToShortDateString
            .NodeTime = fDateTime.ToShortTimeString
            .NodeType = fTypeName
            .UrlOrFileOrFolder = Entry.Folder
          End With
        End If
        If ShowNoAccessableFilesAndFolders And 
          .Type = eLinkType.NoFolderFile Or           .Type = eLinkType.Folder Or
          .Type = eLinkType.File Then
            If NewHistoryNodeAlreadyExists(
              DestinationDateNode, NewEntryNode) =               False Then
                DestinationDateNode.Nodes.Add(                  NewEntryNode)
            End If
        End If
      End If
    End With
  Next
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.

Enum eNameType
  Folder
  File
  None
End Enum 
Die Funktion TypeOfName ermittelt anhand des übergebenen Suchpfads FileOrFolderName, ob es sich um einen Ordner, eine Datei oder ein nicht existierendes Element handelt.

Function TypeOfName(
  ByVal FileOrFolderName As String) As eNameType
  Dim Result As eNameType = eNameType.None
  If IO.File.Exists(FileOrFolderName) Then
    Result = eNameType.File
  ElseIf Directory.Exists(FileOrFolderName) Then
    Result = eNameType.Folder
  End If
  Return Result
End Function 

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 Zeichen­kette (BinaryString) sowie die letzte Zugriffszeit (LastAccess­­Time) in die Datenstruktur aufgenommen.

Public Structure RecentDocFolderInfos
  Dim Counter As Integer
  Dim IndexValue As UInt32
  Dim IndexName As String
  Dim DocName As String
  Dim LinkFileName As String
  Dim BinaryString As String
  Dim LastAccessTime As Date
  Dim LinkFileInfo As RecentLinkFileInfo
End Structure 
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.
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
Function GetMRUListEx(
  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)
  If RecentDocs_UserKey IsNot Nothing Then
    Dim EntryList As Byte() = 
      RecentDocs_UserKey.GetValue(      "MRUListEx", Nothing)
    If EntryList IsNot Nothing Then
      Dim Entries As Integer = EntryList.Length / 4
      Dim StartIndex As Integer = 0
      For x = 0 To Entries - 1
        Counter = Counter + 1
        Dim FourBytes(0 To 3) As Byte
        Array.Copy(EntryList, StartIndex,           FourBytes, 0, 4)
        Dim Index As UInt32 = 
          BitConverter.ToUInt32(FourBytes, 0)
        If Index < 4294967295 Then
          ReDim Preserve Elements(Counter)
          Elements(Counter) = Index
        End If
        StartIndex = StartIndex + 4
      Next
    End If
  End If
  Return Elements
End Function 
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.
Projektdateien herunterladen

Fussnoten

  1. Andreas Maslo, User History Management, dotnetpro 10/2023, Seite 134 ff., http://www.dotnetpro.de/A2310BasicInstinct

Neueste Beiträge

KI lässt Entwickler ihre Leidenschaft zum Programmieren neu entdecken - Motivation
Softwareentwicklung ist gleich Spaßfreie Zone? Das muss nicht sein: Der Beitrag beleuchtet, wie Teams ihren Kopf wieder freibekommen und ihre Freude am Entwickeln neu entdecken.
5 Minuten
10. Jul 2025
Spotlight #1: Azure IoT Operations, Video Teil 2/3 - DWX Spotlight
Das erste DWX Spotlight mit Special Guest Florian Bader. Im Teil 2 des Videos erklärt Florian unter anderem, wie es mit der Sicherheit bei Industrieanlagen und Messdatenerfassung aussieht.
2 Minuten
10. Jul 2025
Miscellaneous

Das könnte Dich auch interessieren

Interaktiver 3D-Editor in C# - CodeProject, Elmue
Das einfach zu verwendende 3D-Steuerelement (Windows Forms), kann in wenigen Minuten in eine Anwendung integriert werden.
2 Minuten
Entwicklung von Linux-Anwendungen mit WSL - Linux
Mit der Kombination von Microsoft Dev Box und dem Windows Subsystem für Linux entwickeln Sie Anwendungen für Linux-Systeme.
3 Minuten
1. Okt 2024
Design mit WinUI 3 - Design moderner Apps, Teil 4
Das Erscheinungsbild einer App wird wesentlich durch Farben, Materialien, Kontrast und Animationen bestimmt. WinUI 3 setzt dabei umfassend auf das Fluent Design.
14 Minuten
13. Nov 2023
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige