VBA user form dies - vba

I have a macro to process my inbox which has 7000 emails (I know, I know) and it takes a while. Naturally, I made a user form to show the progress by settling a label with remaining email count.
During execution however, I noticed that the form would stop updating at random time, probably as outlook goes into non responsive due to the macro execution.
Yesterday I accidentally dragged the now not-updating form aside which revealed another instance of the form beneath it, and that one is being updated correctly!
What is the mechanism behind this? My macro only created one instance of the form.
Edits:
The code is to delete emails with the same subject as the selected email. It was like the following. I added the commented out lines (pb.hide and pb.show) which somewhat solved the problem, although the flicker from hide/show is visually noticeable.
j = myitems.Count \ 20
l = 0
For i = myitems.Count To 1 Step -1
If l < j Then
l = l + 1
Else
l = 0
'pb.Hide
pb.Caption = "Emails to be processed: " & i
'pb.Show
End If
If TypeOf myitems(i) Is Outlook.MailItem Then
If myitems(i).ConversationID = selectedConversationID Then
myitems(i).Move deletedItemsFolder
ElseIf myitems(i).subject = selectedSubject Then
myitems(i).Move deletedItemsFolder
End If
End If
Next i
I have since switched to the Restrict method to get emails with same subject (as well as the GetConversation mehod) which takes seconds so the form is useless now. But they are not foolproof. There are times the selected email itself is not returned. Anyway, no biggie.

VBA is a single-threaded environment not designed for running secondary threads. If you consider creating a COM add-in you could use a low-level code such as Extended MAPI (or any other third-party wrappers around that api such as Redemption) which allows running secondary threads and deal with a store. So, you could move your loop on a secondary thread releasing the UI one (the main thread). Also it makes sense to consider using proper OOM methods and properties that can help to speed up the process of searching for specific items in Outlook. For example, you may consider using the Find/FindNext or Restrict methods of the Items class. They allow getting only items that correspond to the search criteria. Read more about these methods in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder`
The AdvancedSearch method of the Application class can be helpful as well. The key benefits of using the AdvancedSearch method in Outlook are:
The search is performed in another thread. You don’t need to run another thread manually since the AdvancedSearch method runs it automatically in the background.
Possibility to search for any item types: mail, appointment, calendar, notes etc. in any location, i.e. beyond the scope of a certain folder. The Restrict and Find/FindNext methods can be applied to a particular Items collection (see the Items property of the Folder class in Outlook).
Full support for DASL queries (custom properties can be used for searching too). To improve the search performance, Instant Search keywords can be used if Instant Search is enabled for the store (see the IsInstantSearchEnabled property of the Store class).
You can stop the search process at any moment using the Stop method of the Search class.

Related

Skip meeting request in for each selection loop

I am working on a simple Marco that use to download selected emails' attachments.
It was really a simple logic but I am still stuck.
I found out that my for each loop is always stop when it met the meeting request email.
(It almost took my whole day to figure it out that the meeting request is The Barricade.)
The problem can be fixed by deletion of the meeting request.
And yet, it is really annoying for a lazybones like me.
Therefore, I really curious that is there any method can let the for each loop just ignore/auto unselect meeting requests?
And I have already tried detect the email subject/context to seprate the meeting requests and normal mails.
But it seem like it would just exit the for each loop when it encountered the meeting request.
So currently I don't have any idea about how to fixing it.
Firstly, you need to show the relevant snippet of your code.
Secondly, you should not assume that you are only dealing with the MailItem objects - before accessing an of the MailItem-specific properties, check that the Class property (it is exposed by all OOM objects) is 43 (which is olMail).

Changing the email text in an "have server reply using specific reply" action with vba

I have the following rule in outlook:
have server reply using specific reply rule
My aim is now, to change the email text that gets send by the action with a vba script. The data we send out as a respones changes daily, so at the moment we have to change it by hand. I tried now many things, but don't come really to the possibility to change the text with vba.
I'm also not sure if i can get it with olRuleActionServerReply (because it has no item oder similar when i watch this action) or with olRuleActionTemplate...
I'm happy for any hint in this situation. Thank you in advance!
I tried to find anything in the documentation but didn't find anything helpful.
There is no way to use a dynamic reply with a different text used for the message body. You need to set up a different rule for that or handle incoming emails in VBA without rules involved.
For example, you could handle the NewMailEx event of the Application class where you could check the incoming email and prepare the response and send it automatically. This event fires once for every received item that is processed by Microsoft Outlook. The item can be one of several different item types, for example, MailItem, MeetingItem, or SharingItem. The EntryIDsCollection string contains the Entry ID that corresponds to that item. Use the Entry ID to call the NameSpace.GetItemFromID method and process the item.

Collection similar to Document.Revisions

Using VBA in Word 2013.
In the Word object model, Document.Revisions gives you a collection of revision objects (tracked changes), and you can accept or reject them programmatically. Plus, the collection itself has a Count property.
I did not find any feature in the object model that exposes the Undo/Redo count or history. To clarify, I am looking for a way to determine how many user changes are in the Undo stack and the Redo stack at a given time (because I am not editing the document contents via macro). It would be a bonus to be able to see the individual changes that are available to undo or redo, but I'm okay without that.
I know I can use the Document.Undo and Document.Redo methods, but I don't see a way to get a count of changes that can be undone or a count of undone changes that can be redone.
I suppose I could just iterate through the stack, so to speak, by invoking Document.Undo or .Redo and checking the return value to see whether there was anything there, then reversing what I had just undone/redone. I was hoping for something akin to Document.Revisions.Count.
EDIT: I need to access the Redo stack. It begins to look as though the object model does not expose that object. An old (2013) question recorded here suggests that there is no such object/collection (search for "redorecord").
Any ideas?
Thanks!
You could create & deploy a custom UndoRecord, so you can roll back all your actions in one go at the end, via code like 'ActiveDocument.Undo', without the need to keep track of all the intervening edits. See: https://learn.microsoft.com/en-us/office/vba/word/Concepts/Working-with-Word/working-with-the-undorecord-object
To deploy this for end-user actions in the document itself, simply use:
Option Explicit
Dim objUndo As UndoRecord
Sub CreateUndoRecord()
Set objUndo = Application.UndoRecord
objUndo.StartCustomRecord
End Sub
to create the Custom Record then, when you're finished, clear the lot with:
Sub ClearUndoRecord()
objUndo.EndCustomRecord
ActiveDocument.Undo
End Sub

Print original email when it is undelivered

I'm using MS Office 2016 Desktop App on Windows 10.
I didn't find any success searching online and also contacting MS Outlook Support to find that there is no easy way to achieve it except VBA script.
We send individual mail having an attachment, to a huge bulk of people. (I'm fine with this.)
I want when an email is not sent successfully (because of incorrect email address or other reason), to perform some action on the original email (not the undeliverable mail notice).
Possible action could be printing the mail and its attachments to some other email.
I want this as it is burdensome to print individual emails from around 1000s of emails daily.
MS Outlook rules don't provide the condition for 'when email undelivered' or similar. Can this be achieved with VBA script?
In theory - yes. Firstly, you need to make sure you get the actual NDR and the object type is ReportItem. That shouldn't be a problem if you are sending through Exchange, but if you are sending through POP3/SMTP account or if the NDR is delivered as a regular plain text message from the target server rather than your own Exchange Server, all bets are off.
The various report properties are stored in the recipient table of the NDR items. Unfortunately, ReportItem in OOM does not expose the Recipients collection like it does for MailItem. You can get NDR information from the Body property, but unfortunately OOM has a bug (left unfixed for a few years now) that causes ReportItem,Body to return meaningless garbled text. See Extract text string from undeliverable email body to Excel.
In most cases, you would be able to extract the message id of the original message (that caused in the NDR) from the PR_TRANSPORT_MESSAGE_HEADERS property (take a look at an NDR with OutlookSpy - I am its author - click IMessage button). You can access that property using ReportItem.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x007D001F"). You can then try to extract the In-Reply-To MIME header and use it to search for the original message in the Sent Items folder using Items.Find on the PR_INTERNET_MESSAGE_ID property (DASL name http://schemas.microsoft.com/mapi/proptag/0x1035001F). And here lies another problem - it is possible that the item in the Sent Items folder will not have that property: it is set automatically by Exchange, but the item in teh Sent Items folder in the cached store might not have it because most likely the item in never synchronized (this is due to bandwidth optimization trick by Outlook), so you might have to open the folder in the online mode using the MAPI_NO_CACHE flag, which OOM won't let you use - it is either Extended MAPI (C++ or Delphi) or Redemption (I am also its author - any language). See https://stackoverflow.com/questions/45952523/what-is-the-vba-property-for-server-folder-contains-x-itmes for an example.

How should one class request info from another one?

I am working on a VB.NET batch PDF exporting program for CAD drawings. The programs runs fine, but the architecture is a mess. Basically, one big function takes the entire process from start to finish. I would like to make a separate class, or several, to do the exporting work.
Here's the problem:
Sometimes the pdf file which will be created by my program already exists. In this case, I would like to ask the user if he/she would like to overwrite existing PDFs. I only want to do this if there is actually something which will be overwritten and I only want to do this once. In other words, "yes" = "yes to all." It seems wrong to have the form (which will be calling this new PDF exporting class) figure out what the PDF files will be called and whether there will be any overwrites. In fact, it would be best to have the names for the PDF files determined as the individual CAD drawings are processed (because I might want to use information which will only become available after loading the files in the CAD program in the background).
Here's the question:
How should I handle the process of prompting the user? I would like to keep all GUI logic (even something as simple as a dialog box) out of my PDF exporting class. I need a way for the PDF exporting class to say, "Hey, I need to know if I should overwrite or skip this file," and the form class (or any other class) to say, "Um, ok, I'll ask the user and get back to you."
It seems there ought to be some pattern to handle this situation. What is it?
Follow-ups:
Events: It seems like this is a good way to go. Is this about what the code should look like in the PDF exporting class?
Dim e As New FileExistsEventArgs(PDFFile)
RaiseEvent FileExists(Me, e)
If e.Overwrite Then
'Do stuff here
End If
A crazy idea: What about passing delegate functions to the export method of the PDF exporting class to handle the overwrite case?
You could use an Event, create a custom event argument class with a property on it that the application can call. Then when your app is handling the event prompt the user and then tell the exporter what to do. I'm a c# guy so let me give you a sample in there first:
void form_Load(object sender,EventArgs e)
{
//We are subscribing to the event here. In VB this is done differently
pdfExporter.FileExists+=new FileExistsEventHandler(pdfExporter_fileExists)
}
void pdfExporter_fileExists(object sender, FileExistsEventArgs e)
{
//prompUser takes the file and asks the user
if (promptUser(e.FileName))
{
}
}
Your PDF making class should raise an event. This event should have an eventargs object, which can have a boolean property called "Overwrite" which the GUI sets in whatever fashion you want. When the event returns in your PDF class you'll have the user's decision and can overwrite or not as needed. The Gui can handle the event anyway it likes.
Also, I commend you for working to keep the two seperate!
So the appropriate method on your class needs an optional parameter of
[OverwriteExisting as Boolean = False]
However your form will need to handle the logic of establishing whether or not a file exists. It seems to me that this would not be a function that you would want encapsulated within your PDF export class anyway. Assuming that your form or other function/class ascertains that an overwrite is required then the export methos is called passing True as a Boolean to your export class.
You could do a two phase commit type of thing.
The class has two interfaces, one for prepping the filenames and filesystem, and another for doing the actual work.
So the first phase, the GUI calls the initialization interface, and gets a quick answer as to whether or not anything needs over-writing. Possibly even a comprehensive list of the files that will get over-written. User answers, the boolean variable in the other responses is known, and then the exporter gets to the real work, and the user can go do something else.
There would be some duplication of computation, but it's probably worth it to get the user's part of the operation over with as soon as possible.
You can't keep the GUI stuff out of the PDF Exporting Code. but you can precisely define the minimum needed and not be tied to whatever framework you are using.
How I do it is that I have a Status class, and a Progress class. The two exist because Status is design to update a status message, and the Progress Bar is designed to work with a indicator of progress.
Both of them work with a object that has a class type of IStatusDisplay and IPrograssDisplay respectfully.
My GUI defines a object implementing IStatusDisplay (or IProgressDisplay) and registers as the current display with the DLL having Status and Progress. The DLL with Status and Progress also have two singleton called NullStatus and NullProgress that can be used when there is no UI feedback wanted.
With this scheme I can pepper my code with as many Status or Progress updates I want and I only worry about the implementations at the GUI Layer. If I want a silent process I can just use the Null objects. Also if I completely change my GUI Framework all the new GUI has to do is make the new object that implements the IStatusDisplay, IProgressDisplay.
A alternative is to raise events but I find that confusing and complicated to handle at the GUI level. Especially if you have multiple screen the user could switch between. By using a Interface you make the connection clearer and more maintainable in the longe.
EDIT
You can create a Prompt Class and a IPromptDisplay to handle situation like asking whether you want to overwrite files.
For example
Dim P as New Prompt(MyPromptDisplay,PromptEnum.YesNo)
'MyPromptDisplay is of IPromptDisplay and was registered by the GUI when the application was initialized
If PromptYesNo.Ask("Do you wish to overwrite files")= PromptReply.Yes Then
'Do stuff here
End If