I have this code to delete all contacts from Microsoft Outlook that have a specific user property:
public void PurgePTSContacts(ref long rlCount)
{
int iLastPercent = 0;
rlCount = 0;
try
{
// Get the MAPI namespace object (not sure exactly what this is)
Outlook.NameSpace MAPI = _OutlookApp.GetNamespace("MAPI");
if (MAPI != null)
{
// Now get the default Outlook Contacts folder
Outlook.MAPIFolder oFolder = MAPI.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts);
if (oFolder != null)
{
// Ensure it is a calendar folder. This test is not strictly required.
if (oFolder.DefaultItemType == Outlook.OlItemType.olContactItem)
{
// Get the collect of items from the calendar folder
Outlook.Items oFolderItems = oFolder.Items;
if (oFolderItems != null)
{
int iNumItems = oFolderItems.Count;
// Progress to do
int iItem = 0;
foreach (object oItem in oFolderItems)
{
iItem++;
if(oItem is Outlook.ContactItem )
{
Outlook.ContactItem oContact = (oItem as Outlook.ContactItem);
int iPercent = ((iNumItems - iItem) + 1) * 100 / iNumItems;
if (iPercent >= iLastPercent + 5 || iPercent == 100)
{
iLastPercent = iPercent;
// Show progress
}
Outlook.UserProperties oContactDetails = oContact.UserProperties;
if (oContactDetails != null)
{
if (oContactDetails.Find("xxxxx") != null)
{
oContact.Delete();
rlCount++;
}
}
oContact = null;
}
}
}
}
}
}
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
Now, what i don't understand is that when I import the contacts, there are 428. But when I delete the contacts, it only does just over 200. Then I have to repeatedly call my PurgePTSContacts method and it purges some more.
With each subsequent call less and less are purged until it is 0.
Why can't I purge all 400x in one function call?
Do not use foreach when you are modifying the collection - use a down loop:
for (int i = FolderItems.Count; i >= 1; i--)
{
object oItem = FolderItems[i];
Outlook.ContactItem oContact = (oItem as Outlook.ContactItem);
if (oContact != null)
{
...
Marshal.ReleaseComObject(oContact);
}
Marshal.ReleaseComObject(oItem);
}
UPDATE. Per OP request, an example that shows how to delete all items in a folder using Redemption (I am its author):
RDOSession session = new RDOSession();
session.MAPIOBJECT = MAPI.MAPIOBJECT; //an instance of the Namespace object from your snippet above
RDOFolder rFolder = session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts); //or you can use RDOSession.GetFolderFromID if you already have an Outlook.MAPIFolder
rFoldder.EmptyFolder ();
If you want to only delete contacts (but not distribution lists), you can retreive their entry ids first using RDOFolder.Items.MAPITable.ExecSQL("SELECT EntryID from Folder where MessageClass LIKE 'IPM.Contact%' ") (ExecSQL returns an instance of the ADODB.Recordset COM object), use it to to build an array of entry ids, and pass it to RDOFolder.Items.RemoveMultiple().
To create an instance of the RDOSession object, you can also use the RedemptionLoader class if you don't want to register redemption.dll in the registry using regsvr32.exe (you can just place redemption.dll alongside your executable).
Related
I have an VSTO Outlook Add-in. In the compose windows I have a button which removes all the recipients that satify a condition.
Below the code of the button click event:
private void ClickButton(object sender, System.Windows.RoutedEventArgs e)
{
List<Outlook.Recipient> toList = new List<Outlook.Recipient>();
List<Outlook.Recipient> CCList = new List<Outlook.Recipient>();
List<Outlook.Recipient> BCCList = new List<Outlook.Recipient>();
Outlook.Recipient recipient = null;
Outlook.Recipients recipients = this.MyMailItem?.Recipients;
for (int i = recipients?.Count ?? 1; i > 0; i -= 1)
{
recipient = recipients[i];
if (!this.recipientsList.Contains(recipient))
{
if (recipient.Type == (int)Outlook.OlMailRecipientType.olTo)
{
toList.Add(recipient);
}
else if (recipient.Type == (int)Outlook.OlMailRecipientType.olCC)
{
CCList.Add(recipient);
}
else if (recipient.Type == (int)Outlook.OlMailRecipientType.olBCC)
{
BCCList.Add(recipient);
}
}
Marshall.ReleaseComObject(recipient);
recipient = null;
}
this.MyMailItem.To = null;
this.MyMailItem.CC = null;
this.MyMailItem.BCC = null;
if (toList != null && toList.Count > 0)
{
MyMailItem.To = string.Join(";", this.GetRecipientsAsString(toList));
}
if (CCList != null && CCList.Count > 0)
{
MyMailItem.CC = string.Join(";", this.GetRecipientsAsString(CCList));
}
if (BCCList != null && BCCList.Count > 0)
{
MyMailItem.BCC = string.Join(";", this.GetRecipientsAsString(BCCList));
}
this.recipientsList.Clear();
}
Note that recipientsList is a global variable of type List<Outlook.Recipient>.
private List<string> GetRecipientsAsString(List<Outlook.Recipient> recipientsList)
{
List<string> recList = null;
if (recipientsList?.Count > 0)
{
recList = new List<string>();
foreach (Outlook.Recipient recipient in recipientsList)
{
recList.Add(string.IsNullOrWhiteSpace(recipient.Name) ? recipient.Address : recipient.Name);
}
}
return recList;
}
Sometimes, not always, i am receiving below exception:
COM object that has been separated from its underlying RCW cannot be used.
This is thrown in GetRecipientsAsString method at this line:
recList.Add(string.IsNullOrWhiteSpace(recipient.Name) ? recipient.Address : recipient.Name);
What am I doing wrong?
Keeping Outlook COM objects in the collection is the source of problems. For example, when you set the To, Cc or Bcc properties the Recipients collection is updated, i.e. old recipient instances are destroyed and new ones are created/added. So, your Recipient objects stored in a list are getting obsolete and calling any properties or methods in the GetRecipientsAsString function could lead to exceptions like yours.
Instead, I'd recommend keeping a list of email addresses or names. In that case you can re-create a recipient instance if required by using the CreateRecipient function of the Namespace class.
I am running through a set of records using a for each loop, and also doing simple checks to ensure that good data is inserted into a database table.
Sometimes the dataset can be missing the LegistarID value, the change I need to do in my code, is to add a check for LegistarItem,
if the value of LegistarID is missing, but the AgendaItem value is not, then assign the value of AgendaItem to LegistarID
if LegistarId is missing, and there is also no AgendaItem value, then return a message to the user, to let them know that these values need to be present in the dataset they are trying to import.
I know it does not sound complex, but I am having a hard time making this change successfully. I need a bit of help if possible, please.
Here is my code as I currently have it:
if (ModelState.IsValid)
{
using (Etities db = new Entities())
{
foreach (var i in meeting)
{
if (i.MeetingID == 0)
{
message = string.Format("This file is missing the Meeting ID value of at least 1 record. \n Verify that the data you are trying to upload meets the criteria, and then try to upload your file again.", i.MeetingID);
return new JsonResult { Data = new { status = status, message = message } };
}
else
{
// development
var compositeKey = db.MeetingAgenda.Find(i.MeetingID, i.AgendaItem);
if (compositeKey == null)
{
// Add new
// development
db.MeetingAgenda.Add(i);
//
}
else
{
// Serves as an update, or addition of a previously imported dataset
db.Entry(compositeKey).CurrentValues.SetValues(i.MeetingID);
db.Entry(compositeKey).State = EntityState.Modified;
}
}
}
db.SaveChanges();
status = true;
}
}
else
{
message = string.Format("Please, verify that the file you are trying to upload is correctly formatted, and that the data it contains, meets the expected criteria, then click the upload button again. \n Thank you!");
return new JsonResult { Data = new { status = status, message = message } };
}
I think that part of the code I need is something like this:
else if (i.LegistarID == 0 and i.AgendaItem != 0)
{
i.LegistarID = i.AgendaItem
}
I just am unsure how in the current code place it.
I would check all rows before returning a result.
if (ModelState.IsValid) {
var errors = new List<string> ();
var rowCounter = 1;
using (Etities db = new Entities ()) {
foreach (var i in meeting) {
if (i.MeetingID == 0) {
// Let the user know this row is bad
errors.Add ($"Row {rowCounter}: This file is missing the Meeting ID. Verify that the data you are trying to upload meets the criteria, and then try to upload your file again.");
}
// Check if LegistarID is missing
if (i.LegistarID == 0) {
// Check if Agenda Item is present
if (i.AgendaItem == 0) {
errors.Add ($"Row {rowCounter}: Meeting has no LegistarID and no Agenda Item. Please check data");
} else {
i.LegistarID = i.AgendaItem
}
}
// development
var compositeKey = db.MeetingAgenda.Find (i.MeetingID, i.AgendaItem);
if (compositeKey == null) {
// Add new
// development
db.MeetingAgenda.Add (i);
//
} else {
// Serves as an update, or addition of a previously imported dataset
db.Entry (compositeKey).CurrentValues.SetValues (i.MeetingID);
db.Entry (compositeKey).State = EntityState.Modified;
}
rowCounter++;
}
// If there are errors do not save and return error message
if (errors.Count > 0) {
return new JsonResult { Data = new { status = false, message = string.Join ("\n", errors) } };
}
db.SaveChanges ();
status = true;
}
} else {
message = string.Format ("Please, verify that the file you are trying to upload is correctly formatted, and that the data it contains, meets the expected criteria, then click the upload button again. \n Thank you!");
return new JsonResult { Data = new { status = status, message = message } };
}
The "if(i.MeetingID == 0)" else is redundant, because you are returning if the condition is met. So to avoid unneeded/confusing nesting I would rewrite the actual code (of the loop only) as:
foreach (var i in meeting)
{
if (i.MeetingID == 0)
{
message = string.Format("This file is missing the Meeting ID value of at least 1 record. \n Verify that the data you are trying to upload meets the criteria, and then try to upload your file again.", i.MeetingID);
return new JsonResult { Data = new { status = status, message = message } };
}
// development
var compositeKey = db.MeetingAgenda.Find(i.MeetingID, i.AgendaItem);
if (compositeKey == null)
{
// Add new
// development
db.MeetingAgenda.Add(i);
//
}
else
{
// Serves as an update, or addition of a previously imported dataset
db.Entry(compositeKey).CurrentValues.SetValues(i.MeetingID);
db.Entry(compositeKey).State = EntityState.Modified;
}
}
Then, I would add the new condition in between the MeetingID = 0 check and the rest of the code, like this:
foreach (var i in meeting)
{
if (i.MeetingID == 0)
{
message = string.Format("This file is missing the Meeting ID value of at least 1 record. \n Verify that the data you are trying to upload meets the criteria, and then try to upload your file again.", i.MeetingID);
return new JsonResult { Data = new { status = status, message = message } };
}
// *** New check on LegistarID and AgendaItem ***
if(i.LegistarID == 0)
{
// Is there a chance to fill LegistarID with AgendaItem?
if(i.AgendaItem != 0)
{
// Yes, fill it and then let the rest of the code flow peacefully.
i.LegistarID = i.AgendaItem
}
else
{
// No way: I must stop the procedure here and warn the user about this.
// return "these values need to be present in the dataset they are trying to import."
}
}
// development
var compositeKey = db.MeetingAgenda.Find(i.MeetingID, i.AgendaItem);
if (compositeKey == null)
{
// Add new
// development
db.MeetingAgenda.Add(i);
//
}
else
{
// Serves as an update, or addition of a previously imported dataset
db.Entry(compositeKey).CurrentValues.SetValues(i.MeetingID);
db.Entry(compositeKey).State = EntityState.Modified;
}
}
I am developing a c# add-in for Outlook 2016. In some procedures I need to remove the mail item category and user properties. I am trying to do this by removing properties from inbox/sent folder simultaneously. For this I start 2 new tasks:
Task.Factory.StartNew(() => RunRemovingExistingVisualIndicationFromInboxFolder(), GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken.Token);
Task.Factory.StartNew(() => RunRemovingExistingVisualIndicationFromSentFolder(), GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken.Token);
In my code below, is the function which removes these properties (for example, I attach only processing mail items in my Inbox folder. Note: the same code is used for my Sent folder. I only changed the Outlook folder to use the "Sent" folder.)
public static void RunRemovingExistingVisualIndicationFromInboxFolder()
{
NLogMethods.GetInstance().WriteInfoLog("[ADDITIONALVISUALINDICATIONSETTINGSFORM -> RUNREMOVEEXISTINGVISUALINDICATION()]: Processing " + "\"" + "Inbox" + "\"" + " folder started.");
Outlook.Items folderItemsInbox = null;
folderItemsInbox = OutlookMethods.currentOAppNameSpace.Stores[JSONMethods.ReadAddInSetting().AddInPreferredMailBoxStoreName].GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox).Items;
if (folderItemsInbox.Count > 0)
{
for (int i = 1; i <= folderItemsInbox.Count; i++)
{
totalProcessedInboxMailItems++;
if (GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken.IsCancellationRequested)
{
GlobalEnvironmentMethods.globalProcessingRemovingVisualIndicationTaskState = false;
GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken.Dispose();
GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken = null;
GlobalEnvironmentMethods.ServiceProcedures.ReleaseComObject(new[] { folderItemsInbox });
totalProcessedInboxMailItems = 0;
NLogMethods.GetInstance().WriteWarnLog("[ADDITIONALVISUALINDICATIONSETTINGSFORM -> RUNREMOVEEXISTINGVISUALINDICATION()]: The task was cancelled by user.");
return;
}
else
{
Outlook.MailItem mailItemObjectInbox = null;
mailItemObjectInbox = folderItemsInbox[i] as Outlook.MailItem;
if (mailItemObjectInbox != null)
{
if (mailItemObjectInbox.Class == Outlook.OlObjectClass.olMail)
{
bool mailItemChanged = false;
if (mailItemObjectInbox.Categories != null)
{
if (mailItemObjectInbox.Categories.Contains(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToLower()) || mailItemObjectInbox.Categories.Contains(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToUpper()))
{
//When mail item has many categories, our need remove from string only our "CRM" category, other categories, include char "," will be applied correctly by Outlook automatically;
//i.e. if mail item has 3 cat.: Green category, CRM, Yellow category -> after removing "CRM" cat. category text was: Green category, , Yellow category, but don't worry, Outlook will apply this string absolutely correctly;
//Note: Mail item categories is case sensitive;
mailItemChanged = true;
if (mailItemObjectInbox.Categories.Contains(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToLower()))
{
mailItemObjectInbox.Categories = mailItemObjectInbox.Categories.Remove(mailItemObjectInbox.Categories.IndexOf(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToLower()), AddInEnvironmentMethods.CRM_CATEGORY_NAME.Length);
}
else if (mailItemObjectInbox.Categories.Contains(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToUpper()))
{
mailItemObjectInbox.Categories = mailItemObjectInbox.Categories.Remove(mailItemObjectInbox.Categories.IndexOf(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToUpper()), AddInEnvironmentMethods.CRM_CATEGORY_NAME.Length);
}
}
}
if (mailItemObjectInbox.UserProperties.Find(AddInEnvironmentMethods.CRM_COLUMN_NAME) != null)
{
mailItemChanged = true;
mailItemObjectInbox.UserProperties.Find(AddInEnvironmentMethods.CRM_COLUMN_NAME).Delete();
}
if (mailItemChanged)
{
mailItemObjectInbox.Save();
}
GlobalEnvironmentMethods.ServiceProcedures.ReleaseComObject(new[] { mailItemObjectInbox });
}
else
{
GlobalEnvironmentMethods.ServiceProcedures.ReleaseComObject(new[] { mailItemObjectInbox });
}
}
}
}
}
else
{
GlobalEnvironmentMethods.ServiceProcedures.ReleaseComObject(new[] { folderItemsInbox });
}
GlobalEnvironmentMethods.globalProcessingRemovingVisualIndicationTaskState = false;
GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken.Dispose();
GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken = null;
NLogMethods.GetInstance().WriteInfoLog("[ADDITIONALVISUALINDICATIONSETTINGSFORM -> RUNREMOVEEXISTINGVISUALINDICATION()]: Processing " + "\"" + "Inbox" + "\"" + " folder completed.");
}
The count of processing mail items in my Inbox folder = 988 items.
But, sometimes this function catches an exception "Out of memory".
I don't understand why this occurs...
Note: Exception occurred on this line of code:
if (mailItemObjectInbox.Categories != null) //Out of memory ex...
What am I doing incorrect? Thanks
I'm developing an addin application for Outlook using Redemption. I'm trying to get a list of addresses to add to the email as addresses and then show the email.
session = new RDOSession();
session.Logon();
var contacts = session.GetDefaultFolder
(rdoDefaultFolders.olFolderContacts);
var mailItem = contacts.Items.Add("New message");
AddinModule.CurrentInstance.Session.MAPIOBJECT = mailItem.Session.MAPIOBJECT;
mailItem.Attachments.Add(file, OlAttachmentType.olByValue, Type.Missing, Type.Missing);
RDOFolder folder = session.GetDefaultFolder(rdoDefaultFolders.olFolderContacts);
foreach (RDOFolder subFolder in folder.Folders)
{
if (subFolder.Name == "CAS_Notifications")
{
foreach (var rdoItem in subFolder.Items)
{
RDOContactItem contactItem = rdoItem as RDOContactItem;
RDODistListItem distList = rdoItem as RDODistListItem;
if (distList != null)
{
foreach (RDOAddressEntry rdoAddressEntry in distList.OneOffMembers)
{
mailItem.Recipients.Add(rdoAddressEntry.SMTPAddress);
}
}
else if (contactItem != null)
{
mailItem.Recipients.Add(contactItem.Email1Address);
}
}
}
}
mailItem.Display();
Threw exception
System.Runtime.InteropServices.COMException: "Error in IMAPIFormMgr.LoadForm: MAPI_E_NOT_FOUND"
How to debug it?
The following line is the problem:
var mailItem = contacts.Items.Add("New message");
Add takes either a message class (e.g. "IPM.Note" or one of the olItemType / rdoItemType enums (such as olMailItem = 0).
The error you get essentially tells you that MAPI cannot find a form for the specified message class ("New message").
I have defined four custom user properties and i can not access to the in order to get their data, custom properties are present in outlook appointment but i can get to them :
My c# code below :
Outlook.ItemProperties itemProp = appointmentItem.ItemProperties;
foreach (Outlook.ItemProperty userprop in itemProp)
{
if (userprop.IsUserProperty)
{
MessageBox.Show(userprop.Name + "\t" + userprop.Value);
}
}
I have used an api(JWebServices) that creates appointment event into Outlook(2007 my version), in my code below i have created some CustomProperties, called in C# Addin Outlook UserPropertie.
I still can get the value of custom users properties that i have defined from the java side
PropertyName myRdvTypePropertyName = new PropertyName("RdvType", StandardPropertySet.PUBLIC_STRINGS);
ExtendedProperty myRdvTypeExtendedProperty = new ExtendedProperty(myRdvTypePropertyName, event.getTyperdv());
appointment.getExtendedProperties().add(myRdvTypeExtendedProperty);
PropertyName myRdvEmplacementPropertyName = new PropertyName("RdvEmplacement", StandardPropertySet.PUBLIC_STRINGS);
ExtendedProperty myRdvEmplacementExtendedProperty = new ExtendedProperty(myRdvEmplacementPropertyName, event.getLieu());
appointment.getExtendedProperties().add(myRdvEmplacementExtendedProperty);
PropertyName myRdvAdressePropertyName = new PropertyName("RdvAdresse", StandardPropertySet.PUBLIC_STRINGS);
ExtendedProperty myRdvAdresseExtendedProperty = new ExtendedProperty(myRdvAdressePropertyName, event.getEvent_location());
appointment.getExtendedProperties().add(myRdvAdresseExtendedProperty);
To be sure the custom user properties are created, i use Outlook SPY
the screenshot below:
the code below in the FormRegionShowing :
if (appointmentItem.UserProperties["RdvType"] != null)
{
this.TypeRdvComboBox.SelectedItem = appointmentItem.UserProperties["RdvType"].Value;
}
if (appointmentItem.UserProperties["RdvEmplacement"] != null)
{
this.emplacementRdvComboBox.SelectedItem = appointmentItem.UserProperties["RdvEmplacement"].Value;
}
if (appointmentItem.UserProperties["RdvAdresse"] != null)
{
this.adresseTextBox.Text = (string)appointmentItem.UserProperties["RdvAdresse"].Value;
}
Thanks for the inspiration, this works:
Outlook.MailItem mItem = (Outlook.MailItem)item;
string udpName = "";
string udpValueString = "";
Debug.Print(" mItem.UserProperties.Count: " + mItem.UserProperties.Count);
for (int i = 1; i <= mItem.UserProperties.Count; i++) {
udpName = mItem.UserProperties[i].Name;
var udpValue = mItem.UserProperties[i].Value;
udpValueString = udpValue.ToString();
Debug.Print(i + ": " + udpName + ": " + udpValueString);
}
Try using the UserProperties property and UserProperties class instead. Here is what MSDN states:
If you use UserProperties.Find to look for a custom property and the call succeeds, it will return a UserProperty object. If it fails, it will return Null. If you use UserProperties.Find to look for a built-in property, specify False for the Custom parameter. If the call succeeds, it will return the property as a UserProperty object. If the call fails, it will return Null. If you specify True for Custom, the call will not find the built-in property and will return Null.
using System.Runtime.InteropServices;
// ...
private void ShowUserProperties(Outlook.MailItem mail)
{
Outlook.UserProperties mailUserProperties = null;
Outlook.UserProperty mailUserProperty = null;
StringBuilder builder = new StringBuilder();
mailUserProperties = mail.UserProperties;
try
{
for (int i = 1; i < = mailUserProperties.Count; i++)
{
mailUserProperty = mailUserProperties[i];
if (mailUserProperty != null)
{
builder.AppendFormat("Name: {0} \tValue: {1} \n\r",
mailUserProperty.Name, mailUserProperty.Value);
Marshal.ReleaseComObject(mailUserProperty);
mailUserProperty = null;
}
}
if (builder.Length > 0)
{
System.Windows.Forms.MessageBox.Show(builder.ToString(),
"The UserProperties collection");
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
finally
{
if (mailUserProperties != null)
Marshal.ReleaseComObject(mailUserProperties);
}
}
Read more about that in the How To: Get Outlook e-mail item’s custom properties – C# and VB.NET samples article.