Class action lawsuits come with months, if not years, of effort to collect attorney’s fees out of settlements. Increasingly lawyers find themselves in need of processing thousands of email messages to account for their time. Despite the capabilities of eDiscovery within mail systems, the reality is that us – people – tend to store email in a variety of ways. One of our clients in Miami just had to go through something like this, and like many attorneys he was unsure what to do next. He new he needed to review a thousands of emails (as did his co-counsels), assign billing to them, and submit them. However, all that came with a risk. Surely, among the various messages there are comments, reactions, and thoughts that could be misconstrued. For this, the emails would need to be redacted.
For anyone who has done eDiscovery work, it’s opposite of redaction. For anyone who has done redactive work, it’s usually not something one does to the entire body of all emails like this request shaped out to be. Fortunately, the problem reached one of our solution consultants who had a simple answer: eDiscovery and an export to Excel.
During the information gathering stage we learned that the data was stored in a prepared Personal Folder Files (PSTs) and possibly in a case management software in the form of individual EML files. After further digging we were able to translate the vision of the attorneys involved into a technical answer: a spreadsheet that contains the relevant metadata and subjects of each message. That would be sufficient to do the job. To do this quickly we spun up an Amazon Web Services instance with plenty of horsepower and got busy putting together some code.
Now, experience has taught us that you have to be ready for the unexpected. There was agreement that there is likely more to come out of this, so we prepared a very simple Outlook Macro to save each highlighted message as a row in an Excel document. Ran it, tested it, and submitted it within a few hours. At the time we had a budget of $3,500 and 1 week. At about 2 billable hours in we had results that seemingly satisfied the attorneys. Cool.
Apparently the results impressed because the scope grew exponentially. Turned out that the PST file we worked with was just the tip of the iceberg, and the real treasure trove of emails was in the lead attorneys mailbox. Not organized or structured – just as a lonely search result in an open Outlook window. Now, the trouble with this kind of thing is that Outlook searches depend on cached mode, Windows indexing, and the oomph of the machine. With a 50Gb mailbox, a 3 month cache, and aging equipment none of that was going to work. Queue Amazon Web Services once again along with Microsoft Office 365 eDiscovery.
Because this firm was already a managed services client we had our full array of site and cloud application security tools deployed. This meant proper Office 365 plans, authorized access, and the means to execute. We performed eDiscovery searches in lieu of the Outlook search being used and waited for the results. Soon enough those arrived, and were transferred – in PST form – to our AWS EC2 instances for further processing. There all we had to do was massage out the message corruption (and there was some when you deal with this much data) and create our spreadsheets.
The project was completed 4 days ahead of schedule and at about 30% of the original budget. Here is the Outlook Macro thad did the bulk of the heavy lifting:
Outlook VBS Macro
Option Explicit
Sub CopyToExcel()
Dim xlApp As Object
Dim xlWB As Object
Dim xlSheet As Object
Dim rCount As Long
Dim bXStarted As Boolean
Dim enviro As String
Dim strPath As String
Dim currentExplorer As Explorer
Dim Selection As Selection
Dim olItem As Outlook.MailItem
Dim obj As Object
Dim strColA, strColB, strColC, strColD, strColE, strColF, strColG, strColH, strColI, strColJ, strColK, strColL, strColM, strColN, strColO, strColP, strColQ, strColR, strColS, strColT, strColU, strColV, strColW, strColX, strColY, strColZ, strColAA, strColAB, strColAC, strColAD, strColAE, strColAF, strColAG, strColAH, strColAI, strColAJ, strColAK, strColAL, strColAM, strColAN, strColAO, strColAP, strColAQ As String
' Get Excel set up
On Error Resume Next
Set xlApp = GetObject(, "Excel.Application")
If Err <> 0 Then
Application.StatusBar = "Please wait while Excel source is opened ... "
Set xlApp = CreateObject("Excel.Application")
bXStarted = True
End If
On Error GoTo 0
'## Open a specific workbook to input the data
'the path of the workbook under the windows user account
'enviro = CStr(Environ("USERPROFILE"))
' strPath = enviro & "\Documents\test.xlsx"
' Set xlWB = xlApp.Workbooks.Open(strPath)
' Set xlSheet = xlWB.Sheets("Sheet1")
'## End Specific workbook
'## Use New Workbook
Set xlWB = xlApp.Workbooks.Add
Set xlSheet = xlWB.Sheets("Sheet1")
'## end use new workbook
' Add column names
xlSheet.Range("A1") = "Sent"
xlSheet.Range("B1") = "SentOn"
xlSheet.Range("C1") = "Sender Name"
' xlSheet.Range("D1") = "Sender Address"
xlSheet.Range("E1") = "Subject"
xlSheet.Range("F1") = "To"
xlSheet.Range("G1") = "Recipients"
xlSheet.Range("H1") = "CC"
xlSheet.Range("I1") = "BCC"
xlSheet.Range("J1") = "Size"
xlSheet.Range("K1") = "Attachments"
xlSheet.Range("L1") = "Importance"
xlSheet.Range("M1") = "Message Class"
xlSheet.Range("N1") = "Creation Time"
xlSheet.Range("O1") = "Received Time"
xlSheet.Range("P1") = "Class"
xlSheet.Range("Q1") = "Parent"
xlSheet.Range("R1") = "Read Receipt Requested"
xlSheet.Range("S1") = "ReceivedByEntryID"
xlSheet.Range("T1") = "Received By Name"
xlSheet.Range("U1") = "ReceivedOnBehalfOfEntryID"
xlSheet.Range("V1") = "RecivedOnBehalfOfName"
xlSheet.Range("W1") = "Billing Info"
xlSheet.Range("X1") = "Categories"
xlSheet.Range("Y1") = "AutoForwarded"
xlSheet.Range("Z1") = "ConversationID"
xlSheet.Range("AA1") = "ConversationIndex"
xlSheet.Range("AB1") = "ConversationTopic"
xlSheet.Range("AC1") = "DeferredDeliveryTime"
' xlSheet.Range("AD1") = "BodyFormat"
' xlSheet.Range("AE1") = "Body"
' xlSheet.Range("AF1") = "HTMLBody"
xlSheet.Range("AG1") = "lastModTime"
xlSheet.Range("AH1") = "Mileage"
xlSheet.Range("AI1") = "OutlookInternalVersion"
xlSheet.Range("AJ1") = "OutlookVersion"
xlSheet.Range("AK1") = "ReplyRecipientNames"
' xlSheet.Range("AL1") = "RTFBody"
xlSheet.Range("AM1") = "SenderEmailType"
xlSheet.Range("AN1") = "SendUsingAccount"
xlSheet.Range("AO1") = "Sensitivity"
xlSheet.Range("AP1") = "Unread"
xlSheet.Range("AQ1") = "UserProperties"
' Process the message record
On Error Resume Next
'Find the next empty line of the worksheet
rCount = xlSheet.Range("A" & xlSheet.Rows.Count).End(-4162).Row
'needed for Exchange 2016. Remove if causing blank lines.
rCount = rCount + 1
' get the values from outlook
Set currentExplorer = Application.ActiveExplorer
Set Selection = currentExplorer.Selection
For Each obj In Selection
Set olItem = obj
'collect the fields
strColA = olItem.Sent
strColB = olItem.SentOn
strColC = olItem.SenderName
' strColD = olItem.SenderEmailAddress
strColE = olItem.Subject
strColF = olItem.To
strColG = olItem.Recipients
strColH = olItem.CC
strColI = olItem.BCC
strColJ = olItem.Size
strColK = olItem.Importance
strColL = olItem.Attachments
strColM = olItem.MessageClass
strColN = olItem.CreationTime
strColO = olItem.ReceivedTime
strColP = olItem.Class
strColQ = olItem.Parent
strColR = olItem.ReadReceiptRequested
strColS = olItem.ReceivedByEntryID
strColT = olItem.ReceivedByName
strColU = olItem.ReceivedOnBehalfOfEntryID
strColV = olItem.ReceivedOnBehalfOfName
strColW = olItem.BillingInformation
strColX = olItem.Categories
strColY = olItem.AutoForwarded
strColZ = olItem.ConversationID
strColAA = olItem.ConversationIndex
strColAB = olItem.ConversationTopic
strColAC = olItem.DeferredDeliveryTime
'Body Stuff
' strColAD = olItem.BodyFormat
' strColAE = olItem.Body
' strColAF = olItem.HTMLBody
strColAG = olItem.LastModificationTime
strColAH = olItem.Mileage
strColAI = olItem.OutlookInternalVersion
strColAJ = olItem.OutlookVersion
strColAK = olItem.ReplyRecipientNames
' strColAL = olItem.RTFBody
' Final Meta
strColAM = olItem.SenderEmailType
strColAN = olItem.SendUsingAccount
strColAO = olItem.Sensitivity
strColAP = olItem.UnRead
strColAQ = olItem.UserProperties
'### Get all recipient addresses
' instead of To names
Dim strRecipients As String
Dim Recipient As Outlook.Recipient
For Each Recipient In olItem.Recipients
strRecipients = Recipient.Address & "; " & strRecipients
Next Recipient
strColD = strRecipients
'### end all recipients addresses
'### Get the Exchange address
' if not using Exchange, this block can be removed
Dim olEU As Outlook.ExchangeUser
Dim oEDL As Outlook.ExchangeDistributionList
Dim recip As Outlook.Recipient
Set recip = Application.Session.CreateRecipient(strColB)
If InStr(1, strColB, "/") > 0 Then
' if exchange, get smtp address
Select Case recip.AddressEntry.AddressEntryUserType
Case OlAddressEntryUserType.olExchangeUserAddressEntry
Set olEU = recip.AddressEntry.GetExchangeUser
If Not (olEU Is Nothing) Then
strColB = olEU.PrimarySmtpAddress
End If
Case OlAddressEntryUserType.olOutlookContactAddressEntry
Set olEU = recip.AddressEntry.GetExchangeUser
If Not (olEU Is Nothing) Then
strColB = olEU.PrimarySmtpAddress
End If
Case OlAddressEntryUserType.olExchangeDistributionListAddressEntry
Set oEDL = recip.AddressEntry.GetExchangeDistributionList
If Not (oEDL Is Nothing) Then
strColB = olEU.PrimarySmtpAddress
End If
End Select
End If
' ### End Exchange section
'write them in the excel sheet
xlSheet.Range("A" & rCount) = strColA
xlSheet.Range("B" & rCount) = strColB
xlSheet.Range("C" & rCount) = strColC
' xlSheet.Range("D" & rCount) = strColD
xlSheet.Range("E" & rCount) = strColE
xlSheet.Range("F" & rCount) = strColF
xlSheet.Range("G" & rCount) = strColG
xlSheet.Range("H" & rCount) = strColH
xlSheet.Range("I" & rCount) = strColI
xlSheet.Range("J" & rCount) = strColJ
xlSheet.Range("K" & rCount) = strColK
xlSheet.Range("L" & rCount) = strColL
xlSheet.Range("M" & rCount) = strColM
xlSheet.Range("N" & rCount) = strColN
xlSheet.Range("O" & rCount) = strColO
xlSheet.Range("P" & rCount) = strColP
xlSheet.Range("Q" & rCount) = strColQ
xlSheet.Range("R" & rCount) = strColR
xlSheet.Range("S" & rCount) = strColS
xlSheet.Range("T" & rCount) = strColT
xlSheet.Range("U" & rCount) = strColU
xlSheet.Range("V" & rCount) = strColV
xlSheet.Range("W" & rCount) = strColW
xlSheet.Range("X" & rCount) = strColX
xlSheet.Range("Y" & rCount) = strColY
xlSheet.Range("Z" & rCount) = strColZ
xlSheet.Range("AA" & rCount) = strColAA
xlSheet.Range("AB" & rCount) = strColAB
xlSheet.Range("AC" & rCount) = strColAC
' xlSheet.Range("AD" & rCount) = strColAD
' xlSheet.Range("AE" & rCount) = strColAE
' xlSheet.Range("AF" & rCount) = strColAF
xlSheet.Range("AG" & rCount) = strColAG
xlSheet.Range("AH" & rCount) = strColAH
xlSheet.Range("AI" & rCount) = strColAI
xlSheet.Range("AJ" & rCount) = strColAJ
xlSheet.Range("AK" & rCount) = strColAK
' xlSheet.Range("AL" & rCount) = strColAL
xlSheet.Range("AM" & rCount) = strColAM
xlSheet.Range("AN" & rCount) = strColAN
xlSheet.Range("AO" & rCount) = strColAO
xlSheet.Range("AP" & rCount) = strColAP
xlSheet.Range("AQ" & rCount) = strColAQ
'Next row
rCount = rCount + 1
' size the cells
xlSheet.Columns("A:E").EntireColumn.AutoFit
' xlSheet.Columns("C:C").ColumnWidth = 100
' xlSheet.Columns("D:D").ColumnWidth = 30
xlSheet.Range("A2").Select
xlSheet.Columns("A:E").VerticalAlignment = xlTop
Next
xlApp.Visible = True
' to save but not close
'xlWB.Save
' to save and close
' xlWB.Close 1
' If bXStarted Then
' xlApp.Quit
' End If
' end save and close
Set olItem = Nothing
Set obj = Nothing
Set currentExplorer = Nothing
Set xlSheet = Nothing
Set xlWB = Nothing
Set xlApp = Nothing
End Sub