How to call MailItem.Display method when clicking on a table cell? - excel

I need to call the Outlook MailItem.Display method when clicking on a specific cell in a table column in Excel.
Below is my module for filling out table.
' This module performs email retrieval and viewing. Dynamically adds email information to a table and creates
' links that open Outlook mailitems in a modal window.
Option Explicit
'Initialize Outlook objects
Dim appOL, appNS, appFolder, email As Object
'initialize ListObject
Dim tbl As ListObject
'Add email information to tbl_email_data
Public Sub addDataToEmailTable()
'GetDefaultFolder(6) is "Inbox" of whoever is signed into Outlook desktop version.
'Does not account for subfolders in Inbox and does not work with Web Outlook version.
Set appOL = CreateObject("Outlook.Application")
Set appNS = appOL.GetNamespace("MAPI")
Set appFolder = appNS.GetDefaultFolder(6)
'initialize table
Set tbl = ThisWorkbook.Worksheets("Email").ListObjects("tbl_email_data")
Dim rowCount As Long
rowCount = 1
If tbl.DataBodyRange Is Nothing Then
tbl.ListRows.Add
End If
'loop through emails and put information into tbl_email_data
For Each email In appFolder.Items
If email.Unread = True Then
tbl.DataBodyRange.Cells(rowCount, 1).Value2 = "Unread"
Else
tbl.DataBodyRange.Cells(rowCount, 1).Value2 = "Read"
End If
tbl.DataBodyRange.Cells(rowCount, 2).Value2 = email.SenderName
tbl.DataBodyRange.Cells(rowCount, 3).Value2 = email.SentOn
tbl.DataBodyRange.Cells(rowCount, 4).Value2 = email.Subject
rowCount = rowCount + 1
Next email
End Sub
I was going to create a userform with a comboBox so when selected a textbox fills with item.body.
This does not account for embedded images, and HTML formatted messages.
I saw Outlook has a method for mailitem that opens the email directly without exiting Excel.

So I figured out how to call the MailItem.Display method based on a table that represents email information in the Inbox folder of Outlook. Lots of trial and error but I got it to work. Below is the full code for the module that will handle all of this.
Option Explicit
Public excelInbox As Collection
Dim appOL, appNS, appInbox, appItem As Object
Public isOnline As Boolean
Public Function checkConnection(status As Boolean)
Set appOL = CreateObject("Outlook.Application")
Set appNS = appOL.GetNameSpace("MAPI")
If appNS.Offline = True Then
MsgBox "Outlook account is not connected to Exchange server. Please verify network connection to get updated Inbox preview"
status = False
Set appNS = Nothing
Set appOL = Nothing
Else
MsgBox "Outlook account is online"
status = True
End If
Set appInbox = appNS.GetDefaultFolder(6)
Set excelInbox = New Collection
End Function
Public Sub makeExcelInbox()
Call checkConnection(isOnline)
If isOnline <> True Then Exit Sub
Set appInbox = appNS.GetDefaultFolder(6) '6 is the enumeration for Inbox root folder in Outlook.
'loop and place only emails into excel Inbox.
For Each appItem In appInbox.Items
If appItem.Class = 43 Then excelInbox.Add appItem '43 represents a mail item in Outlook.
Next appItem
End Sub
Public Sub makeEmailPreviewTable()
Call makeExcelInbox
If excelInbox.Count <> 0 Then
Dim tbl As ListObject
Dim rowCount As Integer
Set tbl = ws_email.ListObjects("tbl_emailData")
rowCount = 1
For Each appItem In excelInbox
If appItem.Unread = True Then
tbl.DataBodyRange.Cells(rowCount, 1).Value2 = "Unread"
Else
tbl.DataBodyRange.Cells(rowCount, 1).Value2 = "Read"
End If
tbl.DataBodyRange.Cells(rowCount, 2).Value2 = appItem.SenderName
tbl.DataBodyRange.Cells(rowCount, 3).Value2 = appItem.SentOn
tbl.DataBodyRange.Cells(rowCount, 4).Value2 = appItem.Subject
rowCount = rowCount + 1
Next appItem
Set tbl = Nothing
ElseIf excelInbox.Count = 0 Then MsgBox "No messages to show in Inbox Preview."
End If
End Sub
Public Function getEmailForDisplay(Target As Range)
'Call makeExcelInbox
For Each appItem In excelInbox
If Target.Value = appItem.Subject Then appItem.Display
Next appItem
End Function
I used the selection change event in the worksheet that has the table to pass the target range value to a function that checks if that value is the same as the subject property of an email in the inbox. It is not the prettiest code, but for any others that come across this with a similar problem this should at least get you on the right path. Here is the worksheet code for event below.
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'Check for selection of a cell in tbl_emailData column Subject, then pass the value to a function.
Dim rng As Range
Dim tableRange As Range
Set tableRange = ListObjects("tbl_emailData").DataBodyRange
Dim rowCount As Long
rowCount = 1
If Intersect(Target, tableRange) Is Nothing Then Exit Sub
'check for valid target location
For Each rng In tableRange
On Error GoTo ErrorHandler
If Target = tableRange(rowCount, 4) Then
Call getEmailForDisplay(Target)
Else
rowCount = rowCount + 1
End If
Next rng
ErrorHandler:
Exit Sub
End Sub
Just an important note, I am still designing this program so if you sample the code you have to make sure you have a table called "tbl_emailData" and a worksheet called "ws_email". Then when you want to run the code, make sure to run the sub "makeEmailPreviewTable" first. In my design the worksheets and cells will all be locked so only the subject column cells will be selectable, this prevents run-time errors in case the user selects more than one cell.
Update: Added errorhandling to the selection event to ignore multi-selection errors. This will ignore, and then when a proper cell is selected then display the email in a Outlook modal.

Related

How to check if contact information exists in my Outlook contacts list?

I have code which adds contacts from a worksheet to my Outlook contacts. Each Contact is on a separate line and has 5 columns with First Name, Last name, Email Address, Company and Mobilephone Number.
How do I add only those lines from the worksheet, which aren't in my contacts, so it doesn't create duplicates?
Sub ExcelWorksheetDataAddToOutlookContacts3()
Dim oApplOutlook As Object
Dim oNsOutlook As Object
Dim oCFolder As Object
Dim oDelFolder As Object
Dim oCItem As Object
Dim oDelItems As Object
Dim lLastRow As Long, i As Long, n As Long, c As Long
'determine last data row in the worksheet:
lLastRow = Sheets("Sheet1").Cells(Rows.Count, "A").End(xlUp).Row
'Create a new instance of the Outlook application,
' if an existing Outlook object is not available.
'Set the Application object as follows:
On Error Resume Next
Set oApplOutlook = GetObject(, "Outlook.Application")
'if an instance of an existing Outlook object is not available,
' an error will occur (Err.Number = 0 means no error):
If Err.Number <> 0 Then
Set oApplOutlook = CreateObject("Outlook.Application")
End If
'disable error handling:
On Error GoTo 0
'use the GetNameSpace method to instantiate
' (ie. create an instance) a NameSpace object variable,
' to access existing Outlook items.
'Set the NameSpace object as follows:
Set oNsOutlook = oApplOutlook.GetNamespace("MAPI")
'----------------------------
'Empty the Deleted Items folder in Outlook so that
' when you quit the Outlook application you bypass the prompt:
' Are you sure you want to permanently delete all the items
' and subfolders in the "Deleted Items" folder?
'set the default Deleted Items folder:
'The numerical value of olFolderDeletedItems is 3.
'The following code has replaced the Outlook's built-in
' constant olFolderDeletedItems by its numerical value 3.
Set oDelFolder = oNsOutlook.GetDefaultFolder(3)
'set the items collection:
Set oDelItems = oDelFolder.Items
'determine number of items in the collection:
c = oDelItems.Count
'start deleting from the last item:
For n = c To 1 Step -1
oDelItems(n).Delete
Next n
'----------------------------
'set reference to the default Contact Items folder:
'The numerical value of olFolderContacts is 10.
'The following code has replaced the Outlook's built-in
' constant olFolderContacts by its numerical value 10.
Set oCFolder = oNsOutlook.GetDefaultFolder(10)
'post each row's data on a separate contact item form:
For i = 2 To lLastRow
'Using the Items.Add Method to create
' a new Outlook contact item in the default Contacts folder.
Set oCItem = oCFolder.Items.Add
'display the new contact item form:
oCItem.Display
'set properties of the new contact item:
With oCItem
.firstName = Sheets("Sheet1").Cells(i, 1)
.LastName = Sheets("Sheet1").Cells(i, 2)
.Email1Address = Sheets("Sheet1").Cells(i, 3)
.CompanyName = Sheets("Sheet1").Cells(i, 4)
.MobileTelephoneNumber = Sheets("Sheet1").Cells(i, 5)
End With
'close the new contact item form after saving:
'The numerical value of olSave is 0.
'The following code has replaced the Outlook's built-in
' constant olSave by its numerical value 0.
oCItem.Close 0
Next i
'quit the Oulook application:
oApplOutlook.Quit
'clear the variables:
Set oApplOutlook = Nothing
Set oNsOutlook = Nothing
Set oCFolder = Nothing
Set oDelFolder = Nothing
Set oCItem = Nothing
Set oDelItems = Nothing
MsgBox "Successfully Exported Worksheet Data to the Default Outlook Contacts Folder."
End Sub
Is this what you are trying? Here is a very basic fuction which uses Outlook Items.Find property to check if the email address exists in the address book.
Option Explicit
Dim OutApp As Object
Dim OutNs As Object
Dim OutFolder As Object
Dim OutItems As Object
Const olFolderContacts As Integer = 10
Sub Sample()
Set OutApp = CreateObject("Outlook.Application")
Set OutNs = OutApp.GetNameSpace("MAPI")
Set OutFolder = OutNs.GetDefaultFolder(olFolderContacts)
Set OutItems = OutFolder.items
Dim EmailToFind As String
'~~> Change email here
EmailToFind = "Sid#Sid.Com"
MsgBox DoesContactExists(EmailToFind)
End Sub
'~~> Function to check if the email exists
Private Function DoesContactExists(EmailAddress As String) As Boolean
Dim olContact As Object
On Error Resume Next
Set olContact = OutItems.Find("[Email1Address] = '" & name & "'")
On Error GoTo 0
If Not olContact Is Nothing Then DoesContactExists = True
End Function

Send one report to each manager using VBA and Outlook

I have a list which contains:
- Clients
- Manager e-mail
- Head manager e-mail
I'm trying to send e-mails using VBA and Outlook, in a way that each time the loop finds one manager (I'm checking for an e-mail address), it sends every client listed for that manager.
If a branch has no manager e-mail address listed, the e-mail should go to the Head Manager (branch 1236, for example, would receive one e-mail, to the Head Manager, with several clients).
The e-mail body will contain pre formatted text, and after that the sheet list with the client list.
I'm having some troubles:
a) to list the branch's clients from the sheet to the mail body
b) to jump from the next manager after the first e-mail, instead of repeating the e-mail for the same manager every time the loop finds the same manager
c) logging the mail sent on the J column
This is a sheet with some of the report:
https://drive.google.com/file/d/1Qo-DceY8exXLVR7uts3YU6cKT_OOGJ21/view?usp=sharing
My loop works somewhat, but I believe I need some other approach to achieve this.
Private Sub CommandButton2_Click() 'envia o email com registro de log
Dim OutlookApp As Object
Dim emailformatado As Object
Dim cell As Range
Dim destinatario As String
Dim comcopia As String
Dim assunto As String
'Dim body_ As String
Dim anexo As String
Dim corpodoemail As String
'Dim publicoalvo As String
Set OutlookApp = CreateObject("Outlook.Application")
'Loop para verificar se o e-mail irá para o gerente da carteira ou para o gerente geral
For Each cell In Sheets("publico").Range("H2:H2000").Cells
If cell.Row <> 0 Then
If cell.Value <> "" Then 'Verifica se carteira possui gerente.
destinatario = cell.Value 'Email do gerente da carteira.
Else
destinatario = cell.Offset(0, 1).Value 'Email do Gerente Geral.
End If
assunto = Sheets("CAPA").Range("F8").Value 'Assunto do e-mail, conforme CAPA.
'publicoalvo = cell.Offset(0, 2).Value
'body_ = Sheets("CAPA").Range("D2").Value
corpodoemail = Sheets("CAPA").Range("F11").Value & "<br><br>" & _
Sheets("CAPA").Range("F13").Value & "<br><br>" ' & _
Sheets("CAPA").Range("F7").Value & "<br><br><br>"
'comcopia = cell.Offset(0, 3).Value 'Caso necessário, adaptar para enviar email com cópia.
'anexo = cell.Offset(0, 4).Value 'Caso necessário, adaptar para incluir anexo ao email.
'Montagem e envio dos emails.
Set emailformatado = OutlookApp.CreateItem(0)
With emailformatado
.To = destinatario
'.CC = comcopia
.Subject = assunto
.HTMLBody = corpodoemail '& publicoalvo
'.Attachments.Add anexo
'.Display
End With
emailformatado.Send
Sheets("publico").Range("J2").Value = "enviado"
End If
Next
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
Set OutMail = Nothing
Set OutApp = Nothing
End Sub
Have a manager class that has a collection of clients. Have a collection of manager instances.
Manager Class
'#Folder("VBAProject")
Option Explicit
Private Type TManager
ManagerEmail As String
Clients As Collection
End Type
Private this As TManager
Private Sub Class_Initialize()
Set this.Clients = New Collection
End Sub
Private Sub Class_Terminate()
Set this.Clients = Nothing
End Sub
Public Property Get ManagerEmail() As String
ManagerEmail = this.ManagerEmail
End Property
Public Property Let ManagerEmail(ByVal value As String)
this.ManagerEmail = value
End Property
Public Property Get Clients() As Collection
Set Clients = this.Clients
End Property
Client Class
'#Folder("VBAProject")
Option Explicit
Private Type TClient
ClientID As String
End Type
Private this As TClient
Public Property Get ClientID() As String
ClientID = this.ClientID
End Property
Public Property Let ClientID(ByVal value As String)
this.ClientID = value
End Property
Standard Module
Option Explicit
Dim Managers As Collection
Sub PopulateManagers()
Set Managers = New Collection
Dim currWS As Worksheet
Set currWS = ThisWorkbook.Worksheets("publico")
With currWS
Dim loopRange As Range
Set loopRange = .Range(.Cells(2, 8), .Cells(.UsedRange.Rows.Count, 8)) 'H2 to the last used row; assuming it's the column for manager emails
End With
Dim currCell As Range
For Each currCell In loopRange
If currCell.value = vbNullString Then 'no manager; try for a head manager
If currCell.Offset(0, 1).value = vbNullString Then 'no managers at all
Dim currManagerEmail As String
currManagerEmail = "NoManagerFound"
Else
currManagerEmail = currCell.Offset(0, 1).Text
End If
Else
currManagerEmail = currCell.Text
End If
Dim currManager As Manager
Set currManager = Nothing
On Error Resume Next
Set currManager = Managers(currManagerEmail)
On Error GoTo 0
If currManager Is Nothing Then
Set currManager = New Manager
currManager.ManagerEmail = currManagerEmail
Managers.Add currManager, Key:=currManager.ManagerEmail
End If
Dim currClient As Client
Set currClient = New Client
currClient.ClientID = currWS.Cells(currCell.Row, 1).Text 'assumes client ID is in column 1
currManager.Clients.Add currClient, Key:=currClient.ClientID
Next
End Sub
once you have the collection of managers just loop that to create your manager-specific email.
Since I used Usedrange.Rows.Count to set up the range to loop it should have worked ok without an additional check. However, since I don't have your actual data to be sure, you may need it. I don't have line numbers, so I don't know what line 51 refers to. To loop the Managers:
Sub LoopManagers()
Dim currManager As Manager
For Each currManager In Managers
Debug.Print currManager.ManagerEmail
Dim currClient As Client
For Each currClient In currManager.Clients
Debug.Print currClient.ClientID
Next
Next
End Sub
You will need to adapt what I've provided to create your emails. Work on it. If you need more help post what you tried and describe what problems you have.

Running an Excel macro from Outlook

I am trying to set up an automated database in Excel which sends an email when a user has equipment signed out past the expected due date.
I also want to implement a feature where the user can reply to the email with a keyword 'extend' to extend their sign out date by 7 days.
I have the email sending correctly. I want to create a script in Outlook that links their reply to the Excel worksheet that is open. Both Outlook and Excel will be open at the same time on a dedicated PC. I don't want to open a new Excel file every time the Outlook macro runs.
Here is the Outlook macro:
Sub Exc_macro(Item As Outlook.MailItem)
Dim ExApp As Workbook
Dim gageID As String
Dim cap As String
If Left(Item.Body, 6) = "extend" Then 'Check for keyword in body
gageID = Mid(Item.Subject, 23) 'Get equipment ID number
Set ExApp = Excel.ActiveWorkbook
Call ExApp.Application.Run("Module2.increase", gageID)
End If
End Sub
I want to pass the gageID argument to the Excel macro here:
Sub increase(gageID As String)
Set Rng = Range(Range("A2"), Range("A" & Rows.Count).End(xlUp))
For Each cell In Rng
If cell.Value = gageID Then
cell.Offset(0, 9).Value = cell.Offset(0, 9).Value + 7
End If
Next
End Sub
How do I reference the open Workbook in Outlook and subsequently run the Excel macro?
Like this:
Sub Exc_macro(Item As Outlook.MailItem)
Dim ExApp As Object
Dim gageID As String
Dim cap As String
If Left(Item.Body, 6) = "extend" Then 'Check for keyword in body
gageID = Mid(Item.Subject, 23) 'Get equipment ID number
Set ExApp = GetObject(,"Excel.Application")
ExApp.Run "'my ExcelWB.xlsm'!increase", gageID
End If
End Sub
Make your increase Sub is in a regular code module
http://www.rondebruin.nl/win/s9/win001.htm

retrieving email address from outlook through userform

I would like to ask if there is a way to retrieve email address from outlook address book from the values enter in user form textbox and retrieve it.
For example, my textbox1 have user entering the full names of people they want to search and with a search button, textbox2 will have all the email address retrieve from outlook address book based on textbox1.
Currently what I have is, a module call retrieve email
Option Explicit
Sub GetAddresses()
Dim o, AddressList, AddressEntry
Dim c As Range, r As Range, AddressName As String
Set o = CreateObject("Outlook.Application")
Set AddressList = o.Session.AddressLists("Contacts")
'Chage this range to include the first names only. AddressName assignment line handles concatenating the last name.
Set r = Add.Emailname
For Each c In r
AddressName = c.Value & " " & c.Offset(0, 1).Value
For Each AddressEntry In AddressList.AddressEntries
If AddressEntry.name = AddressName Then
c.Offset(0, 2).Value = AddressEntry.Address
Exit For
End If
Next AddressEntry
Next c
End Sub
And in my user form, the search button
Private Sub Searchbutton_Click()
Call GetAddresses
End Sub
The code is what I have seen from online. Can anyone help me edit and guide me?
I see you have copied your code. This code is meant to loop around a range. You could simply remove the loop and inmplement your textbox value and assign it to your searchbutton. However you'll need a .ListBox1 (instead of .TextBox2) since you wanted to have all hits in contact to appear in a list. I've also implemented the .Instr function to see if the searchvalue appears in the contacts name (using LCase to be sure). This would making a search on just a lastname way easier. The code then would look like:
Option Explicit
Private Sub GetAddresses()
Dim o, AddressList, AddressEntry
Dim AddressName As String
Set o = CreateObject("Outlook.Application")
Set AddressList = o.Session.AddressLists("Contacts")
'Change this range accordingly
AddressName = UserForm1.TextBox1.Value
For Each AddressEntry In AddressList.AddressEntries
If InStr(1, LCase(AddressEntry.Name), LCase(AddressName)) <> 0 Then
UserForm1.ListBox1.AddItem AddressEntry.Address
End If
Next AddressEntry
End Sub
Private Sub Searchbutton_Click()
UserForm1.ListBox1.Clear
Call GetAddresses
End Sub

VBA Excel Username grants access

Looking for a little help, I have an excel document that should only grant certain users access, all employees have a user name and when they input any information that shows up with their entry. I'm hoping to secure the file so that only certain employees can have access. So far I have
Private Sub Workbook_Open()
Dim Users As Variant
Dim UName As String
Dim UFind As Variant
Users = Array("JBLOGS", "DOEJOHN", "ASmith", "JanDoe")
UName = Environ("UserName")
On Error Resume Next
UFind = WorksheetFunction.Match(UName, Users, 0)
If Err <> 0 Then
MsgBox "You are not authorised to use this Workbook"
ThisWorkbook.Close SaveChanges:=False
End If
End Sub
This is fine, but I had wanted it to be on a sheet of its own ie column titled Users then a list of users that can be added to easily.
I also was wondering if certain users could be restricted to certain sheets, for example, John Doe is in Africa, Jane is in America, can I restrict them to only see sheets titled 'Africa' and 'America'
Had a look and couldn't see anything, so not sure if it easily done...
I'd suggest creating a hidden worksheet to hold your list of usernames, you can even protect the hidden sheet with a password if desired. Additionally, you could expand your username list to a table that lists the worksheets each user is allowed to view. Any sheets disallowed by the table could also be hidden from that user (and, of course, unhidden for a different user with granted access). As a side note, you may find it useful to make a case-insensitive comparison of usernames from the table to the environment variable - this has tripped me up sometimes.
EDIT1: Here's an example to get you started:
Create a worksheet named "AuthUsers" and then create a table named "UserTable". Define two columns in the table, the first called "Users" and the second called "Sheets".
EDIT2: Added the ViewAuthorizedSheets method to hide/view appropriate worksheets and updated the test sub. This also works just fine when called from Worksheet_Open.
Option Explicit
Sub test()
Debug.Print "user is authorized = " & IsUserAuthorized(Environ("UserName"))
ViewAuthorizedSheets Environ("UserName")
If IsUserAuthorized(Environ("UserName")) Then
Debug.Print "authorized sheets = " & GetAuthorizedSheets(Environ("UserName"))
Else
MsgBox "User is not authorized to view any sheets.", vbCritical + vbOKOnly
End If
End Sub
Public Sub ViewAuthorizedSheets(uname As String)
Dim authSheets As String
Dim sh As Worksheet
uname = Environ("UserName")
authSheets = GetAuthorizedSheets(uname)
For Each sh In ThisWorkbook.Sheets
If sh.Name <> "AuthUsers" Then
If InStr(1, authSheets, sh.Name, vbTextCompare) > 0 Then
sh.Visible = xlSheetVisible
Else
sh.Visible = xlSheetHidden
End If
End If
Next sh
End Sub
Function IsUserAuthorized(uname As String) As Boolean
Dim ws As Worksheet
Dim userTbl As ListObject
Dim userList As Range
Dim allowedUser As Variant
Dim allowed As Boolean
Set ws = ThisWorkbook.Sheets("AuthUsers")
Set userTbl = ws.ListObjects("UserTable")
Set userList = userTbl.ListColumns("Users").DataBodyRange
allowed = False
For Each allowedUser In userList
If LCase(allowedUser) = LCase(uname) Then
allowed = True
Exit For
End If
Next allowedUser
Set userList = Nothing
Set userTbl = Nothing
Set ws = Nothing
IsUserAuthorized = allowed
End Function
Function GetAuthorizedSheets(uname As String) As String
Dim ws As Worksheet
Dim userTbl As ListObject
Dim userList As Range
Dim allowedUser As Variant
Dim allowed As String
Set ws = ThisWorkbook.Sheets("AuthUsers")
Set userTbl = ws.ListObjects("UserTable")
Set userList = userTbl.DataBodyRange
allowed = False
For Each allowedUser In userList.Columns(1).Cells
If LCase(allowedUser) = LCase(uname) Then
allowed = allowedUser.Offset(0, 1).value
Exit For
End If
Next allowedUser
Set userList = Nothing
Set userTbl = Nothing
Set ws = Nothing
GetAuthorizedSheets = allowed
End Function
In your ThisWorkbook module, the call is accessed simply by
Option Explicit
Private Sub Workbook_Open()
ViewAuthorizedSheets Environ("UserName")
End Sub
Private Sub Workbook_Open()
Dim EmpArray(3) As String
Dim Count As Integer
EmpArray(0) = "dzcoats"
EmpArray(1) = "cspatric"
EmpArray(2) = "eabernal"
EmpArray(3) = "lcdotson"
Count = 0
For i = LBound(EmpArray) To UBound(EmpArray)
If Application.UserName = EmpArray(i) Then Count = Count = 1
Next i
If Count = 0 Then
MsgBox ("You dont have access to this file")
ThisWorkbook.Close SaveChanges:=False
End If
End Sub
This should work. My Count logic is sloppy though but it does the trick

Resources