I edit my Outlook message before sending to remove the first line, which is used for an internal process, so I can send the email cleanly to the recipient.
The script works as long as the format is consistent within this first line. When the format varies, such as difference in color or italic or bold, the script fails to remove this line.
I'm trying to remove this line regardless of format while maintaining the formatting in the rest of the email body.
Example: BAAR-6546543456.
The code will fail with either BAAR-6546543456. or BAAR-6546543456. .
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Dim OutMail As Object
Dim PrintMail As Object
Dim FirstLetters As String
Dim LastPos As Long
Dim sHostName As String
Dim DirectoryLine As String
On Error GoTo EndTask
Set Item = CreateObject("Outlook.Application")
Set OutMail = Item.ActiveInspector.CurrentItem
sHostName = Environ$("username")
FirstLetters = Left(OutMail.Body, 5)
LastPos = InStr(OutMail.Body, ".")
If Right(FirstLetters, 1) = "-" Then
If RecordFlag = "R" Then
Set objMsg = Application.CreateItem(olMailItem)
With objMsg
.Body = "Your document(s) have been dispatched on " & Format(Now(), "yyyy-mm-dd hh:mm:ss")
.BodyFormat = olFormatHTML ' send HTML
.Display
End With
Set PrintMail = Item.ActiveInspector.CurrentItem
Else
Dispatch_remark = " Dispatched to " & OutMail.To & "; " & OutMail.CC & " on " & Format(Now(), "yyyy-mm-dd hh:mm:ss")
OutMail.HTMLBody = OutMail.HTMLBody & Dispatch_remark
Set PrintMail = Item.ActiveInspector.CurrentItem
End If
Processing (this is the internal piece which is out of scope of this question)
OutMail.HTMLBody = Replace(OutMail.HTMLBody, DirectoryLine, "", , , 1)
If RecordFlag = "R" Then
objMsg.Delete
Else
OutMail.HTMLBody = Replace(OutMail.HTMLBody, Trim(Dispatch_remark), "", 1)
End If
End If
Set objMsg = Nothing
Set OutMail = Nothing
Set PrintMail = Nothing
Set Item = Nothing
EndTask:
End Sub
My focus is on the Replace method portion of the script, which fails when the format is inconsistent:
OutMail.HTMLBody = Replace(OutMail.HTMLBody, DirectoryLine, "", , , 1)
If RecordFlag = "R" Then
objMsg.Delete
Else
OutMail.HTMLBody = Replace(OutMail.HTMLBody, Trim(Dispatch_remark), "", 1)
End If
This code inspects the active mail and has two ways to delete the line you want. The first is commented and you can use it if you are sure that the line you want to delete is this first one of the mail. If it isn't you should the loop part.
Choose the way you want and adapt to your code.
Sub Mail_DeleteParagraph()
Dim Ins As Object: Set Ins = Application.ActiveInspector
Dim Doc As Object: Set Doc = Ins.WordEditor
Dim oPara As Object
' Delete first paragraph
'Set oPara = Doc.Paragraphs(1).Range
'oPara.Delete
' Delete paragraph that starts with 'BAAR-' and has '.'
Dim i As Integer
For Each oPara In Doc.Paragraphs
Debug.Print i & " - " & oPara.Range
If Left(oPara.Range, 5) = "BAAR-" And InStr(1, oPara.Range, ".") > 0 Then
oPara.Range.Delete
exit sub
End If
i = i + 1
Next oPara
End Sub
Related
Problem:
I want to type a paragraph of text, add my short signature + date + time and format everything so another person would see I added this comment to the mail.
Example:
This is my personal comment on the topic // Signature Tom, 22.08.21, 14:00 (<- add the last part by VBA-Code and put this whole paragraph in red and italic by VBA)
Dear Sir or Madam
...-> mail body
Sincerely
What I have
So far it is two separate VBA sub routines I managed to create with trial and error from the web, but I would like it in one step (because I have to call them one after a time).
1st:
Option Explicit
Public Sub AddShortSignature()
Dim xDoc As Object
Dim xSel As Object
On Error Resume Next
Select Case TypeName(Application.ActiveWindow)
Case "Explorer"
Set xDoc = Application.ActiveExplorer.Selection(1).GetInspector.WordEditor
Case "Inspector"
Set xDoc = Application.ActiveInspector.WordEditor
End Select
Set xSel = xDoc.Application.Selection
xSel.InsertBefore Format(Now, "DD/MM/YYYY hh/mm")
xSel.InsertBefore Format(" // Tom., ")
Set xDoc = Nothing
Set xSel = Nothing
SendKeys "{End}", True
SendKeys "+{Home}", True
End Sub
and 2nd, format everything to my liking:
Sub formateverything()
Dim objDoc As Object
Dim objSel As Object
Set objDoc = ActiveInspector.WordEditor
Set objSel = objDoc.Windows(1).Selection
objSel.Font.Name = "Arial"
objSel.Font.Italic = True
objSel.Font.Bold = False
objSel.Font.Underline = False
objSel.Font.Color = RGB(0, 0, 0)
objSel.Font.Size = 14
End Sub
As I can see in your pictures, your comment and signature comes at the top of the mail (first paragraph), so this is what I came up with.
Public Sub OutlookMail_CommentAndSignature()
Dim Ins As Outlook.Inspector
Dim Doc As Object
Dim mySignature As String
Dim oPara As Object ' paragraph
Dim paraText As String ' paragraph text
Dim paraLength As Integer ' paragraph length
Set Ins = Application.ActiveInspector
Set Doc = Ins.WordEditor
' Signature
mySignature = " // Tom., " & Format(Now, "DD/MM/YYYY hh:mm") & vbCrLf
' ' First paragraph: comment + signature
' Set oPara = Doc.Paragraphs(1).Range
' paraLength = Len(oPara.Text)
' oPara.Text = Left(oPara.Text, paraLength - 1) ' without vbCrLf
' oPara.Text = oPara.Text & mySignature
' ' format first paragraph
' oPara.Font.Italic = wdToggle
' oPara.Font.ColorIndex = wdRed
' Selected text
Dim selRange As Object: Set selRange = Doc.Application.Selection.Range
' set text range to italic and red
selRange.InsertAfter mySignature
' format first paragraph
selRange.Font.Italic = wdToggle
selRange.Font.ColorIndex = wdRed
End Sub
This code sets the cursor to the end position of the message field, and the inserts a 'signature'. With this you set whatever position you want.
Public Sub OutlookMail_SetCursorAT()
Dim Ins As Outlook.Inspector
Dim Doc As Object
Dim Range As Object
Dim Pos As Long
Set Ins = Application.ActiveInspector
Set Doc = Ins.WordEditor
' Set the cursor to the end position of the message field
If Not Doc Is Nothing Then
Pos = Doc.Range.End - 1
Set Range = Doc.Range(Pos, Pos)
Range.Select
End If
' Signature
Dim mySignature As String
mySignature = vbCrLf & Format(" // Tom., ")
mySignature = mySignature & vbCrLf & Format(Now, "DD/MM/YYYY hh/mm")
Range.InsertAfter mySignature
End Sub
I have a form with a multiline texbox, when I write a paragraph and try to copy it to outlook just as It looks in texbox with the spaces between lines but it copy all the paragraph in one line. You can see it in the images. I know that I can write the paragraph in HTML code in .HTMLBody, but that's not what I want because I want to edit that anytime I send a mail. I don't know if there is a code to do that, if not could you give me some other ideas?
Form_Enviar_Correo
Outlook Mail
Sub ENVIAR()
Dim a As Worksheet, b As Worksheet
Dim OApp As Object, OMail As Object, sbdy As String
Application.ScreenUpdating = False
Application.DisplayAlerts = False
On Error Resume Next
ChDir (ActiveWorkbook.Path)
Dest = Form_Enviar_Correo.Txt_Para.Value
Asun = Form_Enviar_Correo.Txt_Asunto.Value
CC = Form_Enviar_Correo.Txt_CC.Value
Set OApp = CreateObject("Outlook.Application")
Set OMail = OApp.CreateItem(0)
spie = "<img align=left width=80 height=90 src=https://xxxxxxxxxxxxx.png>"
sbdy = spie
With OMail
.To = Dest
.CC = CC
'.BCC = SCop
.Subject = Asun
.Body = Form_Enviar_Correo.Txt_Cuerpo.Text
.HTMLBody = sbdy
.Display
'.Send
End With
Set OMail = Nothing
Set OApp = Nothing
Application.ScreenUpdating = True
Application.DisplayAlerts = True
'MsgBox ("El mensaje se envió con éxito"), vbInformation, "AVISO"
End Sub
Assuming the lines are separated by a carriage return vbCr you can just replace them all with the HTML equivalent with one line of code using the Replace function.
Dim sText as String
sTest = "This is" & vbCr & "a test"
MsgBox sTest
Dim sHTMLFormat as String
sHTMLFormat = Replace(sTest, vbCr, "<br>")
MsgBox sHTMLFormat
so...
.HTMLBody = Replace(Form_Enviar_Correo.Txt_Cuerpo.Text, vbCr, "<br>")
They may also be separated by vbNewLine or vbCrLf or vbLf so use the one that works in your case.
I have looked through multiple posts to send an email if a value in a range of cells changes and adapted the code I found in those posts to suit my needs, but for some reason the email is not being sent when the value in any cell of the range defined changes, and I am a little lost at why. Any guidance is greatly appreciated. Please see code below (please note that for confidentiality purposes the emails and names are fake).
Private Sub Workbook_Change(ByVal Target As Range)
' Uses early binding
' Requires a reference to the Outlook Object Library
Dim RgSel As Range, RgCell As Range
Dim OutlookApp As Object, MItem As Object
Dim Subj As String, EmailAddr As String, Recipient As String
Dim CustName As String, Msg As String
Dim pEmail As String
On Error GoTo NX
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Set RgCell = Range("C2:C100")
Set RgSel = Intersect(Target, RgCell)
If Not RgSel Is Nothing Then
Set OutlookApp = CreateObject("Outlook.Application")
Set MItem = OutlookApp.CreateItem(0)
For Each cell In RgCell
If cell.Value = "Bob" Then 'Fake Name for posting question
pEmail = "BobT#SomethingBlahBlahBlah.com" 'Fake email address used for posting question
CustName = cell.Offset(0, -1).Value
Subj = "***NEW ITEM ASSIGNED***" & " - " & UCase(CustName)
Recipient = "Bob T. Builder" 'Fake name for posting question
EmailAddr = pEmail
' Compose Message
Msg = "Dear, " & Recipient & vbCrLf & vbCrLf
Msg = Msg & "I have assigned " & CustName & "'s" & " item to you." & vbCrLf
Msg = Msg & "Please review the information in their customer folder in the L: Drive." & vbCrLf & vbCrLf
Msg = Msg & "Sincerely," & vbCrLf & vbCrLf & vbCrLf
Msg = Msg & "Bob's Boss" & vbCrLf 'Fake name for posting question
Msg = Msg & "Vice President"
' Create Mail Item and send
With MItem
.to = EmailAddr
.Subject = Subj
.body = Msg
.Save 'This will change to .send after testing is complete
End With
Set RgSel = Nothing
Set OutlookApp = Nothing
Set MItem = Nothing
End If
Next cell
Application.DisplayAlerts = True
Application.ScreenUpdating = True
NX:
Resume Next
End Sub
I think you've intended to use the Worksheet_Change event but have Private Sub Workbook_Change... instead.
Additional issues:
For Each cell In RgCell should probably be For Each cell in RgSel, or For Each cell in Target - otherwise the code runs through each cell in C2:C100, and not just the cell(s) changed, or Target.
There is no need to Set RgSel = Nothing
With Set MItem = OutlookApp.CreateItem(0), you create an email message before you've checked If cell.Value = "Bob". Move this line within the If statement.
Set OutlookApp = Nothing should be outside the For Each loop, i.e. it should be done after you've finished looping.
On Error GoTo NX, and then NX: Resume Next, is equivalent to On Error Resume Next, which doesn't handle any errors, but rather ignores them.
You may be missing a closing End If, or it is not included in this snippet.
I'm working on a macro which checks the attachment name against the subject name and the domain.
At the moment there's a couple of minor issues, I don't want the macro to recognise images in the signature as an attachment. Solutions I've seen include using an if statement to work out the size, so for example only check attachments over 5kb etc.
The other issue is, if there is no attachment at all, the macro falls over! I think I need another if statement in there at the end to do an item count but I'm not sure how that alters my conditions at the end of the macro!
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Dim outRecips As Outlook.Recipients
Dim outRecip As Outlook.Recipient
Dim outPropAcc As Outlook.PropertyAccessor
Dim strDomain As String
Dim lngPreDom As Long
Dim lngPostDom As Long
Dim strSubject As String
Dim objAttachments As Outlook.Attachments
Dim strAttachment As String
Dim Response As String
' set domain value
Const PR_SMTP_ADDRESS As String = "http://schemas.microsoft.com/mapi/proptag/0x39FE001E"
Set outRecips = Item.Recipients
For Each outRecip In outRecips
Set outPropAcc = outRecip.PropertyAccessor
strDomain = outPropAcc.GetProperty(PR_SMTP_ADDRESS)
strDomain = Split(strDomain, "#")(1)
lngPreDom = InStr(strDomain, "#")
lngPostDom = InStr(strDomain, ".")
strDomain = LCase(Mid(strDomain, lngPreDom + 1, lngPostDom - lngPreDom - 1))
Exit For
Next
' set subject value
strSubject = LCase(Item.Subject)
' set attachment name
Set objAttachments = Item.Attachments
strAttachment = LCase(objAttachments.Item(1).FileName)
' if external recipient, check email contents
If strDomain <> "exampleemail" _
Then
If InStr(strSubject, strDomain) = 0 _
Or InStr(strAttachment, strDomain) = 0 _
Or InStr(strAttachment, strSubject) = 0 _
Then
Response = "Attachment/Subject do not match Recipient(s)" & vbNewLine & "Send Anyway?"
If MsgBox(Response, vbYesNo + vbExclamation + vbMsgBoxSetForeground, "Check Recipients") = vbNo Then
Cancel = True
End If
End If
End If
End Sub
Use the Attachment.PropertyAccessor object to read the PR_ATTACHMENT_HIDDEN property (http://schemas.microsoft.com/mapi/proptag/0x7FFE000B); if it's true it's an embedded image (usually in signatures).
I have been using VBA to some degree, using this code:
Sub ExtractEmail()
Dim OlApp As Outlook.Application
Dim Mailobject As Object
Dim Email As String
Dim NS As NameSpace
Dim Folder As MAPIFolder
Set OlApp = CreateObject("Outlook.Application")
' Setup Namespace
Set NS = ThisOutlookSession.Session
' Display select folder dialog
Set Folder = NS.PickFolder
' Create Text File
Set fs = CreateObject("Scripting.FileSystemObject")
Set a = fs.CreateTextFile("c:\mydocuments\emailss.txt", True)
' loop to read email address from mail items.
For Each Mailobject In Folder.Items
Email = Mailobject.To
a.WriteLine (Email)
Next
Set OlApp = Nothing
Set Mailobject = Nothing
a.Close
End Sub
However this gives output as the names of the email addresses and not the actual email address with the "something#this.domain".
Is there an attributte of the mailobject that will allow the email addresses and not the names to be written from the 'To' Textbox.
Thanks
Check out the Recipients collection object for your mail item, which should allow you to get the address: http://msdn.microsoft.com/en-us/library/office/ff868695.aspx
Update 8/10/2017
Looking back on this answer, I realized I did a bad thing by only linking somewhere and not providing a bit more info.
Here's a code snippet from that MSDN link above, showing how the Recipients object can be used to get an email address (snippet is in VBA):
Sub GetSMTPAddressForRecipients(mail As Outlook.MailItem)
Dim recips As Outlook.Recipients
Dim recip As Outlook.Recipient
Dim pa As Outlook.PropertyAccessor
Const PR_SMTP_ADDRESS As String = _
"http://schemas.microsoft.com/mapi/proptag/0x39FE001E"
Set recips = mail.Recipients
For Each recip In recips
Set pa = recip.PropertyAccessor
Debug.Print recip.name &; " SMTP=" _
&; pa.GetProperty(PR_SMTP_ADDRESS)
Next
End Sub
It looks like, for email addresses outside of your organization, the SMTP address is hidden in emailObject.Recipients(i).Address, though it doesn't seem to allow you to distinguish To/CC/BCC.
The Microsoft code was giving me an error, and some investigating reveals that the schema page is no longer available. I wanted a semicolon-delaminated list of email addresses that were either in my Exchange organization or outside of it. Combining it with another S/O answer to convert inner-company email display names to SMTP names, this does the trick.
Function getRecepientEmailAddress(eml As Variant)
Set out = CreateObject("System.Collections.Arraylist") ' a JavaScript-y array
For Each emlAddr In eml.Recipients
If Left(emlAddr.Address, 1) = "/" Then
' it's an Exchange email address... resolve it to an SMTP email address
out.Add ResolveDisplayNameToSMTP(emlAddr)
Else
out.Add emlAddr.Address
End If
Next
getRecepientEmailAddres = Join(out.ToArray(), ";")
End Function
If the email is inside your organization, you need to convert it to an SMTP email address. I found this function from another StackOverflow answer helpful:
Function ResolveDisplayNameToSMTP(sFromName) As String
' takes a Display Name (i.e. "James Smith") and turns it into an email address (james.smith#myco.com)
' necessary because the Outlook address is a long, convoluted string when the email is going to someone in the organization.
' source: https://stackoverflow.com/questions/31161726/creating-a-check-names-button-in-excel
Dim OLApp As Object 'Outlook.Application
Dim oRecip As Object 'Outlook.Recipient
Dim oEU As Object 'Outlook.ExchangeUser
Dim oEDL As Object 'Outlook.ExchangeDistributionList
Set OLApp = CreateObject("Outlook.Application")
Set oRecip = OLApp.Session.CreateRecipient(sFromName)
oRecip.Resolve
If oRecip.Resolved Then
Select Case oRecip.AddressEntry.AddressEntryUserType
Case 0, 5 'olExchangeUserAddressEntry & olExchangeRemoteUserAddressEntry
Set oEU = oRecip.AddressEntry.GetExchangeUser
If Not (oEU Is Nothing) Then
ResolveDisplayNameToSMTP = oEU.PrimarySmtpAddress
End If
Case 10, 30 'olOutlookContactAddressEntry & 'olSmtpAddressEntry
ResolveDisplayNameToSMTP = oRecip.AddressEntry.Address
End Select
End If
End Function
The answers above did not work for me. I think they only work when the recipient is in the address book. The following code is also to able to lookup email addresses from outside the organisation. Additionally it makes a distinction between to/cc/bcc
Dim olRecipient As Outlook.Recipient
Dim strToEmails, strCcEmails, strBCcEmails As String
For Each olRecipient In item.Recipients
Dim mail As String
If olRecipient.AddressEntry Is Nothing Then
mail = olRecipient.Address
ElseIf olRecipient.AddressEntry.GetExchangeUser Is Nothing Then
mail = olRecipient.Address
Else
mail = olRecipient.AddressEntry.GetExchangeUser.PrimarySmtpAddress
End If
Debug.Print "resolved", olRecipient.Name, mail
If olRecipient.Type = Outlook.OlMailRecipientType.olTo Then
strToEmails = strToEmails + mail & ";"
ElseIf olRecipient.Type = Outlook.OlMailRecipientType.olCC Then
strCcEmails = strCcEmails + mail & ";"
ElseIf olRecipient.Type = Outlook.OlMailRecipientType.olBCC Then
strBCcEmails = strBCcEmails + mail & ";"
End If
Next
Debug.Print strToEmails
Debug.Print strCcEmails
Debug.Print strBCcEmails
Another code alternative (based initially on the answer by #andreasDL) which should be able to be used...
Pass in a MailItem to the EmailAddressInfo function to get an array of the Sender, To and CC fields from the message
Private Const olOriginator As Long = 0, olTo As Long = 1, olCC As Long = 2, olBCC As Long = 3
'BCC addresses are not included within received messages
Function PrintEmailAddresses(olItem As MailItem)
If olItem.Class <> olMail Then Exit Function
Dim Arr As Variant: Arr = EmailAddressInfo(olItem)
Debug.Print "Sender: " & Arr(olOriginator)
Debug.Print "To Address: " & Arr(olTo)
Debug.Print "CC Address: " & Arr(olCC)
End Function
Private Function EmailAddressInfo(olItem As MailItem) As Variant
If olItem.Class <> olMail Then Exit Function
On Error GoTo ExitFunction
Dim olRecipient As Outlook.Recipient
Dim olEU As Outlook.ExchangeUser
Dim olEDL As Outlook.ExchangeDistributionList
Dim ToAddress, CCAddress, Originator, email As String
With olItem
Select Case UCase(.SenderEmailType)
Case "SMTP": Originator = .SenderEmailAddress
Case Else
Set olEU = .Sender.GetExchangeUser
If Not olEU Is Nothing Then Originator = olEU.PrimarySmtpAddress
End Select
End With
For Each olRecipient In olItem.Recipients
With olRecipient
Select Case .AddressEntry.AddressEntryUserType
Case olSmtpAddressEntry 'OlAddressEntryUserType.
email = .Address
Case olExchangeDistributionListAddressEntry, olOutlookDistributionListAddressEntry
Set olEDL = .AddressEntry.GetExchangeDistributionList
email = IIf(Not olEDL Is Nothing, olEDL.PrimarySmtpAddress, "")
Case Else
Set olEU = .AddressEntry.GetExchangeUser
email = IIf(Not olEU Is Nothing, olEU.PrimarySmtpAddress, "")
End Select
If email <> "" Then
Select Case .Type
Case olTo: ToAddress = ToAddress & email & ";"
Case olCC: CCAddress = CCAddress & email & ";"
End Select
End If
End With
Next
EmailAddressInfo = Array(Originator, ToAddress, CCAddress)
ExitFunction:
End Function
This is what worked for me with Outlook 2019. Use your internal domain name(s). Might need some tweaking yet - not heavily tested. Place code in the ThisOutlookSession module. (Updated to handle Exchange distribution lists 7/31/20.)
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Dim xMailItem As Outlook.MailItem
Dim xRecipients As Outlook.Recipients
Dim OutRec As Outlook.Recipient
Dim OutTI As Outlook.TaskItem
Dim i As Long
Dim j As Long
Dim xOKCancel As Integer
Dim sMsg As String
Dim oMembers As AddressEntries
Dim oMember As AddressEntry
Dim sDomains As String
Dim sTemp As String
On Error Resume Next
If Item.Class <> olMail Then GoTo ExitCode
sDomains = "#test1.com #test2.com"
Set xMailItem = Item
Set xRecipients = xMailItem.Recipients
'Loop through email recipients to get email addresses
For i = xRecipients.Count To 1 Step -1
'If we have a text address entry in the email
If InStr(xRecipients.Item(i).AddressEntry, "#") > 0 Then
sTemp = xRecipients.Item(i).AddressEntry
If InStrRev(sDomains, LCase(Mid(sTemp, InStr(sTemp, "#"), 254))) <= 0 Then
sMsg = sMsg & sTemp & vbCrLf
End If
Else
Select Case xRecipients.Item(i).AddressEntry.DisplayType
Case Is = olDistList
Set oMembers = xRecipients.Item(i).AddressEntry.Members
For j = oMembers.Count To 1 Step -1
Set oMember = oMembers.Item(j)
sTemp = oMember.GetExchangeUser.PrimarySmtpAddress
If InStrRev(sDomains, LCase(Mid(sTemp, InStr(sTemp, "#"), 254))) <= 0 Then
sMsg = sMsg & sTemp & vbCrLf
End If
Set oMember = Nothing
Next j
Set oMembers = Nothing
Case Is = olUser
Set OutTI = Application.CreateItem(3)
OutTI.Assign
Set OutRec = OutTI.Recipients.Add(xRecipients.Item(i).AddressEntry)
OutRec.Resolve
If OutRec.Resolved Then
sTemp = OutRec.AddressEntry.GetExchangeUser.PrimarySmtpAddress
If InStrRev(sDomains, LCase(Mid(sTemp, InStr(sTemp, "#"), 254))) <= 0 Then
sMsg = sMsg & sTemp & vbCrLf
End If
End If
Set OutTI = Nothing
Set OutRec = Nothing
Case Else
MsgBox "Unaccomodated AddressEntry.DisplayType."
GoTo ExitCode
End Select
End If
Next i
'Display user message
If Len(sMsg) > 0 Then
sMsg = "This email is addressed to the following external Recipients:" & vbCrLf & vbCrLf & sMsg
xOKCancel = MsgBox(sMsg, vbOKCancel + vbQuestion, "Warning")
If xOKCancel = vbCancel Then Cancel = True
End If
End Sub