I have stumbled upon a problem where the Outlook items table sort method does not give desired results - despite the ascending or descending the method GetLast() always returns the same email item. Code as follows:
Application olApp = new Application();
NameSpace olNS = olApp.GetNamespace("MAPI");
MAPIFolder oFolder = olNS.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
Explorer oExp = oFolder.GetExplorer(false);
//olNS.Logon( false, true);
result = new IOActionResult(null);
oFolder.Items.Sort("[ReceivedTime]");
var subject = oFolder.Items.GetLast().Subject;
I have tried specifying following:
oFolder.Items.Sort("[ReceivedTime]", true);
oFolder.Items.Sort("[ReceivedTime]", false);
oFolder.Items.Sort("[ReceivedTime]", OlSortOrder.olAscending);
oFolder.Items.Sort("[ReceivedTime]", OlSortOrder.olDescending);
Which did not seem to work either... Any thoughts appreciated!
On your last line;
var subject = oFolder.Items.GetLast().Subject;
You are being returned a new Items object from Outlook, so your sort was actually performed on an instance that you no longer have a reference to.
Change your code to look like this;
Application olApp = new Application();
NameSpace olNS = olApp.GetNamespace("MAPI");
MAPIFolder oFolder = olNS.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
Items items = oFolder.Items;
items.Sort("[ReceivedTime]");
var subject = items.GetLast().Subject;
A good rule of thumb when developing against Outlook is to always assign intermediary members of objects to their own local variable. This is particular relevant for releasing them later on.
Related
I am very new to EWS and Exchange in general, so not really sure what is the best approach.
Background
I am trying to set configuration information about a room. I was hoping that the EWS API provided me with a Room object that I can add ExtendedProperties on, however, it appears that rooms are just an email address.
I then saw that each room had a CalendarFolder associated with it, so I am now trying to set the room configuration in the CalendarFolder, which is what the original question below refers to.
Original Question
I am trying to do a simple update of a CalendarFolder using:
var folderId = new FolderId(WellKnownFolderName.Calendar, new Mailbox(roomEmail.Address));
var myCalendar = CalendarFolder.Bind(service, folderId, PropertySet.FirstClassProperties);
myCalendar.DisplayName += "Updated";
myCalendar.Update();
However, when I call .Update() I get "The folder save operation failed due to invalid property values."
I believe that the problem might have something to do with myCalendar not having all of the properties that the calendar folder has on the server. So when I update the object it is only sending a partial object which is causing validation errors.
How would I go about updating a CalendarFolder?
After further research
I also stumbled across the following, which does work:
FindFoldersResults root = service.FindFolders(WellKnownFolderName.Calendar, new FolderView(500));
foreach (var folder in root.Folders)
{
folder.DisplayName = "confRoom1";
folder.Update();
}
I'm sure there is a difference between the two approaches, but I don't understand the differences between the folder that I get using the different query methods:
new FolderId(WellKnownFolderName.Calendar, new Mailbox(roomEmail.Address));
var myCalendar = CalendarFolder.Bind(service, folderId, PropertySet.FirstClassProperties);
and
service.FindFolders(WellKnownFolderName.Calendar, new FolderView(500));
Which approach would give me the correct CalendarFolder where I can set the ExtendedProperties for the room?
I'm sure there is a difference between the two approaches, but I don't understand the differences between the folder that I get using the different query methods:
new FolderId(WellKnownFolderName.Calendar, new Mailbox(roomEmail.Address));
var myCalendar = CalendarFolder.Bind(service, folderId, PropertySet.FirstClassProperties);
and
service.FindFolders(WellKnownFolderName.Calendar, new FolderView(500));
The First binds to the default calendar folder in a Mailbox and the second get the subfolders within the Default calendar folder. You can rename the subfolders within the default calendar folder because they are user created. You cannot rename the Default calendar folder in a Mailbox because its a special folder. If you want to set a Extended property (which you can do on a special folder then it easy just define it and set it) eg
ExtendedPropertyDefinition MyCustomProp = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.PublicStrings, "MyCustomProp", MapiPropertyType.String);
CalendarFolder CalendarFolder = CalendarFolder.Bind(service,new FolderId(WellKnownFolderName.Calendar, "user#domain.com"));
CalendarFolder.SetExtendedProperty(MyCustomProp, "My Value");
CalendarFolder.Update();
What you want to get that value you must define a propertySet that tells exchange to return that value when you either Bind or use FindItems (Exchange will not return your property by default) eg
PropertySet MyPropSet = new PropertySet(BasePropertySet.FirstClassProperties);
MyPropSet.Add(MyCustomProp);
CalendarFolder = CalendarFolder.Bind(service, new FolderId(WellKnownFolderName.Calendar, "mailbox#domain.com"),MyPropSet);
Object PropValue = null;
if (CalendarFolder.TryGetProperty(MyCustomProp, out PropValue))
{
Console.WriteLine(PropValue);
}
I am really stuck in this issue and searching didn't yield me a lot. Most answers I found either get Contacts not add them or use LDAP.
The best I've been able to do is display the window where you add people to the distribution list but I am not able to do that part programmatically
Here is the my best attempt:
Microsoft.Office.Interop.Outlook.Application oApp = new Microsoft.Office.Interop.Outlook.Application();
NameSpace oNS = oApp.GetNamespace("MAPI");
//Get Global Address List.
AddressLists oDLs = oNS.AddressLists;
AddressList oGal = oDLs["Global Address List"];
AddressEntries oEntries = oGal.AddressEntries;
AddressEntry oDL = oEntries["MyDistributionList"];
//Get Specific Person
SelectNamesDialog snd = oApp.Session.GetSelectNamesDialog();
snd.NumberOfRecipientSelectors = OlRecipientSelectors.olShowTo;
snd.ToLabel = "D/L";
snd.ShowOnlyInitialAddressList = true;
snd.AllowMultipleSelection = false;
//snd.Display();
AddressEntry addrEntry = oDL;
if (addrEntry.AddressEntryUserType == Microsoft.Office.Interop.Outlook.OlAddressEntryUserType.olExchangeDistributionListAddressEntry)
{
ExchangeDistributionList exchDL = addrEntry.GetExchangeDistributionList();
AddressEntries addrEntries = exchDL.GetExchangeDistributionListMembers();
string name = "John Doe";
string address = "John.Doe#MyCompany.com";
exchDL.GetExchangeDistributionListMembers().Add(OlAddressEntryUserType.olExchangeUserAddressEntry.ToString(), name, address);
exchDL.Update(Missing.Value);
}
Using this i can access the Distribution List but I get "The bookmark is not valid" exception on the
exchDL.GetExchangeDistributionListMembers().Add(OlAddressEntryUserType.olExchangeUserAddressEntry.ToString(), name, address);
line.
I have access on said list.
EDIT:
The thing is that when you use the Outlook API, you use its functionality as a user, not as an admin. More than that, you can only do things that you can do through Outlook UI.
Outlook doesn't allow you to modify distribution lists, so you won't be able to do it using the outlook API.
There are 2 possible ways to do it:
Use the NetApi functions NetGroupAddUser or NetLocalGroupAddMembers, depending on whether the group is a local or global group. This will require importing those functions with P/Invoke and won't work on universal groups.
2. Use LDAP to find the group you need, and add the users you want to it. This can be done using the System.DirectoryServices namespace like this:
using(DirectoryEntry root = new DirectoryEntry("LDAP://<host>/<DC root DN>"))
using(DirectorySearcher searcher = new DirectorySearcher(root))
{
searcher.Filter = "(&(objName=MyDistributionList))";
using(DirectoryEntry group = searcher.findOne())
{
searcher.Filter = "(&(objName=MyUserName))";
using(DirectoryEntry user = searcher.findOne())
{
group.Invoke("Add", user.Path);
}
}
}
These just wrap the old COM ADSI interfaces, that's why I use group.Invoke(). It takes a bit more practice, but is much more powerful than the NetApi functions.
I'm trying to figure out how to get the currently opened document on Lotus Notes through C#, but I cannot. Even though I researched half a day on Google, I couldn't find anything useful.
With my code I get the view I want, the database I want, etc, but I just would like to get the opened document. I tried something like IsUIDocOpen, but none of the full collection contains it as true.
Does someone know if there is any different between an opened document and a non-opened document trough Domino API? My workaround is to get the subject of the email and the size of the email and compare each one and when it matches get the Entry ID and then get the information I need - but that takes too long, especially when the inbox is big.
Any suggestions?
Here is my code:
NotesSession session = new NotesSession();
session.Initialize(sPassword);
notedb = session.GetDatabase(server, filename, false);
if (notedb.IsOpen)
{
mailView = notedb.GetView("$Inbox");
mailDoc = mailView.GetLastDocument();
//mailDoc = mailView.GetDocumentByKey();
try
{
while (mailDoc != null)
{
NotesItem item = mailDoc.GetFirstItem("From");
if (item != null)
{
MessageBox.Show("From = " + item.Text);
}
}
}
}
Solution: should be something like: mailDoc = mailView.GetCurrentDocument(); // But obviously this method does not exist :D
=====================================================================================
Solution code:
Type NotesUIWorkspaceType = Type.GetTypeFromProgID("Notes.NotesUIWorkspace", true);
object workspace = Activator.CreateInstance(NotesUIWorkspaceType);
object uiDoc = NotesUIWorkspaceType.InvokeMember("CurrentDocument", BindingFlags.GetProperty, null, workspace, null);
Type NotesUIDocument = uiDoc.GetType();
object Subject = NotesUIDocument.InvokeMember("FieldGetText", BindingFlags.InvokeMethod, null, uiDoc, new Object[] { "Subject" });
string subject = "test";
NotesUIDocument.InvokeMember("FieldSetText", BindingFlags.InvokeMethod, null, uiDoc, new Object[] { "Subject", subject });
object Body = NotesUIDocument.InvokeMember("FieldGetText", BindingFlags.InvokeMethod, null, uiDoc, new Object[] { "Body" });
What you actually need is the Notes OLE classes.
The C# Interop classes are based on the Notes COM classes. The COM classes only have access to the "back end". I.e., the root object is Lotus.NotesSession, and all the classes work against data stored in .NSF files. They have no access to anything in the Notes UI.
The Notes OLE classes have access to both the "back end", with the root object Notes.NotesSession, and the "front end" with the root object Notes.NotesUIWorkspace. As you can tell by the name of that class, it's the front end classes that give you access to elements of the Notes client UI.
Note the subtle difference: the prefix for the OLE classes is "Notes.", instead of the prefix "Lotus." for the COM classes.
In old-style VB late binding, the OLE classes are instantiated this way:
CreateObject("Notes.NotesUIWorkspace")
I'm not sure how that translates into C#.
Anyhow, once you have the NotesUIWorkspace object, the currently opened document is available with the NotesUIWorkspace.CurrentDocument method.
IF you are using the Interop Classes you need to use NotesUIView.CurrentView.Documents to get what you want... see here.
You'll need to get the NotesUIWorkspace first, then use the CurrentDocument property
NotesUIWorkspace workspace = new NotesUIWorkspace();
NotesUIDocument uidoc = workspace.CurrentDocument();
I am using Exchange Web Services to try to get a list of all Outlook tasks which are not complete.
I have an instance of ExchangeService, and attempt to find all incomplete tasks like this:
SearchFilter searchFilter = new SearchFilter.IsNotEqualTo(TaskSchema.Status, TaskStatus.NotStarted);
FindItemsResults<Item> tasks = service.FindItems(WellKnownFolderName.Tasks, searchFilter, view);
However, on the last line, I get a "ServiceResponseException: The specified value is invalid for property." This seems weird to me because the EWS documentation explicitly states that the Task.Status is supposed to be one of the TaskStatus enumeration values. Creating a SearchFilter which compares against a string value does not cause an exception, but I haven't tried any of the other enumeration options to see whether they give the same behavior.
I am able to do this using ExtendedPropertyDefinition with Exchange 2007.
I am using PidLidTaskComplete Canonical Property.
Full list of named properties available here.
//Create the extended property definition.
ExtendedPropertyDefinition taskCompleteProp = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.Task, 0x0000811C, MapiPropertyType.Boolean);
//Create the search filter.
SearchFilter.IsEqualTo filter = new SearchFilter.IsEqualTo(taskCompleteProp, false);
//Get the tasks.
FindItemsResults<Item> tasks = service.FindItems(WellKnownFolderName.Tasks, filter, new ItemView(50));
I believe you may also achieve that without using any magic numbers:
var view = new ItemView(20);
var query = new SearchFilter.IsNotEqualTo(TaskSchema.IsComplete, true);
var results = exchangeService.FindItems(WellKnownFolderName.Tasks, query, view);
This does work on a certain version of exchange :)
I've been trying to read and set the departmentNumber property for a DirectoryEntry object using C#, but I always have the problem that using ADSI Edit I cannot see that anything in this entry was changed.
Approaches I've tried so far include:
directoryEntry.Properties["departmentNumber"].Value = new object[]{ "SomeContent" };
and
directoryEntry.InvokeSet("departmentNumber", new object[]{ "SomeContent" };
and
directoryEntry.Invoke("PutEx", new object[]{ 2, "departmentNumber", new object[]{"SomeContent"}});
and
directoryEntry.Invoke("Put", new object[]{ "departmentNumber", "SomeContent" });
Update
All of the above followed by directoryEntry.CommitChanges();.
No matter what I try, I do (with some of the examples above) get results in terms of being able to read the value again using corresponding code, but whenever I use the ADSI-Editor and look at the properties of the user, I cannot see that departmentNumber contains any data.
If I set the property to a certain value using ADSI-Editor, I also cannot query that particular value via C#, but what I can do is to use a very simple VB script as below:
Set objUser = GetObject("LDAP://CN=........")
objUser.GetInfo
objUser.PutEx 2, "departmentNumber", Array("SomeContent")
This does change the value of the property that I can see in the ADSI-Editor, but again, using C# I cannot read it.
What is the problem here, and why does it look as if there are two different "departmentNumber" properties? Despite trying to find someone with the same problem I didn't come up with any answers or even pointers in the right direction, so any help is greatly appreciated. Please also ask in case you need more information.
You just forgot to commit changes. It's necessary usin ADSI. Here is an example of a user création and modification :
static void Main(string[] args)
{
/* Connection to Active Directory
*/
DirectoryEntry deBase = new DirectoryEntry("LDAP://192.168.225.100:389/OU=SousMonou,OU=MonOu,DC=dom,DC=fr", "jpb", "pwd");
/* User creation
*/
DirectoryEntry auser = deBase.Children.Add("cn=a User", "user");
auser.CommitChanges();
auser.Properties["samaccountname"].Value = "AUser";
auser.Properties["givenName"].Value = "A";
auser.Properties["sn"].Value = "User";
auser.Properties["displayName"].Value = "AUser";
auser.Properties["userPrincipalName"].Value = "AUser#dom.fr";
auser.Properties["pwdLastSet"].Value = 0;
auser.Properties["userAccountControl"].Value = 544;
auser.CommitChanges();
/* Retreiving the user
*/
DirectorySearcher dsLookForDomain = new DirectorySearcher(deBase);
dsLookForDomain.Filter = "(&(cn=a User))";
dsLookForDomain.SearchScope = SearchScope.Subtree;
SearchResult srUser = dsLookForDomain.FindOne();
if (srUser != null)
{
DirectoryEntry deUser = srUser.GetDirectoryEntry();
deUser.Properties["departmentNumber"].Value = "Test Department";
deUser.CommitChanges();
}
}