I want to take mails from inbox and sentbox folders, compare their subjects and if they match, put it all into a new custom folder.
Here's the code so far:
Outlook.MAPIFolder inBox = (Outlook.MAPIFolder)
this.Application.ActiveExplorer().Session.GetDefaultFolder
(Outlook.OlDefaultFolders.olFolderInbox);
// I also have made this for the sentBox folder
string userName = (string)this.Application.ActiveExplorer()
.Session.CurrentUser.Name;
Outlook.MAPIFolder customFolder = null;
customFolder = (Outlook.MAPIFolder)inBox.Folders.Add(userName,
Outlook.OlDefaultFolders.olFolderInbox);
inBox.Folders[userName].Display();
// This is the custom folder in which i wish to place the matching mails
for (int i = 1; i <= sentboxFolder.Items.Count; i++)
{
outboxItem = sentboxFolder.Items[i];
for (int a = 1; a <= inBox.Items.Count; a++)
{
inboxItem = inBox.Items[a];
if ("RE: " + outboxItem.Subject == inboxItem.Subject)
{
customFolder.Items.Add(inboxItem);
// Here I loop through the inbox and outbox folders and if the subjects match I want to add the inbox part to the custom folder.
I have 3 questions:
1. Is there a way to put both matching mails into one folder ?
2. I know there should be a smarter way to this aside from comparing subjects, Can anyone help how to use conversation ID here?
3. I get an exception at the last line, that it cannot add inbox item into custom folder because it's not an actual object instance. Where should I instantiate mailitem to fix this ?
Thanks in advance.
Firstly, do not use multiple dot notation, especially in a loop - cache the Items collection before entering the loop.
Secondly, do not just loop through all items in a folder looking for a match - use Items.Find.
That being said, you can use MailItem.Move(OtherFolder) . If you want to preserve the original item, use MailItem.Copy (returns new item), then move it to the target folder.
Related
I have the following code to search the global address book by a certain string:
"CONF"
var esb = new ExchangeServiceBinding();
esb.Url = #"https://myurl.com/EWS/Exchange.asmx";
esb.Credentials = new NetworkCredential(_user,_pwd, _domain);
var rnType = new ResolveNamesType {ReturnFullContactData = true, UnresolvedEntry = "CONF"};
ResolveNamesResponseType response = esb.ResolveNames(rnType);
ArrayOfResponseMessagesType responses = resolveNamesResponse.ResponseMessages;
var responseMessage = responses.Items[0] as ResolveNamesResponseMessageType;
ResolutionType[] resolutions = responseMessage.ResolutionSet.Resolution;
the issue is that it seems to be doing a "starts with" search so I have a name called:
"CONF-123" it will show up but if i have a name "JOE-CONF" then it will not.
How can I do a partial string search on this line
var rnType = new ResolveNamesType {ReturnFullContactData = true, UnresolvedEntry = "CONF-"};
i was hoping there was something like:
var rnType = new ResolveNamesType {ReturnFullContactData = true, UnresolvedEntry = "%CONF-%"};
but that doesn't seem to work.
EDIT: Jan 4,2016 - Added sample code for searching AD.
What won't work
Searching the GAL via ResolveNames always uses prefix-string match for Ambiguous Name Resolution (ARN). Although EWS documentation does not say this explicitly, the Exchange ActiveSync documentation does. EWS and Exchange ActiveSync are just protocols; they both rely on ARN underneath, so you are stuck with prefix match, whether you use ActiveSync protocol or EWS.
Here is the relevant quote from Exchange ActiveSync documentation (https://msdn.microsoft.com/en-us/library/ee159743%28v=exchg.80%29.aspx)
The text query string that is provided to the Search command is used
in a prefix-string match
.
What will work
The best thing to do depends on your use case, but here are some ideas:
Search Active Directory in your client program (the program that contains the code you showed in your question)
Set up your own service to search the GAL. Your client program would connect both to Exchange and to your service. Or your service could proxy EWS, so that the client program needs to connect only to your service.
How would you service get the GAL data? One way would be to use EWS ResolveNames repeatedly, to get the GAL data, 100 entries at a time and cache this data in your service. First, retrieve all the "a"s, then all the "b"s, etc. Of course, there can be more than 100 "a"s in the GAL, so just getting all the "a"s could take multiple searches - you would construct your next search string, based on the last entry returned from each search. This can be slow and painful. You would probably want to cache this data in a database and refresh it periodically.
You can also get to GAL through MAPI. You can use MAPI directly (https://msdn.microsoft.com/en-us/library/cc765775%28v=office.12%29.aspx) or through a helper library like Redemption (http://www.dimastr.com/redemption/home.htm). Whether you use MAPI directly or through Redemption, you will need to install Outlook (or Exchange) on the computer where your code is running. Because of this restriction, it may be best to not use MAPI in your client program, but to stick it in a service running on some server and have your client program connect to that service.
AD Code Sample
Another answer provided sample code to search Active Directory. I am adding a code sample that may be better suited for generic use by people who may find this question through search. Compared to the other sample, the code below has the following improvements:
If the search string contains any special characters (like parenthesis), they are escaped, so that the constructed filter string is valid.
Searching by just samaccountname many not be sufficient. If "David Smith" has account name "dsmith", searching for "David" by samaccountname would not find him. My sample shows how to search by more fields and gives some of the fields one may want to search.
Starting at a root like "GC:" is more robust than trying to construct an LDAP entry from Domain.GetComputerDomain().
All IDisposables must be disposed of (usually by using them in a using construct).
// Search Active Directory users.
public static IEnumerable<DirectoryEntry> SearchADUsers(string search) {
// Escape special characters in the search string.
string escapedSearch = search.Replace("*", "\\2a").Replace("(", "\\28")
.Replace(")", "\\29").Replace("/", "\\2f").Replace("\\", "\\5c");
// Find entries where search string appears in ANY of the following fields
// (you can add or remove fields to suit your needs).
// The '|' characters near the start of the expression means "any".
string searchPropertiesExpression = string.Format(
"(|(sn=*{0}*)(givenName=*{0}*)(cn=*{0}*)(dn=*{0}*)(samaccountname=*{0}*))",
escapedSearch);
// Only want users
string filter = "(&(objectCategory=Person)(" + searchPropertiesExpression + "))";
using (DirectoryEntry gc = new DirectoryEntry("GC:")) {
foreach (DirectoryEntry root in gc.Children) {
try {
using (DirectorySearcher s = new DirectorySearcher(root, filter)) {
s.ReferralChasing = ReferralChasingOption.All;
SearchResultCollection results = s.FindAll();
foreach (SearchResult result in results) {
using (DirectoryEntry de = result.GetDirectoryEntry()) {
yield return de;
}
}
}
} finally {
root.Dispose();
}
}
}
}
Though wildcard search isn't possible in EWS, it is possible in AD search. AD Queries support wildcards. i.e., *CONF* can be searched in AD, which will return all results which contain "CONF". Based on the results, query EWS for an corresponding Exchange Object. You need to find a parameter with which you can find the corresponding EWS entry. I guess email address (username) should be sufficient to find the corresponding exchange object.
AD Search code snippet...
private SearchResultCollection SearchByName(string username, string password, string searchKeyword)
{
DirectorySearcher ds = new DirectorySearcher(new DirectoryEntry("LDAP://" + Domain.GetComputerDomain().ToString().ToLower(), username, password));
ds.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=*" + searchKeyword + "*))";
ds.SearchScope = SearchScope.Subtree;
ds.ServerTimeLimit = TimeSpan.FromSeconds(90);
return ds.FindAll();
}
AD Query Example here.
An ambiguous search on an indexed text field can only be done with prefix (or suffix...). That's why Exchange probably implements the query as LIKE 'CONF%'.
I've looked over the documentation and there isn't any way you can bypass it - full table scan (which would have to be the case for %CONF%) does not seem to make sense.
I have also been trying to search the GAL from PHP using php-ews. On a web page the user starts to enter the name to be searched, once 2 chars have been entered a call to read_contacts.php is made passing the 2 chars entered. read_contacts.php uses EWS ResolveNames request to retrieve a limited list of contacts. The code then filters on some criteria and also checks that the displayName starts with the chars entered. This then returns the filtered list:
<?php
$staffName = $_GET['q'];
require_once './php-ews/EWSType.php';
require_once './php-ews/ExchangeWebServices.php';
require_once 'php-ews/EWSType/RestrictionType.php';
require_once 'php-ews/EWSType/ContainsExpressionType.php';
require_once 'php-ews/EWSType/PathToUnindexedFieldType.php';
require_once 'php-ews/EWSType/ConstantValueType.php';
require_once 'php-ews/EWSType/ContainmentModeType.php';
require_once 'php-ews/EWSType/ResolveNamesType.php';
require_once 'php-ews/EWSType/ResolveNamesSearchScopeType.php';
require_once 'php-ews/NTLMSoapClient.php';
require_once 'php-ews/NTLMSoapClient/Exchange.php';
$host = '[exchange server]';
$user = '[exchange user]';
$password = '[exchange password]';
$ews = new ExchangeWebServices($host, $user, $password);
$request = new EWSType_ResolveNamesType();
$request->ReturnFullContactData = true;
$request->UnresolvedEntry = $staffName;
$displayName = '';
$i = 0;
$staff_members = false;
$response = $ews->ResolveNames($request);
if ($response->ResponseMessages->ResolveNamesResponseMessage->ResponseClass == 'Error' && $response->ResponseMessages->ResolveNamesResponseMessage->MessageText == 'No results were found.') {
}
else {
$numNamesFound = $response->ResponseMessages->ResolveNamesResponseMessage->ResolutionSet->TotalItemsInView;
$i2=0;
for ($i=0;$i<$numNamesFound;$i++) {
if ($numNamesFound == 1) {
$displayName = $response->ResponseMessages->ResolveNamesResponseMessage->ResolutionSet->Resolution->Contact->DisplayName;
}
else {
$displayName = $response->ResponseMessages->ResolveNamesResponseMessage->ResolutionSet->Resolution[$i]->Contact->DisplayName;
}
echo "DisplayName: " . $displayName . "\n";
if (stripos($displayName, 'External') == true) {
}
else {
$searchLen = strlen($staffName);
if (strcasecmp(substr($displayName, 0, $searchLen), $staffName) == 0) {
if ($numNamesFound == 1) {
$emailAddress = $response->ResponseMessages->ResolveNamesResponseMessage->ResolutionSet->Resolution->Mailbox->EmailAddress;
}
else {
$emailAddress = $response->ResponseMessages->ResolveNamesResponseMessage->ResolutionSet->Resolution[$i]->Mailbox->EmailAddress;
}
$staff_members[$i2] = array( 'name' => $displayName,'email' => $emailAddress );
$i2++;
}
}
}
$staffJson = json_encode($staff_members);
echo $staffJson;
}
Most of the time this seems to work, ie: 'mi', 'jo' returns Mike, Michael, or Joe, John, except when I sent 'Si' or 'si', for Simon, then the ResolveNames call returns the first 100 entries from the GAL.
For the time being I have increased the minimum number of characters to be entered to 3, ie: 'sim' and this works. The problem will be when we get staff with only 2 character first names.
I just thought I would share the code to see if it helps and to see if anyone knows why my 'si' does not work properly.
I am accessing Exchange 2010
I can add a contact to a address book but for some reason I can't remove it. The code I'm executing is as follows.
String abName = "Name ofthe targetted address book";
Outlook.Folder addressBook;
if (targetFolder.Folders.OfType<Outlook.Folder>().Any(element
=> element.Name == abName))
addressBook = targetFolder.Folders[abName] as Outlook.Folder;
else
addressBook = targetFolder.Folders.Add(
abName, Outlook.OlDefaultFolders.olFolderContacts) as Outlook.Folder;
addressBook.ShowAsOutlookAB = true;
for (int i = addressBook.Items.Count - 1; i >= 0; i--)
if (!stringList.Any(element
=> element == addressBook.Items.OfType<Outlook.ContactItem>()
.ToList()[i].Email1Address))
addressBook.Items.OfType<Outlook.ContactItem>().ToList().RemoveAt(i);
The fetching of the address book works and the matching for strings too. I get into the RemoveAt line for the exactly correct contacts. There's no error or other message when I execute the removal. Still, the contact list remain unaffected.
Why?
What can I do to actually remove the contacts?
I suspect that I may be working on a copy of the actual list containing the contacts. The problem is that if I don't create a List, I'm not sure how to alter the list of contacts.
So, the most helpful answer would shed some light on how to alter addressBook (or perhaps addressBook.Items) given certain condition. E.g., say that we'd like to remove all the contants the name of whom starts with the letter "Q".
At this moment I can only think of a super ugly work-around and it's so rectum-ugly that I don't even mention it here. Really ugly...
You ae not removing an Outlook contact. You are removing an OUtlook object from your own List object.
You need to call ContactItem.Delete.
As a side note, do not use multiple dot notation when working with COM objects, especially in a loop - you will receive a brand new COM object for each dot.
Here is a solution
private void ClearContact(Outlook.Application outlookApplication)
{
Outlook.MAPIFolder contactFolder = outlookApplication.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);
int total = contactFolder.Items.Count;
while (total > 0)
{
// first index number is 1 not 0
var contact = (Outlook.ContactItem)contactFolder.Items[1];
contact.Delete();
total = contactFolder.Items.Count;
}
}
I use netoffice outlook api
http://netoffice.codeplex.com/wikipage?title=Outlook_Example05
And use while loop to delete all contact
Im developing a tool that needs to access to the names.nsf database inside IBM Lotus Notes, and, using the lotus contact ID (Employee ID) (this id will be provided by the user), retrieve the full information of the person (Name, Position, Phone #....)
I found an example at Codeproject.com (http://www.codeproject.com/Articles/18517/Lotus-Notes-Integration-with-Microsoft-NET-Platfor), however it takes around 10 minutes to get the information the way the example does it (the database has more or less 5000 entries), so I'm searching for a faster way of doing it (if I actually use Lotus notes for this it takes about a second!).
Is there a way to accomplish this task without having the user waiting for minutes?
Thought that maybe you can help me out with this one.
The sample you are using goes through the view using
NotesViewEntry viewEntry = notesViewCollection.GetNthEntry( rowCount );
This is (one of) the worst methods to use as it goes for every iteration from the top of the view and iterates through all docs until it reached the nth document.
There are two options:
1) Optimize this code by using
NotesViewEntry viewEntry = notesViewCollection.GetFirstEntry();
and at the end
viewEntry = notesViewCollection.GetNextEntry(viewEntry);
2) (in my humble opinion the better way): Change the code:
- you need a view with the first column sorted by your key => contact ID (Employee ID)
- You can the access the ViewEntry by a code like
LotusNotesView.GetEntryByKey( EmployeeID, true);
If you are lucky the names.nsf is full text indexed. If it's not you could try to ask if it could be full text indexed. When it's indexed you can get the person document quicly like this:
LotusNotesView.FTSearch("[EmployeeID]=1234567", 1);
NotesDocument docPerson = LotusNotesView.GetFirstDocument();
The use of GetNthEntry certainly causes some performance issues. I've taken the relevant code from that site and rewrote it to use the GetFirst/GetNext pattern, which is recommended for all view processing in Lotus Notes.
Note this hasn't been tested, of course. The point is to get the first entry in your collection, check that it is an object, and then process it. At the end of the loop, get the next entry and repeat until you hit null.
NotesViewEntryCollection notesViewCollection = LotusNotesView.AllEntries;
NotesViewEntry viewEntry = notesViewCollection.GetFirstEntry();
while (viewEntry != null)
{
//Get the first document of particular entry.
NotesDocument document = viewEntry.Document;
object documentItems = document.Items;
Array itemArray1 = (System.Array)documentItems;
for( int itemCount=0 ; itemCount< itemArray1.Length; itemCount++ )
{
NotesItem notesItem =
(Domino.NotesItem)itemArray1.GetValue( itemCount );
//compare field value with specific value entered by user
if( notesItem.Text !=null )
{
if( (notesItem.Text.ToUpper()).StartsWith( fieldValue ))
{
Contact contact = new Contact();
for( int icount=0 ; icount< itemArray1.Length; icount++ )
{
NotesItem searchedNotesItem =
(Domino.NotesItem)itemArray1.GetValue( icount );
string FieldName = searchedNotesItem.Name.ToString();
//For FirstName
if( searchedNotesItem.Name == "FirstName" )
contact.FirstName= searchedNotesItem.Text;
//For LastName
if( searchedNotesItem.Name == "LastName" )
contact.LastName = searchedNotesItem.Text;
//For Office Phone Number
if( searchedNotesItem.Name == "OfficePhoneNumber" )
contact.OfficePhoneNumber = searchedNotesItem.Text;
if( searchedNotesItem.Name == "InternetAddress" )
contact.EmailId = searchedNotesItem.Text;
}//end for
contactsList.Add( contact );
break;
}//End if
}
}
//Get the nth entry of the selected view according to the iteration.
NotesViewEntry viewEntry = notesViewCollection.GetNextEntry(viewEntry);
}
Why are you asking the user to provide his Employee ID? You should ask him to provide his Notes username (either FullName or ShortName), or his email address. Any of those can be looked up very quickly in the $Users view in names.nsf, giving you fast access to the document containing all the data that you need.
Note: I'm aware that some companies actually enter their Employee ID into the ShortName field in names.nsf. If that's the case for your organization, then what you should be doing is opening a NotesView object using the NotesDatabase.getView() method, and then use the NotesView.getDocumentByKey() method to get the document for the user. E.g., something like this:
NotesView usersView = namesDb.getView("$Users");
NotesDocument userDoc = usersView.getDocumentByKey(employeeId);
Then just read the data that you want, using userDoc.getItemValue() for each information field that you are interested in. You should only do a loop through the entire userdoc.Items array if you are really trying to capture everything, including a bunch of internal-use values.
I'm writing an application that needs to know if anything on the users active directory object (eg group membership) has altered since the last time the app was ran.
I was looking at the whenChanged attribute but that appears only to change if the user changes their password. I just need something i can hold in my config file, and check that value in active directory the next time the app is ran.
Anyone know of anything i can reliably use?
Cheers
Luke
Given that you are primarily concerned with groups, I think you'll have to create a hash - modifying group membership won't affect the user object.
Here's a quick example I've knocked up.
public static string HashGroups(string user)
{
DirectoryEntry directoryEntry = default(DirectoryEntry);
DirectorySearcher dirSearcher = default(DirectorySearcher);
List<string> result = new List<string>();
directoryEntry = new DirectoryEntry("LDAP://<YOUR_DOMAIN>");
directoryEntry.RefreshCache();
// Get search object, specify filter and scope,
// perform search.
dirSearcher = new DirectorySearcher(directoryEntry);
dirSearcher.PropertiesToLoad.Add("memberOf");
dirSearcher.Filter = "(&(sAMAccountName=" + user + "))";
SearchResult sr = dirSearcher.FindOne();
// Enumerate groups
foreach (string group in sr.Properties["memberOf"])
{
result.Add(group);
}
// OrderBy is important! Otherwise, your hash might fail because
// the groups come back in different order.
MD5 md5 = MD5.Create();
Byte[] inputBytes = Encoding.ASCII.GetBytes(result.OrderBy(s1 => s1).SelectMany(s2 => s2).ToArray());
byte[] hash = md5.ComputeHash(inputBytes);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
PREVIOUS (rubbish) ANSWER
According to the docs, the Modify-Time-Stamp attribute is "A computed attribute representing the date when this object was last changed." which sounds like what you want. It's available in all versions back to 2000.
EDIT It looks like this is computed from whenChanged, so if that is not working for you then this may not.
You could always check the Directory's properties for last write or modification time and maybe keep some XML or some data that keeps track of last write times of files.
How do I get a list of revisions from sharpsvn
If you look at the metadata for SvnLogEventArgs (which is returned as a collection from GetLog) it derives from SvnLoggingEventArgs, which has properties of Author, Revision, Time and LogMessage (amongst others)
Each SvnLogEventArgs item has a collection of ChangedPaths, which have properties for SvnChangeAction, and Path.
You can get a list of all the log info by this method:
var client = new SvnClient();
System.Collections.ObjectModel.Collection<SvnLogEventArgs> logEventArgs;
client.GetLog("targetPath", out logEventArgs);
Iterating through all the logEventArgs will give you some useful information - LogMessage, Author, etc.
I don't know what you're doing, but I am checking the latest version of the working copy using SvnWorkingCopyClient:
var workingCopyClient = new SvnWorkingCopyClient();
SvnWorkingCopyVersion version;
workingCopyClient.GetVersion(workingFolder, out version);
The latest version of the local working repository is then available through
long localRev = version.End;
For a remote repository, use
var client = new SvnClient();
SvnInfoEventArgs info;
client.GetInfo(targetUri, out info);
long remoteRev = info.Revision;
instead.
This is similar to using the svnversion tool from the command line. Hope this helps.
Guessing at what your question really is about the answer is most likely SvnClient.Log(), to get you a list of changes of a path.
Another anwer would be:
for (int i = 1; i < 101; i++)
yield return i;
to get you the first 100 revisisions of a repository ;-)
See Using SharpSvn to retrieve log entries within a date range for some examples on how to use SvnClient.Log()
This is the code form which you can get all the revisions no in list revisions numbers. UriSCpath will be uri for svn path.
SvnTarget tr = SvnTarget.FromUri(UriSCPath);
Collection<SvnLogEventArgs> logEventArgs;
List<Int64> revisionNumbers = new List<Int64>();
SvnLogArgs logArgs = new SvnLogArgs();
DPISVN_Clnt.GetLog(UriSCPath, logArgs, out logEventArgs);
Int64 latestReision = logEventArgs[0].Revision;
foreach (var item in logEventArgs)
{
revisionNumbers.Add(item.Revision);
}