outlook vba regex on each mail item in array - regex

I am using the code below to create output showing how many emails were in a defined folder per day. This all works fine... My question is in the section with XXXXX, how do I reference each mail item so that I can do a regex for a word pattern? The end goal is to find out how many emails contained a keyword on a given day. The desired output is something like this:
,,
2015-01-01,15,2,5
2015-01-01,23,22,0
...
...
I'm ok to figure out the code on determining the number of emails based on the keyword, just not certain how to reference the email messages based on the code as is today...
Thanks for your advice.
Sub HowManyEmails()
Dim objOutlook As Object, objnSpace As Object, objFolder As MAPIFolder
Dim EmailCount As Integer
Set objOutlook = CreateObject("Outlook.Application")
Set objnSpace = objOutlook.GetNamespace("MAPI")
On Error Resume Next
Set objFolder = objnSpace.Folders("Personal Folders").Folders("Inbox").Folders("jobs.keep")
If Err.Number <> 0 Then
Err.Clear
MsgBox "No such folder."
Exit Sub
End If
EmailCount = objFolder.Items.Count
MsgBox "Number of emails in the folder: " & EmailCount, , "email count"
Dim dateStr As String
Dim myItems As Outlook.Items
Dim dict As Object
Dim msg As String
Set dict = CreateObject("Scripting.Dictionary")
Set myItems = objFolder.Items
myItems.SetColumns ("SentOn")
' Determine date of each message:
For Each myItem In myItems
xxxxxxx
xxxxxxx
xxxxxxx
dateStr = GetDate(myItem.SentOn)
If Not dict.Exists(dateStr) Then
dict(dateStr) = 0
End If
dict(dateStr) = CLng(dict(dateStr)) + 1
Next myItem
'Write output to file
Dim enviro As String
enviro = CStr(Environ("USERPROFILE"))
FILEPATH = enviro & "\Desktop\emails.csv"
Open FILEPATH For Output As 1
msg = ""
For Each o In dict.Keys
msg = msg & o & "," & dict(o) & vbCrLf
'MsgBox msg
Next
Print #1, msg
Close #1
'Write output to file
Set objFolder = Nothing
Set objnSpace = Nothing
Set objOutlook = Nothing
End Sub
Function GetDate(dt As Date) As String
GetDate = Year(dt) & "-" & Month(dt) & "-" & Day(dt)
End Function

You need to check the type of item in your code:
Dim myMailItem As Outlook.mailItem
....
For each myItem in myItems
If TypeOf myItem Is MailItem Then
Set myMailItem = myItem
XXXXXXXXXXX and rest of code here use myMailItem instead of myItem to get info
End If
Next myItem

First of all, I'd recommend using the Find/FindNext or Restrict methods of the Items class to find the subset of items that match to the specified condition. Iterating through all items in the folder may take a lot of time.
objnSpace.Folders("Personal Folders").Folders("Inbox")
Use the GetDefaultFolder method of the Namespace class to get a folder that represents the default folder of the requested type for the current profile.
Outlook uses EntryID values for identifying Outlook items uniquely. See Working with EntryIDs and StoreIDs for more information. If you know the IDs of an item and the folder it's stored in, you can directly reference the item using the NameSpace.GetItemFromID method.

Related

How to find a multiline match with RegEx in a text file using VBA?

I have a .txt report that I need to extract certain elements (using RegEx groups) that occur multiple times throughout the file. In order to capture the specific elements I need, I have to match the text in a textline against a pattern then look ahead to see if the next line matches a different pattern. If both are true, then return values of those groups into an array.
I am working in Excel 2013 using VBA and the RegExp library. I was able to write a multiline pattern in RegEx along with all the relevant fields I need in named groups. I am able to open a text file and load the data to a string. What I can't do is both at the same time. I've only been able to identify one line of text against one pattern.
Edit: Here is some sample text that has been redacted from the original source
DAILY CONTROL REPORT WORK OF: 08/07/19
ACQUIRING PAGE: 1
CUSTOMER : ACME CORP
CUSTOMER ID : 0000000001
Other miscellaneous data
DAILY CONTROL REPORT WORK OF: 08/07/19
ISSUING PAGE: 2
CUSTOMER : ACME CORP
CUSTOMER ID : 0000000001
Other miscellaneous data
DAILY CONTROL REPORT WORK OF: 08/07/19
NET SETTLEMENT SUMMARY PAGE: 3
CUSTOMER : ACME CORP
CUSTOMER ID : 0000000001
Other miscellaneous data
DAILY CONTROL REPORT WORK OF: 08/07/19
ACQUIRING PAGE: 4
CUSTOMER : ACME INC
CUSTOMER ID : 0000000002
Other miscellaneous data
DAILY CONTROL REPORT WORK OF: 08/07/19
ISSUING PAGE: 5
CUSTOMER : ACME INC
CUSTOMER ID : 0000000002
Other miscellaneous data
DAILY CONTROL REPORT WORK OF: 08/07/19
NET SETTLEMENT SUMMARY PAGE: 6
CUSTOMER : ACME INC
CUSTOMER ID : 0000000002
Other miscellaneous data
Dim rgx As Object
Set rgx = CreateObject("VBScript.RegExp")
Dim MyFolder As String
Dim MyFile As String
Dim TextLine As String
Dim strPattern As String
Dim strReport As String
Dim MyArray() As Variant
Dim i As Integer
MyFolder = "C:\Reports\Samples\"
MyFile = "MySampleFile.txt"
strPattern = "WORK OF: (?<WORKOF>\d\d\/\d\d\/\d\d)\s+\n\s+NET SETTLEMENT
SUMMARY.+\n.{22}(?<NAME>.+\b)\s+\n.{22}(?<NUM>\d+)\s+\n"
With rgx
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = strPattern
End With
Open (MyFolder & MyFile) For Input As #1
Do Until EOF(1)
Line Input #1, TextLine
strReport = strReport & TextLine
Loop
Close #1
i = 0
If rgx.Test(strReport) Then
ReDim Preserve MyArray(0 to i, 0 to 2)
MyArray(i, 0) = rgx.Replace(strReport, WORKOF)
MyArray(i, 1) = rgx.Replace(strReport, NAME)
MyArray(i, 2) = rgx.Replace(strReport, NUM)
i = i +1
End IF
There are a few things I know are wrong with this code:
1. The strReport string does not include CrLF at end of each line
2. The rgx.Test statement should loop through strReport looking for a
multiline match but the way it is written, it would always fail the test.
3. I am not sure how the array would be populated but it would end up with several hundred records, each with 3 values {WORKOF, NAME, NUM}
4. WORKOF is in the header of every page but the string "NET SETTLEMENT SUMMARY" only occurs about every 3rd or 4th page. That is where the information I want to capture resides.
I don't have your text files but I created a test input file with the following content:
WORK OF: 12/34/56 NET SETTLEMENT SUMMARY name1 789
WORK OF: 01/23/45 NET SETTLEMENT SUMMARY name1 6789
I could match the pattern with the following code:
Option Explicit
' Include: Tools > References > Microsoft VBScript Regular Expressions 5.5
Public Sub FindPatternInTextFile_Test()
Dim varResult As Variant: varResult = FindPatternInTextFile("C:\Reports\Samples\MySampleFile.txt", "WORK OF: (\d\d\/\d\d\/\d\d)\s+NET SETTLEMENT SUMMARY\s+(.+\b)\s+(\d+)")
End Sub
Public Function FindPatternInTextFile(strPath As String, strPattern As String) As Variant
Dim strContent As String: strContent = ReadWholeTextFile(strPath)
Dim rgxPattern As RegExp: Set rgxPattern = CreateRegex(strPattern)
Dim mtcFound As MatchCollection: Set mtcFound = rgxPattern.Execute(strContent)
If 0 < mtcFound.Count Then
Dim strResult() As String: ReDim strResult(0 To mtcFound.Count - 1, 0 To 2)
Dim i As Long: For i = 0 To mtcFound.Count - 1
strResult(i, 0) = mtcFound(i).SubMatches(0)
strResult(i, 1) = mtcFound(i).SubMatches(1)
strResult(i, 2) = mtcFound(i).SubMatches(2)
Next i
FindPatternInTextFile = strResult
Else
FindPatternInTextFile = vbNullString
End If
End Function
Public Function ReadWholeTextFile(strPath As String) As String
Dim strResult As String: strResult = vbNullString
Open strPath For Input As #1
Do Until EOF(1)
Dim strLine As String: strLine = vbNullString
Line Input #1, strLine
strResult = strResult & strLine & vbCrLf
Loop
Close #1
ReadWholeTextFile = strResult
End Function
Public Function CreateRegex(strPattern As String) As RegExp
Dim rgxResult As RegExp: Set rgxResult = New RegExp
With rgxResult
.Global = True
.MultiLine = True
.IgnoreCase = True
.Pattern = strPattern
End With
Set CreateRegex = rgxResult
End Function
You will have to customize the regex pattern based on your actual data.

Warn if outgoing Outlook message contains certain number patterns, i.e. SSN, credit card numbers

I have found multiple variations of this question but none that actually work for me.
What I need to do is check outgoing message in Outlook, before sending, to make sure there are no SSN's, credit card numbers, etc. It also needs to check different type patterns.
For example, someone may put SSN as 123-45-6789 or 123456789.
I have tried one solution that used an Excel file to list private information, modifying one that checked for a certain word in the subject line, etc.
The problem I have is searching the body & searching patterns instead of specific text.
Any help would be greatly appreciated.
I found a solution but I am simply repeating this for each pattern of numbers I am looking for. Is there a way to make a list of the different patterns & then check each item in the list?
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Dim myMailToSend As MailItem
Dim re As Object
Dim s As String
'****************************
Const sPat As String = "\b\d{3}-\d{2}-\d{4}\b"
Set myMailToSend = Item
Set re = CreateObject("vbscript.regexp")
re.Pattern = sPat
s = myMailToSend.Body & " " & myMailToSend.Subject
If re.Test(s) = True Then
strMsg = "The current item contains sensitive information. Do you still want to send it?"
nResponse = MsgBox(strMsg, vbExclamation + vbYesNo, "Check Sensitive Information")
If nResponse = vbYes Then
Cancel = False
Else
Cancel = True
End If
Else
End If
'****************************
Const tPat As String = "\b\d{2}-\d{7}\b"
Set myMailToSend = Item
Set re = CreateObject("vbscript.regexp")
re.Pattern = tPat
s = myMailToSend.Body & " " & myMailToSend.Subject
If re.Test(s) = True Then
strMsg = "The current item contains sensitive information. Do you still want to send it?"
nResponse = MsgBox(strMsg, vbExclamation + vbYesNo, "Check Sensitive Information")
If nResponse = vbYes Then
Cancel = False
Else
Cancel = True
End If
Else
End If
Use "|" OR operator
Example /(a|b)/ Matches the a or the b part
So your Pattern should be "\b\d{3}-\d{2}-\d{4}\b|\b\d{2}-\d{7}\b|\b\d{9}\b"
Demo - https://regex101.com/r/a1QaZb/1
And your code should look like the following-
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Dim myMailToSend As mailitem
Dim re As Object
Dim s As String
Dim strMsg As String
Dim nResponse As String
'****************************
Const sPat As String = "\b\d{3}-\d{2}-\d{4}\b|\b\d{2}-\d{7}\b|\b\d{9}\b"
Set myMailToSend = Item
Set re = CreateObject("vbscript.regexp")
re.Pattern = sPat
s = myMailToSend.Body & " " & myMailToSend.subject
If re.test(s) = True Then
strMsg = "The current item contains sensitive information." & _
"Do you still want to send it?"
nResponse = MsgBox(strMsg, vbExclamation + vbYesNo, _
"Check Sensitive Information")
If nResponse = vbYes Then
Cancel = False
Else
Cancel = True
End If
End If
End Sub

Get Text from Range - VBA (Excel)

I want to make a data validation list from text only in a Range of cells.
I searched for formula but I found nothing so I decided to make my own function but its not working.
I tried those codes:
Code 1:
Function ListFromRange(rng As Range) As Range
Dim cl As Range
Dim entry As Range
For Each cl In rng
If Not IsNumeric(cl.Value) Or cl.Value = "" Then
If entry Is Nothing Then
Set entry = cl
Else
Set entry = Union(entry, cl)
End If
End If
Next
Set ListFromRange = entry
End Function
Code 2:
Function ListFromRange2(rng As Range) As Variant
Dim cl As Range
Dim i As Integer
Dim entry() As String
ReDim entry(rng.Count)
For Each cl In rng
If Not IsNumeric(cl.Value) Or cl.Value = "" Then
entry(i) = cl.Value
i = i + 1
End If
Next
ListFromRange2 = entry
End Function
The second code is working but when I use with a defined name and use that defined name for data validation list its tells me that there is an error in the validation list source but when I use this function with index its returning the desired result.
Also some images to explain more:
I want to make a list from cells that contains a text and apply it here:
But without the number values.
The problem is that the resultant range is multiple columns and cannot be used as the source for a Data Validation List. If you cannot change the design of the table of options so that it is just one column, you need to find another way to set up the Validation List.
Here is a solution using VBA. I put this in a sub that can be run on demand as a macro, but you might drop it into a worksheet event that triggers when data on the sheet changes or some other event.
This will only create the validation list as far down as there is data in Column A. You'll probably want to carry it down further than this, or as mentioned, put this into a worksheet event so that it updates the Validation list as new rows are added.
I set up my sheets as follows, but you can also download my example here.
Option Explicit
Sub Create_Validation_List()
Dim rngList As Range, cl As Range
Dim rngValidationList As Range
Dim strList As String
Set rngList = Worksheets("BasicPrice").Range("A2:F3")
strList = ""
For Each cl In rngList
If Not IsNumeric(cl.Value) And Not cl.Value = "" Then strList = strList & "," & cl.Value 'Add to our Validation List
Next cl
strList = Mid(strList, 2) 'Chop off leading comma
'Apply Data Validation to this Range (starting at cell C2 and ending at the last row with data in column A)
Set rngValidationList = Range("C2:C" & Cells(Rows.Count, "A").End(xlUp).Row)
Application.EnableEvents = False
With rngValidationList.Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=strList 'Pass in Validation List created above
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
Application.EnableEvents = True
End Sub
Let me know if you have any questions.

Using VBA regex on Array

I am writing a macro and the macro works fine, but I am trying to add some error handling to it so others are using it and an error occurs they are able to figure out what happened. The last problem I am having is I am using the Application.GetOpenFilename to open multiple files with multiselect = True. I am using a regex to match the file name and if the wrong file name is chosen then it displays an error message. If multiselect = False then I get no errors, but when it is equal to True I get a Type Mismatch error. I can only assume this is because when mutliselect = True the file is an array which the regex cannot handle. Is there a solution to this or can anyone point me to a better solution to handle the error. I have attached the VBA script as well.
Sub DataImport_Loop()
Dim nom As String
Dim wb As Excel.Workbook
Dim i, j, k, m, n, file As Variant
Dim strPattern As String: strPattern = "Strain End Point [0-9] - FEA Loop - Loading - (Timed)" 'File Pattern
Dim regex As Object
Set regex = CreateObject("VBScript.RegExp")
'Turns Screen Updating and Alert Displays off
Application.ScreenUpdating = False
Application.DisplayAlerts = False
nom = ActiveWorkbook.Name
'takes user straight into necessary folder
If CurDir() <> CurDir("J:") Then
ChDrive "J:"
ChDir "J:FEA Material Data"
End If
'Number of specimens tested
For i = 1 To 5
'Allows user to select multiple files to open
file = Application.GetOpenFilename( _
FileFilter:="Text Files (*.csv), *.csv", _
MultiSelect:=True)
'If no file selected, stop data import and display error message
If Not IsArray(file) Then
MsgBox ("You only imported " & (i - 1) & " Specimens.")
Exit Sub
'Sets patteren to check if correct file
With regex
.Pattern = strPattern
End With
'Checks set pattern, displays error message if not correct file
If regex.Test(file) = False Then
MsgBox ("Select Loading Only")
Exit Sub
End If
Else
Counter = 1
While Counter <= UBound(file)
j = (2 * i) - 1
Workbooks.Open file(Counter)
Set wb = Workbooks("Strain End Point " & Counter & " - FEA Loop - Loading - (Timed).csv")
'End of column, needs + 3 to account for first 3 unused cells
k = Range("F4", Range("F4").End(xlDown)).Count + 3
'Loops through data, deletes negative values
For m = 4 To k
If Range("F" & m).value < 0 Or Range("F" & m).Offset(0, 1) < 0 Then
Range("F" & m).Delete
Range("F" & m).Offset(0, 1).Delete
'If cell is deleted, rechecks new value
m = m - 1
End If
Next m
Range("F4:G" & k).Copy
Workbooks(nom).Sheets(Counter + 1).Cells(4, j).PasteSpecial
wb.Close
'Opens next file
Counter = Counter + 1
Wend
End If
Next i
'Turns Screen Updating and Alert Displays back on
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
When MultiSelect is true, file will always be a variant array, even if only a single file is selected. Therefore you must iterate through each element of the array in order to check it against your mask.
With regard to your mask, I would suggest using the Like operator as it seems simpler and will probably run faster. Note the # replacing the regex pattern [0-9]) eg:
'Checks set pattern, displays error message if not correct file
Const strPattern as String = "Strain End Point # - FEA Loop - Loading - (Timed)" 'File Pattern
For I = LBound(file) To UBound(file)
If Not file(I) Like strPattern Then
MsgBox ("Select Loading Only")
Exit Sub
End If
Next I

VB.Net: Regular Expressions

I'm creating an application that will be able to tell me who is logged onto what PC, in the manufacturing center, where I work.
I'm using psexec's psloggedon cmd process to get me the information for me and a VB.net windows application to show me the information.
I begin by first querying a databse for all the PC's we currently have active and dumping the data into a datagridview object. (Shown below)
Private Sub Button(sender As System.Object, e As System.EventArgs) Handles btngetPC.Click
'GET AREAS FROM DATABASE
Dim ds As New DataSet()
Dim db As String = "QUERY STRING GOES HERE"
'CONNECT TO DATABASE
Using da As New SqlDataAdapter(db, MySQLConnection)
da.Fill(ds, "MACHINE_NAME")
End Using
With datagridView1
.DataSource = ds.Tables("MACHINE_NAME")
End With
'ADD COLUMN TO DATAGRIDVIEW
datagridView1.Columns.Add("LOGGED_IN", "LOGGED_IN")
MySQLConnection.Close()
End Sub
Once I have my datagridview object filled out with all my active PC's, I can then use the machine names to run the psloggedon cmd to get who is logged in. I do so by using:
Private Sub execute(sender As Object, e As EventArgs) Handles bntExecuteCmd.Click
'COUNT ENTRIES
Dim RowCount As Integer = datagridView1.RowCount
''EXECUTE CMD
For i = 0 To RowCount - 2
'PERFORM PSLOGGEDON ROUTINE
Dim Proc1 As New Process
Proc1.StartInfo = New ProcessStartInfo("psloggedon")
Proc1.StartInfo.Arguments = "-l \\" & datagridView1.Rows(i).Cells(1).Value & ""
Proc1.StartInfo.RedirectStandardOutput = True
Proc1.StartInfo.UseShellExecute = False
Proc1.StartInfo.CreateNoWindow = True
Proc1.Start()
If Not Proc1.WaitForExit(300) Then
Proc1.Kill()
End If
'INSERT RESULTS INTO LOGGEN_IN COLUMN
Dim msg As String = Proc1.StandardOutput.ReadToEnd
Dim idx As Integer = msg.LastIndexOf("\"c)
Dim user As String = msg.Substring(idx + 1)
Dim final As String = UCase(System.Text.RegularExpressions.Regex.Replace(user, "^ELP.*$", ""))
datagridView1.Rows(i).Cells(2).Value = final
Next
End Sub
Finally, here is my question:
To get the employee names I must use regex becuase the raw format is unacceptable.
raw format:
"Connecting to Registry of \ELPSC171698...
Users logged on locally:
ECHOSTAR\Jane.Doe"
format after applying:
'INSERT RESULTS INTO LOGGEN_IN COLUMN
Dim msg As String = Proc1.StandardOutput.ReadToEnd
Dim idx As Integer = msg.LastIndexOf("\"c)
Dim user As String = msg.Substring(idx + 1)
Dim final As String = UCase(System.Text.RegularExpressions.Regex.Replace(user, "^ELP.*$", ""))
datagridView1.Rows(i).Cells(2).Value = final
"PAULA.RODRIGUEZ"
Looks good, right? However, when the raw format has more than one associate, like so:
"Connecting to Registry of \ELPSC173068...
Users logged on locally:
ECHOSTAR\John.Doe
ECHOSTAR\Ben.Doe"
the code I have written will get me the last person in this list. In this case, I will get JOHN.DOE when I need to get BEN.DOE.
Now the question: How can I change this code:
'INSERT RESULTS INTO LOGGEN_IN COLUMN
Dim msg As String = Proc1.StandardOutput.ReadToEnd
Dim idx As Integer = msg.LastIndexOf("\"c)
Dim user As String = msg.Substring(idx + 1)
Dim final As String = UCase(System.Text.RegularExpressions.Regex.Replace(user, "^ELP.*$", ""))
datagridView1.Rows(i).Cells(2).Value = final
To get me the first person, "JOHN.DOE" from here:
"Connecting to Registry of \ELPSC173068...
Users logged on locally:
ECHOSTAR\John.Doe
ECHOSTAR\Ben.Doe"
I hope my question was clear and well constructed. Thank you.
Use more specific regex and a capture group to get multiple items.
Dim mc As MatchCollection = Regex.Matches("Users logged on locally: ECHOSTAR\John.Doe ECHOSTAR\Ben.Doe", "[\t ]+[a-z_0-9]+\\(?<n>[a-z_\.0-9]+)(([\t ])|($))", RegexOptions.ExplicitCapture Or RegexOptions.IgnoreCase Or RegexOptions.Multiline)
For Each m As Match In mc
Dim name As String = m.Groups("n").value
Next
if you just want the first one then...
If mc.Count >= 1 Then
Dim name As String = mc(0).Groups("n").Value
End If
Change
Dim idx As Integer = msg.LastIndexOf("\"c)
to
Dim idx As Integer = msg.IndexOf("\"c)
Alternatively consider this:
Assuming you've validated the string first.
Dim user As String = msg.Split({"\"c, " "c}, StringSplitOptions.RemoveEmptyEntries)(1) _
.Replace("."c, " "c).ToUpper
To leave the decimal in just remove .Replace("."c, " "c)