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.
Related
I am busy with creating a search function into my GUI application who is running on my Windows Server to add, remove, update and search users.
I am almost done building the application, but I can not solve the problem of getting details from an other property which is not given in UserPrincipal like the 'Address' property.
How can I get into that property?
I have tried many coding style to get into the given property 'Address', but it still does not work.
Here is the code:
private void ListOfUsers(String ou)
{
List<string> users = new List<string>();
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "EMRE", "OU=" + ou + ",dc=emre,dc=han");
UserPrincipal qbeUser = new UserPrincipal(ctx);
PrincipalSearcher search = new PrincipalSearcher(qbeUser);
foreach (UserPrincipal user in search.FindAll())
{
users.Add(user.UserPrincipalName);
users.Add("********");
users.Add(user.GivenName);
users.Add(user.Surname);
if (user.GetUnderlyingObjectType() == typeof(DirectoryEntry))
{
using (var entry = (DirectoryEntry)user.GetUnderlyingObject())
{
if (entry.Properties["Address"] != null)
users.Add(entry.Properties["Street"].Value.ToString());
}
}
users.Add(user.VoiceTelephoneNumber);
users.Add(user.EmailAddress);
users.Add(ou);
}
string[] row = users.ToArray();
var listViewItem = new ListViewItem(row);
lstStudents.Items.Add(listViewItem);
}
I always get a null returned even the property is not null
The attribute you want is actually called streetAddress. You can also use Properties.Contains to check if the value exists (although the effect is really the same as checking for null, just easier to read).
if (entry.Properties.Contains("streetAddress"))
users.Add(entry.Properties["streetAddress"].Value.ToString());
Personally, I like using DirectoryEntry/DirectorySearcher directly in general, rather than UserPrincipal/PrincipalSearcher because it gives me more control of what it's doing, which can translate into better performance. I wrote a bit about that here: Active Directory: Better performance
We are having an issue with searching a custom record through SuiteTalk. Below is a sample of what we are calling. The issue we are having is in trying to set up the search using the internalId of the record. The issue here lies in in our initial development account the internal id of this custom record is 482 but when we deployed it through the our bundle the record was assigned with the internal Id of 314. It would stand to reason that this internal id is not static in a site per site install so we wondered what property to set up to reference the custom record. When we made the record we assigned its “scriptId’ to be 'customrecord_myCustomRecord' but through suitetalk we do not have a “scriptId”. What is the best way for us to allow for this code to work in all environments and not a specific one? And if so, could you give an example of how it might be used.
Code (C#) that we are attempting to make the call from. We are using the 2013.2 endpoints at this time.
private SearchResult NetSuite_getPackageContentsCustomRecord(string sParentRef)
{
List<object> PackageSearchResults = new List<object>();
CustomRecord custRec = new CustomRecord();
CustomRecordSearch customRecordSearch = new CustomRecordSearch();
SearchMultiSelectCustomField searchFilter1 = new SearchMultiSelectCustomField();
searchFilter1.internalId = "customrecord_myCustomRecord_sublist";
searchFilter1.#operator = SearchMultiSelectFieldOperator.anyOf;
searchFilter1.operatorSpecified = true;
ListOrRecordRef lRecordRef = new ListOrRecordRef();
lRecordRef.internalId = sParentRef;
searchFilter1.searchValue = new ListOrRecordRef[] { lRecordRef };
CustomRecordSearchBasic customRecordBasic = new CustomRecordSearchBasic();
customRecordBasic.recType = new RecordRef();
customRecordBasic.recType.internalId = "314"; // "482"; //THIS LINE IS GIVING US THE TROUBLE
//customRecordBasic.recType.name = "customrecord_myCustomRecord";
customRecordBasic.customFieldList = new SearchCustomField[] { searchFilter1 };
customRecordSearch.basic = customRecordBasic;
// Search for the customer entity
SearchResult results = _service.search(customRecordSearch);
return results;
}
I searched all over for a solution to avoid hardcoding internalId's. Even NetSuite support failed to give me a solution. Finally I stumbled upon a solution in NetSuite's knowledgebase, getCustomizationId.
This returns the internalId, scriptId and name for all customRecord's (or customRecordType's in NetSuite terms! Which is what made it hard to find.)
public string GetCustomizationId(string scriptId)
{
// Perform getCustomizationId on custom record type
CustomizationType ct = new CustomizationType();
ct.getCustomizationTypeSpecified = true;
ct.getCustomizationType = GetCustomizationType.customRecordType;
// Retrieve active custom record type IDs. The includeInactives param is set to false.
GetCustomizationIdResult getCustIdResult = _service.getCustomizationId(ct, false);
foreach (var customizationRef in getCustIdResult.customizationRefList)
{
if (customizationRef.scriptId == scriptId) return customizationRef.internalId;
}
return null;
}
you can make the internalid as an external property so that you can change it according to environment.
The internalId will be changed only when you install first time into an environment. when you deploy it into that environment, the internalid will not change with the future deployments unless you choose Add/Rename option during deployment.
We have an AD with users in "mydomain.com" and users in "child.mydomain.com". When We try to list them, we can only find the "mydomain.com"'s users and groups, but we also need those from the child domain. How can I achieve this using C# ? Please take a look to my sample code :
context = new PrincipalContext(ContextType.Domain);
//...
var filter = new GroupPrincipal(context);
filter.IsSecurityGroup = true;
using(var searcher = new PrincipalSearcher(filter)
using(var results = searcher.FindAll())
{
foreach(GroupPrincipal group in results)
{
string path = "LDAP://rootDSE";
DirectoryEntry searchRoot = new DirectoryEntry(path);
string configNC = searchRoot.Properties["configurationNamingContext"].Value.ToString();
DirectoryEntry configSearchRoot = new DirectoryEntry("LDAP://" + configNC);
DirectorySearcher configSearch = new DirectorySearcher(configSearchRoot);
configSearch.Filter("(NETBIOSName=*)");
configSearch.PropertiesToLoad.Add("dnsroot");
configSearch.PropertiesToLoad.Add("ncname");
configSearch.PropertiesToLoad.Add("NETBIOSName");
SearchResultCollection forestPartitionList = configSearch.FindAll();
List<Tuple<string,string>> netbiosNameList = new List<Tuple<string,string>>(forestPartitionList.Count);
foreach(SearchResult domainPartition in forestPartitionList)
{
string ncname = domainPartition.Properties["ncname"][0].ToString();
string netBIOSName = domainPartition.Properties["NETBIOSName"][0].ToString();
netbiosNameList.Add(Tuple.Create(ncname, netBIOSName));
}
//...
//Find group members
using (var principal = GroupPrincipal.FindByIdentity(context, IdentityType.DistinguishedName, group.DistinguishedName))
using (var members = principal.GetMembers(true))
using (var enumerator = members.GetEnumerator())
{
//...
}
}
}
The code is not exactly written this way, I just want to show you the main calls that are made to query the AD. We can list the parent domain groups and users but not the child domain ones. If I change the initialization of my "context" variable passing the child domain IP and user/password, I can list the groups and users in it. But we want to be able to do so while being in the parent domain.
I hope you can help me. Thanks a lot!
You can query the global catalog.
It contains a read-only, searchable, partial representation of every object in every domain in a multidomain Active Directory forest.
The GC operates on port 3268 ( standard ldap ) and 3269 ( SSL ldap ). Simply connect to any of your domain controllers on one of the above two ports and your search will be automatically directed to the GC server.
To perform any modifications, though, you will have to send such request to a domain controller for that particular domain the object belongs to.
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.
We're using EWS to generate some analytics on some of our mailboxes.
Part of this is getting a count/name/start/end of conversations. A conversation being analogous to the way Outlook 2010 shows them when grouping by conversation.
I was hoping to be able to use the ConversationId to group items, but that seems to be an Exchange 2010-only feature.
I can group by subject within a folder to get a simple idea of threads... however this does not handle split conversations, as Outlook 2010 does - specifically, it doesn't handle bringing in the replies that are in the sent items (these are important to us - we can't get good metrics without also looking at replies).
My current code for getting thread info looks like this:
private IEnumerable<EmailThread> GetThreads(Folder folder)
{
var view = new ItemView(int.MaxValue) {PropertySet = new PropertySet(BasePropertySet.IdOnly)};
// view.PropertySet.Add(ItemSchema.ConversationId); - Can't use this as we're stuck on Exchange 2007 !!!
view.PropertySet.Add(ItemSchema.Subject);
view.PropertySet.Add(ItemSchema.DateTimeReceived);
var grouping = new Grouping(ItemSchema.Subject, SortDirection.Descending, ItemSchema.DateTimeReceived, AggregateType.Maximum);
var groupResults = folder.FindItems(view, grouping);
return groupResults.Select(x => new EmailThread
{
Name = x.Items.First().Subject,
Items = x.Items.Count,
StartDate = x.Items.Last().DateTimeReceived, // Assume last in thread is first email
EndDate = x.Items.First().DateTimeReceived // Assume first in thread is most recent
});
}
I am hoping someone knows of a neat way to efficiently get information on replies that constitute part of a conversation?
You can fetch the ConversationId and the ConversationIndex via extended properties:
private static readonly ExtendedPropertyDefinition ConversationIdProperty = new ExtendedPropertyDefinition(0x3013, MapiPropertyType.Binary);
private static readonly ExtendedPropertyDefinition ConversationIndexProperty = new ExtendedPropertyDefinition(0x0071, MapiPropertyType.Binary);
var items = service.FindItems(WellKnownFolderName.Inbox, new ItemView(512) { PropertySet = new PropertySet(BasePropertySet.FirstClassProperties,
ConversationIdProperty, ConversationIndexProperty)});
Both are binary properties. Their content is described in great detail here:
[MS-OXOMSG]: E-Mail Object Protocol Specification, section 2.2.1.2 and 2.2.1.3.
The properties themselves are defined in [MS-OXPROPS]: Exchange Server Protocols Master Property List.