Finding a date in message body and using it to set a reminder in outlook calendar - vba

Currently I have a macro running on all sent items to check that checks to see if I'm sending the e-mail to a particular customer. If I am, then it checks to see if one of the customer’s requirements for all messages is present which is a 'next update due' if it is not it asks if one is required. See below;
Public Sub application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Dim addrType
Dim addr
Dim recip
For Each recip In Item.Recipients
If recip.Type = olTo Then
addrType = recip.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x3002001F")
If addrType = "EX" Then
addr = recip.AddressEntry.GetExchangeUser.PrimarySmtpAddress
Else
addr = recip.Address
End If
If LCase(addr) = "relevantemail1#outlook.com" Or LCase(addr) = "relevantemail2#outlook.com" Then
If InStr(1, Item.Body, LCase("next update due"), vbTextCompare) > 0 Then
Call errhandler
Else
'ask if we've added the date
prompt$ = "You're sending this to company x and have not added a 'next update due' date, do you need to add one?"
If MsgBox(prompt$, vbYesNo + vbQuestion + vbMsgBoxSetForeground, "Check Address") = vbYes Then
Cancel = True
End If
Exit For
End If
End If
End If
Next
End Sub
I am still really getting to grips with the basics of outlook VBA and string functions etc therein. so my question is: can I find the string after the text "next update due:" (ideally I want a formatted date but a string will do for the time being)? Essentially there will always be two delimiters ": " to begin the date and a carriage return to end it. Once I can retrieve it I am able to export the relevant information to an excel document which I will however I am struggling with the initial retrieval of the string/date.
Hopefully you guys can help, more than happy to expand on the above code if needed.

relative simple regex might be used to extract particular date pattern from body
"next update due:(*):"
and your result will be captured in those parenthessis
or with specific date formatting:
"next update due:*(\d\d\/\d\d\/\d\d)*:"
if your date is like 10/01/15

I have made a few changes to your code and hopefully it will get you in the right track.
A few observations to help you with the code.
Always break long code into smaller functions to help you
Always declare variables
Always write comments to help you.
Here is the code I hope it helps
Public Sub application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Dim strAddress As String
Dim oRecipient As Recipient
Dim strNextDueDate As String
For Each oRecipient In Item.Recipients
If oRecipient.Type = olTo Then
' Get the address
strAddress = GetAddress(oRecipient)
' Check the adress for de
If HasToBeChecked(strAddress) Then
If InStr(1, Item.Body, LCase("next update due"), vbTextCompare) > 0 Then
'Get the value for the date.
strNextDueDate = GetStringBetweenStrings(Item.Body, "next update due:", vbCr)
' ------------------------------------------
' The rest of your code here.....
' ------------------------------------------
'Call errhandler
Else
'ask if we've added the date
prompt$ = "You're sending this to company x and have not added a 'next update due' date, do you need to add one?"
If MsgBox(prompt$, vbYesNo + vbQuestion + vbMsgBoxSetForeground, "Check Address") = vbYes Then
Cancel = True
End If
Exit For
End If
End If
End If
Next
End Sub
' Fucntion to check if the address should be chnaged.
' We have it as a separate function so we can add more
' emails if needed.
Function HasToBeChecked(ByVal strAddress As String)
Dim arrAddresses As Variant
Dim i As Long
Dim ret As Boolean
' Load the array of addresses
arrAddresses = Array("relevantemail1#outlook.com", "relevantemail2#outlook.com")
For i = LBound(arrAddresses) To UBound(arrAddresses)
If LCase$(strAddress) = LCase$(arrAddresses(i)) Then
ret = True
Exit For
End If
Next i
'Return the value
HasToBeChecked = ret
End Function
' Function to retrive the address from a recipient object
Function GetAddress(ByRef oRecipient As Recipient) As String
Const strSCHEMA_PROPERTY_TYPE As String = "http://schemas.microsoft.com/mapi/proptag/0x3002001F"
Dim strAddresType As String
Dim ret As String
' Get the address accoring to if it's an excahnge or a regular email
strAddresType = oRecipient.PropertyAccessor.GetProperty(strSCHEMA_PROPERTY_TYPE)
If addrType = "EX" Then
ret = oRecipient.AddressEntry.GetExchangeUser.PrimarySmtpAddress
Else
ret = oRecipient.Address
End If
' Return the value
GetAddress = ret
End Function
' Function to get he string between teo strings
Function GetStringBetweenStrings(ByVal strToCheck As String, _
ByVal strStart As String, _
ByVal strEnd As String) As String
Dim lPosStart As Long
Dim lPostEnd As Long
Dim ret As String
lPosStart = InStr(1, strToCheck, strStart)
If Not lPosStart = 0 Then
lPosStart = lPosStart + Len(strStart) + 1
lPostEnd = InStr(lPosStart, strToCheck, strEnd)
If Not lPostEnd = 0 Then
ret = Mid$(strToCheck, lPosStart, lPostEnd - lPosStart)
End If
End If
' Return the value
GetStringBetweenStrings = ret
End Function
Thanks

Related

Get selected Appointment folder's email adress

I have two calendars, one is mine and the other is shared. Both are opened in outlook as below.
How can i get selected apointment calendar's email adress?
I saw AppointmentItem has GetOrganizer to find who created the appointment but I don't find any method or property about the user of the calendar in witch the appointment is...
So I tried Application.ActiveExplorer.CurrentFolder to get the selected folder and then get the AdressEntry but I can't get the folder's store because it's a shared calendar (and then folder.store returns null).
Following Dmitry's advices there, I did :
Dim appointment_item As Outlook.AppointmentItem
Dim PR_MAILBOX_OWNER_ENTRYID as String
Dim mapiFolder As Outlook.MAPIFolder
Dim folderStore As Outlook.Store
Dim mailOwnerEntryId As String
Dim entryAddress As Outlook.AddressEntry
Dim smtpAdress As String
PR_MAILBOX_OWNER_ENTRYID = "http://schemas.microsoft.com/mapi/proptag/0x661B0102"
appointment_item = Application.ActiveExplorer.Selection.Item(1)
mapiFolder = appointment_item.Parent
folderStore = mapiFolder.Store
mailOwnerEntryId = folderStore.PropertyAccessor.GetProperty(PR_MAILBOX_OWNER_ENTRYID)
entryAddress = Application.Session.GetAddressEntryFromID(mailOwnerEntryId)
smtpAdress = entryAddress.GetExchangeUser.PrimarySmtpAddress
MsgBox(smtpAdress)
The issue is i can't get .Store of a shared folder as written here in the MS Documentation.
This property returns a Store object except in the case where the Folder is a shared folder (returned by NameSpace.GetSharedDefaultFolder). In this case, one user has delegated access to a default folder to another user; a call to Folder.Store will return Null.
I finally found a way to do it, this topic helped me.
The code below, parses the shared folder storeID to get the shared folder SMTP address.
Public Sub test()
Dim smtpAddress As String
Dim selectedItem As Outlook.Folder
smtpAddress = ""
TryGetSmtpAddress(Application.ActiveExplorer.Selection.Item(1).Parent, smtpAddress)
End Sub
Public Shared Function TryGetSmtpAddress(ByVal folder As MAPIFolder, ByRef smtpAddress As String) As Boolean
smtpAddress = "default"
Dim storeId = HexToBytes(folder.StoreID)
If BitConverter.ToUInt64(storeId, 4) <> &H1A10E50510BBA138UL OrElse BitConverter.ToUInt64(storeId, 12) <> &HC2562A2B0008BBA1UL Then
Return False
End If
Dim indexDn = Array.IndexOf(storeId, CByte(&H0), 60) + 1
Dim indexV3Block = Array.IndexOf(storeId, CByte(&H0), indexDn) + 1
If BitConverter.ToUInt32(storeId, indexV3Block) <> &HF43246E9UL Then
Return False
End If
Dim offsetSmtpAddress = BitConverter.ToUInt32(storeId, indexV3Block + 12)
smtpAddress = BytesToUnicode(storeId, indexV3Block + CInt(offsetSmtpAddress))
Return True
End Function
Private Shared Function HexToBytes(ByVal input As String) As Byte()
Dim bytesLength = input.Length / 2
Dim bytes = New Byte(bytesLength - 1) {}
For i = 0 To bytesLength - 1
bytes(i) = Convert.ToByte(input.Substring(i * 2, 2), 16)
Next
Return bytes
End Function
Private Shared Function BytesToUnicode(ByVal value As Byte(), ByVal startIndex As Integer) As String
Dim charsLength = (value.Length - startIndex) / 2
Dim chars = New Char(charsLength - 1) {}
For i = 0 To charsLength - 1
Dim c = CSharpImpl.__Assign(chars(i), BitConverter.ToChar(value, startIndex + i * 2))
If c = vbNullChar Then
Return New String(chars, 0, i)
End If
Next
Return New String(chars)
End Function
Private Class CSharpImpl
<Obsolete("Please refactor calling code to use normal Visual Basic assignment")>
Shared Function __Assign(Of T)(ByRef target As T, value As T) As T
target = value
Return value
End Function
End Class
It may be possible to get to the top of the folder tree of a shared calendar the long way, without built-in shortcuts.
Tested on my own calendar, not a shared calendar.
Option Explicit
Sub appointment_sourceFolder()
' VBA code
Dim obj_item As Object
Dim appointment_item As AppointmentItem
Dim parentOfAppointment As Variant
Dim parentParentFolder As Folder
Dim sourceFolder As Folder
Set obj_item = ActiveExplorer.Selection.Item(1)
If obj_item.Class <> olAppointment Then Exit Sub
Set appointment_item = obj_item
' Recurring appointment leads to
' the parent of the recurring appointment item then the calendar folder.
' Single appointment leads to
' the calendar folder then the mailbox name.
Set parentOfAppointment = appointment_item.Parent
Set parentParentFolder = parentOfAppointment.Parent
Debug.Print vbCr & " parentParentFolder: " & parentParentFolder.Name
Set sourceFolder = parentParentFolder
' Error bypass for a specific purpose
On Error Resume Next
' If parentParentFolder is the shared calendar,
' walking up one folder is the mailbox.
' If parentParentFolder is the mailbox,
' walking up one folder is an error that is bypassed,
' so no change in sourceFolder.
' Assumption:
' The shared calendar is directly under the mailbox
' otherwise add more Set sourceFolder = sourceFolder.Parent
Set sourceFolder = sourceFolder.Parent
' Return to normal error handling immediately
On Error GoTo 0
Debug.Print " sourceFolder should be smtp address: " & sourceFolder
'MsgBox " sourceFolder should be smtp address: " & sourceFolder
End Sub

Passing Values in VBA

In the code I am posting, I am using a check box called "ACDS Test" and whenever it is checked it creates a sheet, then when it becomes unchecked it calls the upper function and deletes the sheet.
I am trying to add a message box that essentially works like a fail safe to ensure they want to delete the page. If they say they do not want to delete the page then I want the checkbox to stay checked.
For some reason I am getting this error message when I try to pass the value to make sure the checkbox stays checked and I cannot figure out why.
The error comes up on the line:
Sub ACDSTest_Click(CorrectValue As Integer)
And the specific error is: "Compile error: Procedure Declaration does not match description of event or procedure having the same name".
Any help is much appreciated! IF any more clarification is needed please feel free to ask!
Sub DeleteWorksheet(NameSheet As String)
Dim Ans As Long
Dim t As String
Dim CorrectValue As Integer
Dim i As Long, k As Long
k = Sheets.Count
Ans = MsgBox("Would you like to take this test off of the form?", vbYesNo)
Select Case Ans
Case vbYes
'Code reads through each page and finds one with corresponding name to string t
'Once it finds the correct page, it deletes it
For i = k To 1 Step -1
t = Sheets(i).Name
If t = NameSheet Then
Sheets(i).Delete
End If
Next i
CorrectValue = 0
Case vbNo
CorrectValue = 1
End Select
End Sub
Sub ACDSTest_Click(CorrectValue As Integer)
Dim NameSheet As String
Dim NameValue As String
NameSheet = "ACDS"
NameValue = "ACDS Test"
If ACDSTest.Value = True Then
CreateWorksheet (NameSheet), (NameValue)
Worksheets("Sheet1").Activate
Else
DeleteWorksheet (NameSheet)
If CorrectValue = 1 Then
ActiveSheet.Shapes("ACDS Test").ControlFormat.Value = 1
End If
End If
End Sub
The issue here is that the CorrectValue variable as you define it in DeleteWorksheet does not exist in the context of the
variable does not exist in context of the ACDSTest_Click subroutine. This is because variables defined within subroutines or functions are local to those functions. To correct this I would convert DeleteWorksheet to a function such as the below.
Further, the event that fires Private Sub ACDSTest_Click() cannot handle passing a value to that function, so changing it to Sub ACDSTest_Click(CorrectValue As Integer) causes an error.
Function DeleteWorksheet(ByVal SheetName As String) As Boolean
On Error GoTo SheetDNE
SheetName = Sheets(SheetName).Name 'Check if sheet exists w/o other objects
On Error GoTo 0
Select Case MsgBox("Would you like to take this test off of the form?", vbYesNo)
Case vbYes
Application.DisplayAlerts = False
Sheets(SheetName).Delete
Application.DisplayAlerts = True
DeleteWorksheet = True
Case Else: DeleteWorksheet = False
End Select
Exit Function 'Exit The Function w/o error
SheetDNE: 'Sheet Does Not Exist
MsgBox "The indicated sheet, " & SheetName & ", does not exist", vbOKOnly
End Function
And
Private Sub ACDSTest_Click()
Dim NameSheet As String
Dim NameValue As String
NameSheet = "ACDS"
NameValue = "ACDS Test"
If ACDSTest.Value = True Then
CreateWorksheet (NameSheet), (NameValue)
Worksheets("Sheet1").Activate
Else
If Not DeleteWorksheet(NameSheet) Then _
ActiveSheet.Shapes("ACDS Test").ControlFormat.Value = 1
End If
End Sub

Extract email address from string: invalid procedure call or argument

The idea is to scan incoming emails for a particular subject line, extract and reply to the email address from the first line of the email.
The issue lies in the emailC line, where it is telling me it is an invalid procedure call or argument.
Private Sub Application_NewMailEx(ByVal EntryIDCollection As String)
Dim mymail As Outlook.MailItem
Dim ns As Outlook.NameSpace
Set ns = Application.GetNamespace("MAPI")
Set mymail = ns.GetItemFromID(EntryIDCollection)
Substr = Trim(mymail.Subject)
If InStr(1, Substr, "TEST SUBJECT") > 0 Then
sText = mymail.Body
vText = Split(sText, Chr(13), -1, vbTextCompare)
'Find the next empty line of the worksheet
emailC = Trim(Left(sText, InStr(sText, "<") - 1)) 'Split(vText(0), " ", -1, vbTextCompare)
Resultstr = Trim(Mid(sText, InStr(sText, ">") + 1))
senderstr = mymail.SenderEmailAddress
Call SendReply(emailC, mymail.Subject, Resultstr, senderstr)
End If
End Sub
Private Sub SendReply(Tostr, Subjectstr, Bodystr, senderstr)
Dim mymail2 As Outlook.MailItem
Set mymail2 = Application.CreateItem(olMailItem)
nam = mymail2.Session.CurrentUser.Name
With mymail2
.To = senderstr
.Subject = "RE: " & Subjectstr
.ReplyRecipients.Add emailC
.Body = Replace(Bodystr, Tostr, "", 1, -1, vbTextCompare)
End With
mymail2.Send
End Sub
Most likely the mail Body does not contain any '<' or '>'. In that case, the Instr will return 0, and you end up with a command left(sText, -1) which will exacly throw the error you describe
For a starter, change your code
dim p as integer
p = InStr(sText, "<")
if p = 0 then
debug.Print "no '<' found, text = :" & sText
else
emailC = Trim(Left(sText, p - 1))
....
After that you have to make up your mind what to do in such a case (and you should also the case that you find a '<' but no '>')
Perhaps the < and > are in sText represented as < and >.
It's generally good practice to declare all your variables even if they're just Strings.
I would do this for all the String's you're using. I would also change your SendReply routine as follows:
Private Sub SendReply(ByVal Tostr as String, ByVal Subjectstr as String, ByVal Bodystr as String, ByVal senderstr as String)
From memory, if you don't do the above, the code doesn't know what data type the variables should be.

Select account for VBA script

I have written code to limit the number of addresses for the To, CC and BCC fields.
The only problem is the code affects all accounts and not a specific account which I select.
e.g: user1#xyz.com
user2#xyz.com
I want this code to work only for the "user1#xyz.com" account and not the "user2#xyz.com" account. But the code is running in the whole Outlook session. Is there any option to choose the account in the code?
Private Sub Application_ItemSend(ByVal Element As Object, Cancel As Boolean)
Dim aaa() As String
Dim bbb() As String
Dim ccc() As String
aaa = Split(Element.To, ";")
bbb = Split(Element.CC, ";")
ccc = Split(Element.BCC, ";")
If (UBound(aaa) + 1) + (UBound(bbb) + 1) + (UBound(ccc) + 1) > 10 Then
MsgBox ("You have added too many recipients! Please contact your Administrator."), vbExclamation, "Authorization required!"
Cancel = True
End If
End Sub
I've written a lot of VBA, but none for Outlook, so I'm guessing a bit here. How about inserting this before you Split anything:
If LCase(Environ("Username") & "#" & Environ("Userdnsdomain")) <> "user1#xyz.com" Then Exit Sub
?
You can find the applicable account like this
Private Sub Application_ItemSend(ByVal Element As Object, Cancel As Boolean)
Dim oAccount As account
For Each oAccount In Session.Accounts
If oAccount = "user1#xyz.com" Then
Dim aaa() As String
Dim bbb() As String
Dim ccc() As String
aaa = Split(Element.To, ";")
bbb = Split(Element.CC, ";")
ccc = Split(Element.BCC, ";")
If (UBound(aaa) + 1) + (UBound(bbb) + 1) + (UBound(ccc) + 1) > 1 Then
MsgBox ("You have added too many recipients! Please contact your Administrator."), vbExclamation, "Authorization required!"
Cancel = True
End If
End If
Next
ExitRoutine:
Set oAccount = Nothing
End Sub

Search for a certain style in word 2010 and make it into a bookmark using vba

How to make a style as a bookmark in word 2010?
You won't be able to use most of the text in the document as the bookmark name. It is just illegal to use certain characters in a bookmark name in Word/VBA. It may be possible to add such characters in bookmark names in an XML format of the document, so if it is required, you can ask a separate question.
This feels like way too much code to post on SO. You really need to explain what framework you have in place and tell us where your hurdles are. We can't do this again. "Works for me". If you have any questions though don't hesitate to ask.
Run the "RunMe" macro at the bottom.
Private Function IsParagraphStyledWithHeading(para As Paragraph) As Boolean
Dim flag As Boolean: flag = False
If InStr(1, para.Style, "heading", vbTextCompare) > 0 Then
flag = True
End If
IsParagraphStyledWithHeading = flag
End Function
Private Function GetTextRangeOfStyledParagraph(para As Paragraph) As String
Dim textOfRange As String: textOfRange = para.Range.Text
GetTextRangeOfStyledParagraph = textOfRange
End Function
Private Function BookmarkNameAlreadyExist(bookmarkName As String) As Boolean
Dim bookmark As bookmark
Dim flag As Boolean: flag = False
For Each bookmark In ActiveDocument.Bookmarks
If bookmarkName = bookmark.name Then
flag = True
End If
Next
BookmarkNameAlreadyExist = flag
End Function
Private Function CreateUniqueBookmarkName(bookmarkName As String)
Dim uniqueBookmarkName As String
Dim guid As String: guid = Mid$(CreateObject("Scriptlet.TypeLib").guid, 2, 36)
guid = Replace(guid, "-", "", , , vbTextCompare)
uniqueBookmarkName = bookmarkName & guid
CreateUniqueBookmarkName = uniqueBookmarkName
End Function
Private Function BookmarkIt(rng As Range, bookmarkName As String)
Dim cleanName As String: cleanName = MakeValidBMName(bookmarkName)
If BookmarkNameAlreadyExist(cleanName) Then
cleanName = CreateUniqueBookmarkName(cleanName)
End If
ActiveDocument.Bookmarks.Add name:=cleanName, Range:=rng
End Function
''shamelessly stolen from gmaxey at http://www.vbaexpress.com/forum/showthread.php?t=37674
Private Function MakeValidBMName(strIn As String)
Dim pFirstChr As String
Dim i As Long
Dim tempStr As String
strIn = Trim(strIn)
pFirstChr = Left(strIn, 1)
If Not pFirstChr Like "[A-Za-z]" Then
strIn = "A_" & strIn
End If
For i = 1 To Len(strIn)
Select Case Asc(Mid$(strIn, i, 1))
Case 49 To 58, 65 To 90, 97 To 122
tempStr = tempStr & Mid$(strIn, i, 1)
Case Else
tempStr = tempStr & "_"
End Select
Next i
tempStr = Replace(tempStr, " ", " ")
MakeValidBMName = tempStr
End Function
Sub RunMe()
Dim para As Paragraph
Dim textOfPara As String
For Each para In ActiveDocument.Paragraphs
If IsParagraphStyledWithHeading(para) Then
textOfPara = GetTextRangeOfStyledParagraph(para)
If para.Range.Bookmarks.Count < 1 Then
BookmarkIt para.Range, textOfPara
End If
End If
Next
End Sub