Ad Space here

Welcome Guest! To enable all features please Login or Register.

Notification

Icon
Error

Share
Options
Go to last post Go to first unread
Dustin Higgins  
#1 Posted : Sunday, October 13, 2019 10:40:24 PM(UTC)
Dustin Higgins

Rank: Advanced Member

Groups: Registered
Joined: 7/1/2018(UTC)
Posts: 64
_United States

Thanks: 1 times
Was thanked: 6 time(s) in 6 post(s)

Do you want to use #PowerShell to parse the Outlook AutoComplete file?

I went on a binary bender over the weekend for reasons I'm still not sure of.   I set off on a quest to parse email addresses out of the Outlook AutoComplete file.  There are compiled 3rd party tools to do this, however I was bound and determined to conquer this quest with #PowerShell.

I went into work on Monday and told a coworker "Guess what I did over the weekend?"  I proceeded to tell him and he just started busted out laughing.  I interrupted his outburst and told him "I have no words that can make this even the slightest bit ok."

Anyway, it is not that much code if you understand the format of the file.  I found a nk2 spec, started converting bytes to hex, recognized some patterns and went to town.  Don't get me wrong, there were long periods of me just staring at hexadecimal strings in my PowerShell window.  Sure enough, the spec seems to work for the latest versions of Outlook.  Looking back, I have learned a ton.  I learned about Format-Hex which is very awesome.  I've also learned how to search for text strings in the Windows registry that are stored in binary values.  That one is for another day.

There is 1 supporting function that converts bytes to hex.  The other function returns the "AutoComplete" PowerShell custom object that has the following methods:

1. ReadFile (Takes a file path as input and reads the file)

2. ParseFile (Parses the binary AutoComplete file)

3. WriteFile (Takes a file path as input and writes a new file)

4. RemoveRow (Takes an email address as input)

5. AddByteArray (Only used by WriteFile)

Here is the supporting function:

Code:

function Convert-Byte2Hex {     <#     .SYNOPSIS     Function to return a Hex String from a Byte Array     .DESCRIPTION     Function to return a Hex String from a Byte Array     #>     param (         [Parameter(Mandatory=$true)]         $ByteArray     )     # Set the string to blank     $BinaryString = ""     # Loop throught the array of bytes     foreach ($i in $ByteArray) {         # Add the Hex to the $BinaryString variable         $BinaryString += $i.ToString("X2")     }     # Return the BinaryString     return $BinaryString }

Here is the function that returns the object that does all the work:

Code:

function Get-AutoCompleteObj {     <#     .SYNOPSIS     Function to build an object that parses the Outlook Auto Complete file     .DESCRIPTION     Function to build an object that parses the Outlook Auto Complete file     #>     # Create the object     $Obj = [PSCustomObject]@{             AutoCompleteFile = $null         Contents = $null         NewContents = New-Object "System.Collections.Generic.List[byte]"         RowList = New-Object "System.Collections.Generic.List[psobject]"         MetaDataBegin = $null         MetaDataEnd = $Null         NumberOfRows = $null         NumberOfRowsInt = $null         PropertyTag = $null         Exception = $null         ObjTimestamp = Get-Date     }     # Add the ReadFile Method     $Obj | Add-Member -Name "ReadFile" -MemberType ScriptMethod -Value {         param (             [Parameter(Mandatory=$true)]             [system.io.fileinfo]$File         )         try {             $this.AutoCompleteFile = $File             # Read the file contents             $this.Contents = [System.Io.File]::ReadAllBytes($File)         } catch {             Write-Host $_.Exception.Message -ForegroundColor Red             $this.Exception = $_         }     }     # Add the Parse Method     $Obj | Add-Member -Name "ParseFile" -MemberType ScriptMethod -Value {         try {             # Clear the RowList             $this.RowList.Clear()             # Read the beginning of the file             $this.MetaDataBegin = $this.Contents[0..11]             $this.NumberOfRows = $this.Contents[12..15]             $this.NumberOfRowsInt = [bitconverter]::ToInt32($this.NumberOfRows,0)             $ByteCounter = 16             Write-Host "NUMBER OF ROWS: $($this.NumberOfRowsInt)"             # Loop through all of the rows             for($i = 1 ; $i -le  $this.NumberOfRowsInt; $i++) {                 Write-Host "PARSING ROW: $($i.ToString())"                 # Create a row obj                 $RowObj = [PSCustomObject]@{                     StartIndex = $ByteCounter                     PropertyCount = $null                     PropertyCountInt = $null                     Email = $null                     PropertyList = New-Object "System.Collections.Generic.List[psobject]"                     ObjTimestamp = Get-Date                 }                 $RowObj.PropertyCount = $this.Contents[$ByteCounter..($ByteCounter+3)]                 $ByteCounter += 4                 $RowObj.PropertyCountInt = [bitconverter]::ToInt32($RowObj.PropertyCount,0)                 # Loop through all of the properties                 for ($p = 1 ; $p -le $RowObj.PropertyCountInt; $p ++) {                     # Create a property obj                     $PropObj = [PSCustomObject]@{                             Number = $p                         Tag = $null                         TagBinary = $null                         Reserved = $null                         Bytes = $null                         BytesInt = 0                         Union = $null                         Value = $null                         ValueStr = $null                         ObjTimestamp = Get-Date                     }                     $PropObj.Tag = $this.Contents[$ByteCounter..($ByteCounter+3)]                     $ByteCounter += 4                     $PropObj.TagBinary = Convert-Byte2Hex $PropObj.Tag                     $PropObj.Reserved = $this.Contents[$ByteCounter..($ByteCounter+3)]                     $ByteCounter += 4                     $PropObj.Union = $this.Contents[$ByteCounter..($ByteCounter+7)]                     $ByteCounter += 8                     # Starts with 1F is a string - 02 is binary.  03 tags don't have variable size fields                     if ($PropObj.TagBinary -match "\A1F" -or $PropObj.TagBinary -match "\A02") {                         # Process the fields that have a variable size                         $PropObj.Bytes = $this.Contents[$ByteCounter..($ByteCounter+3)]                         $ByteCounter += 4                         $PropObj.BytesInt = [bitconverter]::ToInt32($PropObj.Bytes,0)                         $PropObj.Value =  $this.Contents[$ByteCounter..($ByteCounter+$PropObj.BytesInt-1)]                         $ByteCounter = $ByteCounter + $PropObj.BytesInt                         # Starts with 1F is a string                         if ($PropObj.TagBinary -match "\A1F") {                             $NewString = New-Object Text.StringBuilder                             foreach ($b in $PropObj.Value) {                                 $Bin = $b.ToString("X2")                                 if ($Bin -ne "00") {                                     $NewString.Append([char]$b) > $null                                 }                             }                             $PropObj.ValueStr = $NewString.ToString()                             if ($PropObj.TagBinary -eq "1F000160") {                                 $RowObj.Email = $PropObj.ValueStr                             }                         }                     }                     # Add the Property to the Row                     $RowObj.PropertyList.Add($PropObj)                 }                 # Add the Row to the Object                 $this.RowList.Add($RowObj)             }             # Read the final Metadata             $this.MetaDataEnd = $this.Contents[$ByteCounter..($ByteCounter+11)]             $ByteCounter = $ByteCounter + 12         } catch {             Write-Host $_.Exception.Message -ForegroundColor Red             $this.Exception = $_         }     }     # Add the WriteFile Method     $Obj | Add-Member -Name "WriteFile" -MemberType ScriptMethod -Value {         param (             [Parameter(Mandatory=$true)]             [string]$FilePath         )         try {             # Clear the byte array             $this.NewContents.Clear()             # Add the begining metadata             $this.AddByteArray($this.MetaDataBegin)             $this.AddByteArray($this.NumberOfRows)             # Loop through the rows             foreach ($i in $this.RowList) {                 $this.AddByteArray($i.PropertyCount)                 # Loop through all of the properties                 foreach ($p in $i.PropertyList) {                     $this.AddByteArray($p.Tag)                     $this.AddByteArray($p.Reserved)                     $this.AddByteArray($p.Union)                     if ($p.TagBinary -match "\A1F" -or $p.TagBinary -match "\A02") {                         $this.AddByteArray($p.Bytes)                         $this.AddByteArray($p.Value)                     }                 }             }             # Add the end metadata             $this.AddByteArray($this.MetaDataEnd)             # write the output file             [System.Io.File]::WriteAllBytes($FilePath, $this.NewContents)         } catch {             Write-Host $_.Exception.Message -ForegroundColor Red             $this.Exception = $_         }     }     # Add the RemoveRow Method     $Obj | Add-Member -Name "RemoveRow" -MemberType ScriptMethod -Value {         param (             [Parameter(Mandatory=$true)]             [string]$Email         )         # Set the Count to return         $Count = 0         try {             Write-Host "Checking to remove: $email"             # Loop through all of the rows             for ($i = 0 ; $i -lt $this.RowList.Count; $i ++) {                 if ($this.RowList[$i].PropertyList[0].ValueStr -eq $Email) {                     Write-Host "Removing: $email Row: $(($i+1).ToString())"                     # Bump the count                     $Count = $Count + 1                     # Remove it from the Row List                     $this.RowList.Remove($this.RowList[$i])                     # Decrement the number of Rows                     $this.NumberOfRowsInt = $this.NumberOfRowsInt - 1                     Write-Host "New Number Of Rows: $($this.NumberOfRowsInt.ToString())"                     # Change the byte array                     $this.NumberOfRows = [System.BitConverter]::GetBytes($this.NumberOfRowsInt)                 }             }         } catch {             Write-Host $_.Exception.Message -ForegroundColor Red             $this.Exception = $_         }         # Return the Count         Return $Count     }     # Add the WriteFile Method     $Obj | Add-Member -Name "AddByteArray" -MemberType ScriptMethod -Value {         param (         [Parameter(Mandatory=$true)]             $bytes = $null         )         try {             foreach ($b in $bytes) {                 $this.NewContents.Add($b)             }         } catch {             Write-Host $_.Exception.Message -ForegroundColor Red             $this.Exception = $_         }     }     # Return the object     Return $Obj }

Here is an example how to use it:

Code:

# Get the object $OutlookAcObj = Get-AutoCompleteObj # Read the file $AutoCompleteFile = Get-Item ($env:USERPROFILE + "\AppData\Local\Microsoft\Outlook\RoamCache\Stream_Autocomplete*dat") | sort -Property LastWriteTime | Select-Object -Last 1  $OutlookAcObj.ReadFile($AutoCompleteFile) if ($OutlookAcObj.Exception -ne $null) {     write-host "Error reading the file: Exiting ..." -ForegroundColor Red     if ($OutlookAcObj.Exception.Exception.Message -match "used\sby\sanother") {         write-host "Make sure Outlook is closed!" -ForegroundColor Red     }     exit } # Parse the file $OutlookAcObj.ParseFile() # Remove an email address $OutlookAcObj.RemoveRow("binarybender@dhb-scripting.com") # Write a new file $OutlookAcObj.WriteFile($OutlookAcObj.AutoCompleteFile.FullName) $OutlookAcObj

If you got this far and like it, feel free to tell me by sending an email to binarybender@dhb-scripting.com

Feedback is welcome and follow Dustin Higgins on Twitter

Thanks!

Dustin

Edited by user Tuesday, October 15, 2019 1:36:35 PM(UTC)  | Reason: Not specified

thanks 1 user thanked Dustin Higgins for this useful post.
Allan Berg on 10/29/2019(UTC)
Sponsor
Ad Space here
Rss Feed  Atom Feed
Users browsing this topic
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.