Extract email address from string: invalid procedure call or argument - vba

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.

Related

How to quickly search selected text on Google in the Microsoft Word Document?

I found an article with a Macro that can selected text in the Microsoft Word Document and search it on Google:
https://www.datanumen.com/blogs/quickly-search-selected-text-google-yahoo-bing-word-document/
However, the first row's code "Dim objIE As Object" makes it cannot be ran on my computer since my company has uninstalled the Internet Explorer (IE) many years ago. And the current Microsoft Edge API does not allow such method.
Sub OpenBrowser(strAddress As String, Menubar As Boolean, nHeight As Long, nWidth As Long, varResizable As Boolean)
Dim objIE As Object
' Create and set the object settings.
Set objIE = CreateObject("InternetExplorer.Application")
With objIE
.Visible = False
.width = nWidth
.height = nHeight
.Menubar = Menubar
.Visible = True
.resizable = varResizable
.Navigate strAddress
End With
End Sub
Sub SearchOnGoogle()
Dim strText As String
Dim strButtonValue As String
strButtonValue = MsgBox("Do you want to search the selected text on Google?", vbYesNo, "Search on Google")
If strButtonValue = vbNo Then
Exit Sub
Else
' Make sure there is text selected.
If Selection.Type <> wdSelectionIP Then
strText = Selection.text
strText = Trim(strText)
Else
MsgBox ("Please select text first!")
Exit Sub
End If
' Search selected text on Google with browser window opened in set size.
OpenBrowser "https://www.google.com/search?num=20&hl=en&q=" & strText, True, 550, 650, True
End If
End Sub
Then, I have written the following Macro to select the word in MS Word and then search on Google. But it can only search one word only. If multiple words (such as "Social Capital") is selected and ran this Macro, the Chrome will pop-out two times and search "Social" and "Capital" separately.
Sub Google_Search_Single_Word()
Dim theTerm As String
Dim strURL As String
Dim arrSites(1)
Dim appPath As String
Dim strText As String
Dim strButtonValue As String
appPath = """C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"""
If Selection.Type = wdSelectionIP Then
theTerm = Selection.Words(1).Text
Else
theTerm = Selection.Text
End If
arrSites(1) = "http://www.google.com/search?hl=en&q=" + theTerm
For i = 0 To 1 Step 1
strURL = arrSites(i)
Shell (appPath & " -url " & strURL)
Next i
End Sub
Thus I found a version of Excel VBA Macro from the website:https://excelchamps.com/blog/vba-code-search-google-chrome/, which is also applicable to MS Word. However, this is a method that pop-out a box to search. If you don't type anything on that, it still automatically open the Google Chrome, which is not user-friendly.
Sub GoogleSearch()
Dim chromePath As String
Dim search_string As String
Dim query As String
query = InputBox("Please enter the keywords", "Google Search")
search_string = query
search_string = Replace(search_string, " ", "+")
chromePath = "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
Shell (chromePath & " -url http://www.google.com/search?hl=en&q=" & search_string)
End Sub
I'm thankful that I can enjoy the above contributions from different experts. Does anyone know how can I edit one of the above versions to make a Macro that can quickly search selected text on Google in the Microsoft Word Document?
Here's a version of the Google_Search_Single_Word Sub that can handle multiple words. It uses the helper function URLEncode, and you will need to include the Microsoft ActiveX Data Objects library in your project (Tools > References. If there are multiple versions available, go with the highest version number).
URLEncode is from this answer.
Sub Google_Search_Selected_Text()
Dim theTerm As String
Dim strURL As String
Dim arrSites(1)
Dim appPath As String
Dim strText As String
Dim strButtonValue As String
appPath = """C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"""
theTerm = URLEncode(Selection.Text)
MsgBox theTerm
arrSites(1) = "http://www.google.com/search?hl=en&q=" + theTerm
For i = 0 To 1 Step 1
strURL = arrSites(i)
Shell (appPath & " -url " & strURL)
Next i
End Sub
Public Function URLEncode( _
ByVal StringVal As String, _
Optional SpaceAsPlus As Boolean = False _
) As String
Dim bytes() As Byte, b As Byte, i As Integer, space As String
If SpaceAsPlus Then space = "+" Else space = "%20"
If Len(StringVal) > 0 Then
With New ADODB.Stream
.Mode = adModeReadWrite
.Type = adTypeText
.Charset = "UTF-8"
.Open
.WriteText StringVal
.Position = 0
.Type = adTypeBinary
.Position = 3 ' skip BOM
bytes = .Read
End With
ReDim Result(UBound(bytes)) As String
For i = UBound(bytes) To 0 Step -1
b = bytes(i)
Select Case b
Case 97 To 122, 65 To 90, 48 To 57, 45, 46, 95, 126
Result(i) = Chr(b)
Case 32
Result(i) = space
Case 0 To 15
Result(i) = "%0" & Hex(b)
Case Else
Result(i) = "%" & Hex(b)
End Select
Next i
URLEncode = Join(Result, "")
End If
End Function
Go to Google, type in two words (e.g. dog food), and look at the resulting URL. Notice that the space between dog and food has been replaced with a + symbol (https://www.google.com/search?q=dog%20food). This is called URL escaping and is necessary for the URL to be properly interpreted. The original version of the Sub does not escape the URL, so Google only picks up the first word.
The version I posted escapes the URL to ensures that spaces are converted into +, as well as handling other characters that need to be escaped such as ! -> %21, ? -> %3F, etc.

How to get first names & last names of recipients of Outlook meeting invite?

I have a script to iterate through my calendar events that day and produce in a separate email a list in the following format:
Event 1:
Subject:
When:
Attendees:
The function, which lists all attendees:
Function listAttendees(ByRef item As Variant, myself As String, ByRef nicknames As Scripting.Dictionary) As String
listAttendees = ""
'Dim pa As Outlook.PropertyAccessor
Dim sAtt As String
For i = 1 To item.Recipients.Count
sAtt = item.Recipients.item(i).AddressEntry.GetExchangeUser().FirstName & " " & item.Recipients.item(i).AddressEntry.GetExchangeUser().LastName
sAtt = cleanName(sAtt)
If nicknames.Exists(sAtt) Then
sAtt = nicknames(sAtt)
End If
If sAtt <> myself Then
If listAttendees <> "" Then
listAttendees = listAttendees & ", "
End If
listAttendees = listAttendees & "[[" & sAtt & "]]"
End If
Next
End Function
I get
Runtime error 91 - object variable or with block variable not set
The error points to:
sAtt = item.Recipients.item(i).AddressEntry.GetExchangeUser().FirstName & " " & item.Recipients.item(i).AddressEntry.GetExchangeUser().LastName
This script was working a few days ago.
The GetExchangeUser method should be called only if the AddressEntry.AddressEntryUserType property is set to the olExchangeUserAddressEntry value. Here is what MSDN states for the property:
AddressEntryUserType provides a level of granularity for user types that is finer than that of AddressEntry.DisplayType. The DisplayType property does not distinguish users with different types of AddressEntry, such as an AddressEntry that has a Simple Mail Transfer Protocol (SMTP) email address, a Lightweight Directory Access Protocol (LDAP) address, an Exchange user address, or an AddressEntry in the Outlook Contacts Address Book. All these entires have olUser as their AddressEntry.DisplayType.
For illustration purposes take a look how it can be used in the code:
Sub DemoAE()
Dim colAL As Outlook.AddressLists
Dim oAL As Outlook.AddressList
Dim colAE As Outlook.AddressEntries
Dim oAE As Outlook.AddressEntry
Dim oExUser As Outlook.ExchangeUser
Set colAL = Application.Session.AddressLists
For Each oAL In colAL
'Address list is an Exchange Global Address List
If oAL.AddressListType = olExchangeGlobalAddressList Then
Set colAE = oAL.AddressEntries
For Each oAE In colAE
If oAE.AddressEntryUserType = olExchangeUserAddressEntry Then
Set oExUser = oAE.GetExchangeUser
Debug.Print(oExUser.JobTitle)
Debug.Print(oExUser.OfficeLocation)
Debug.Print(oExUser.BusinessTelephoneNumber)
End If
Next
End If
Next
End Sub

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

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

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

How to obtain the macros defined in an Excel workbook

Is there any way, in either VBA or C# code, to get a list of the existing macros defined in a workbook?
Ideally, this list would have a method definition signatures, but just getting a list of the available macros would be great.
Is this possible?
I haven't done vba for Excel in a long time, but if I remember well, the object model for the code was inaccessible through scripting.
When you try to access it, you receive the following error.
Run-time error '1004':
Programmatic access to Visual Basic Project is not trusted
Try:
Tools | Macro | Security |Trusted Publisher Tab
[x] Trust access to Visual Basic Project
Now that you have access to the VB IDE, you could probably export the modules and make a text search in them, using vba / c#, using regular expressions to find sub and function declarations, then delete the exported modules.
I'm not sure if there is an other way to do this, but this should work.
You can take a look the following link, to get started with exporting the modules.
http://www.developersdex.com/vb/message.asp?p=2677&ID=%3C4FCD0AE9-5DCB-4A96-8B3C-F19C63CD3635%40microsoft.com%3E
This is where I got the information about giving thrusted access to the VB IDE.
Building on Martin's answer, after you trust access to the VBP, you can use this set of code to get an array of all the public subroutines in an Excel workbook's VB Project. You can modify it to only include subs, or just funcs, or just private or just public...
Private Sub TryGetArrayOfDecs()
Dim Decs() As String
DumpProcedureDecsToArray Decs
End Sub
Public Function DumpProcedureDecsToArray(ByRef Result() As String, Optional InDoc As Excel.Workbook) As Boolean
Dim VBProj As Object
Dim VBComp As Object
Dim VBMod As Object
If InDoc Is Nothing Then Set InDoc = ThisWorkbook
ReDim Result(1 To 1500, 1 To 4)
DumpProcedureDecsToArray = True
On Error GoTo PROC_ERR
Set VBProj = InDoc.VBProject
Dim FuncNum As Long
Dim FuncDec As String
For Each VBComp In VBProj.vbcomponents
Set VBMod = VBComp.CodeModule
For i = 1 To VBMod.countoflines
If IsSubroutineDeclaration(VBMod.Lines(i, 1)) Then
FuncDec = RemoveBlanksAndDecsFromSubDec(RemoveAsVariant(VBMod.Lines(i, 1)))
If LCase(Left(VBMod.Lines(i, 1), Len("private"))) <> "private" Then
FuncNum = FuncNum + 1
Result(FuncNum, 1) = FindToLeftOfString(InDoc.Name, ".") '
Result(FuncNum, 2) = VBMod.Name
Result(FuncNum, 3) = GetSubName(FuncDec)
Result(FuncNum, 4) = VBProj.Name
End If
End If
Next i
Next VBComp
PROC_END:
Exit Function
PROC_ERR:
GoTo PROC_END
End Function
Private Function RemoveCharFromLeftOfString(TheString As String, RemoveChar As String) As String
Dim Result As String
Result = TheString
While LCase(Left(Result, Len(RemoveChar))) = LCase(RemoveChar)
Result = Right(Result, Len(Result) - Len(RemoveChar))
Wend
RemoveCharFromLeftOfString = Result
End Function
Private Function RemoveBlanksAndDecsFromSubDec(TheLine As String) As String
Dim Result As String
Result = TheLine
Result = RemoveCharFromLeftOfString(Result, " ")
Result = RemoveCharFromLeftOfString(Result, " ")
Result = RemoveCharFromLeftOfString(Result, "Public ")
Result = RemoveCharFromLeftOfString(Result, "Private ")
Result = RemoveCharFromLeftOfString(Result, " ")
RemoveBlanksAndDecsFromSubDec = Result
End Function
Private Function RemoveAsVariant(TheLine As String) As String
Dim Result As String
Result = TheLine
Result = Replace(Result, "As Variant", "")
Result = Replace(Result, "As String", "")
Result = Replace(Result, "Function", "")
If InStr(1, Result, "( ") = 0 Then
Result = Replace(Result, "(", "( ")
End If
RemoveAsVariant = Result
End Function
Private Function IsSubroutineDeclaration(TheLine As String) As Boolean
If LCase(Left(RemoveBlanksAndDecsFromSubDec(TheLine), Len("Function "))) = "function " Or LCase(Left(RemoveBlanksAndDecsFromSubDec(TheLine), Len("sub "))) = "sub " Then
IsSubroutineDeclaration = True
End If
End Function
Private Function GetSubName(DecLine As String) As String
GetSubName = FindToRightOfString(FindToLeftOfString(DecLine, "("), " ")
End Function
Function FindToLeftOfString(FullString As String, ToFind As String) As String
If FullString = "" Then Exit Function
Dim Result As String, ToFindPos As Integer
ToFindPos = InStr(1, FullString, ToFind, vbTextCompare)
If ToFindPos > 0 Then
Result = Left(FullString, ToFindPos - 1)
Else
Result = FullString
End If
FindToLeftOfString = Result
End Function
Function FindToRightOfString(FullString As String, ToFind As String) As String
If FullString = "" Then Exit Function
Dim Result As String, ToFindPos As Integer
ToFindPos = InStr(1, FullString, ToFind, vbTextCompare)
Result = Right(FullString, Len(FullString) - ToFindPos + 1 - Len(ToFind))
If ToFindPos > 0 Then
FindToRightOfString = Result
Else
FindToRightOfString = FullString
End If
End Function