Tuesday, January 24, 2017

AX 2009 users search in Active Directory with Directory Services library

Requesting Active Directory from Dynamics AX is usually done by xAxaptaUserManager class, but if you should access many active directory properties, this class could limit you in your code.

In order to access all active directory properties, you could use System.DirectoryServices framework (CLR) like Microsoft in its user import wizard (SysUserADUserImportWizard form).

In this form, you could observe this kind of managed code used in element.searchADUser() method (AX 2009 for instance).

I created a little class to retrieve some user with a domain and sid, and another one to retrieve userInfo record from an UPN (UserPrincipalName) parsing all domain of AD (because active directory could be composed with many domain names).

Here is the sample to retrieve a UPN from a SID and domain :

 static EMail getUPNFromUserInfo(SID  _sid,NetworkDomain _domain)  
 {  
   System.DirectoryServices.DirectorySearcher     directorySearcher;  
   System.DirectoryServices.SearchResult        searchResult;  
   System.DirectoryServices.DirectoryEntry       entry;  
   System.DirectoryServices.PropertyCollection     PropertyCollection;  
   System.DirectoryServices.PropertyValueCollection  PropertyValueCollection;  
   System.DirectoryServices.SearchScope        searchScope;  
   str                         selectedDomainName = _domain;  
   str                         prefix = 'LDAP://';  
   int                         totalNumber;  
   int                         i;  
   str                         criteria;  
   str                         adUPN = '';  
   ;  
   if(_sid && _domain)  
   {  
     try  
     {  
       searchScope = CLRInterop::parseClrEnum('System.DirectoryServices.SearchScope', 'Subtree');  
       entry = new System.DirectoryServices.DirectoryEntry(prefix+selectedDomainName);  
       directorySearcher = new System.DirectoryServices.DirectorySearcher(entry);  
       directorySearcher.set_PageSize(65535);  
       directorySearcher.set_CacheResults(false);  
       directorySearcher.set_SearchScope(searchScope);  
       criteria += strfmt('(objectSid=%1)', _sid) ;  
       directorySearcher.set_Filter(strfmt('(&(objectClass=user)(objectCategory=person)%1(userAccountControl:1.2.840.113556.1.4.803:=512))', criteria));  
       searchResult = directorySearcher.FindOne();  
       if(searchResult)  
       {  
           entry = searchResult.GetDirectoryEntry();  
           if (entry)  
           {  
             PropertyCollection = entry.get_Properties();  
           }  
           if (!PropertyCollection)  
           {  
             continue;  
           }  
           PropertyValueCollection = PropertyCollection.get_Item('userPrincipalName');  
           if (PropertyValueCollection)  
           {  
             if (PropertyValueCollection.get_Value())  
             {  
               adUPN = PropertyValueCollection.get_Value();  
               //newAlias = PropertyValueCollection.get_Value();  
             }  
         }  
       }  
     }  
     catch (Exception::CLRError)  
     {  
       error("@SYS117735");  
       return '';  
     }  
   }  
   return adUPN;  
 }  

The other algorithm to find a user from UserPrincipalName in AX :
Domain isn't in parameters of the method, so we need to check all domain available.


 static UserInfo getUserInfoFromADUPN(EMail  _upn)  
 {  
   System.DirectoryServices.DirectorySearcher     directorySearcher;  
   System.DirectoryServices.SearchResult        searchResult;  
   System.DirectoryServices.DirectoryEntry       entry;  
   System.DirectoryServices.PropertyCollection     PropertyCollection;  
   System.DirectoryServices.PropertyValueCollection  PropertyValueCollection;  
   System.DirectoryServices.SearchScope        searchScope;  
   str                         selectedDomainName;  
   str                         prefix = 'LDAP://';  
   int                         totalNumber;  
   str                         criteria;  
   str                         networkDomain,domainNameTmp;  
   NetworkAlias                    adAlias;  
   AxaptaUserManager                  mgr;  
   container                      domainNames;  
   int                         domainCount,j,totalfound;  
   UserInfo                      userInfo;  
   ;  
   if(_upn)  
   {  
     try  
     {  
       mgr = new AxaptaUSerManager();  
       domainNames = mgr.enumerateDomains('');  
       if(domainNames)  
       {  
         domainCount = conlen(domainNames);  
         // Add all the domain names to the domains combo box  
         if(domainCount > 0)  
         {  
           for(j=0;j<domainCount;j++)  
           {  
             domainNameTmp = '';  
             domainNameTmp = conpeek(domainNames, j+1);  
             searchScope = CLRInterop::parseClrEnum('System.DirectoryServices.SearchScope', 'Subtree');  
             entry = new System.DirectoryServices.DirectoryEntry(prefix+domainNameTmp);  
             directorySearcher = new System.DirectoryServices.DirectorySearcher(entry);  
             directorySearcher.set_PageSize(65535);  
             directorySearcher.set_CacheResults(false);  
             directorySearcher.set_SearchScope(searchScope);  
             criteria += strfmt('(userPrincipalName=%1)', _upn) ;  
             directorySearcher.set_Filter(strfmt('(&(objectClass=user)(objectCategory=person)%1(userAccountControl:1.2.840.113556.1.4.803:=512))', criteria));  
             searchResult = directorySearcher.FindOne();  
             if(searchResult)  
             {  
               //searchResult = searchResultCollection.get_Item(0);  
               entry = searchResult.GetDirectoryEntry();  
               if (entry)  
               {  
                 PropertyCollection = entry.get_Properties();  
               }  
               if (!PropertyCollection)  
               {  
                 continue;  
               }  
               PropertyValueCollection = PropertyCollection.get_Item('samAccountName');  
               if (PropertyValueCollection)  
               {  
                 if (PropertyValueCollection.get_Value())  
                 {  
                   adAlias = PropertyValueCollection.get_Value();  
                   break;  
                 }  
               }  
             }  
           }  
         }  
       }  
     }  
     catch (Exception::CLRError)  
     {  
       error("@SYS117735");  
       return null;  
     }  
   }  
   userInfo.clear();  
   if(adAlias)  
   {  
     select userInfo where userInfo.id == adAlias;  
   }  
   return userInfo;  
 }  


In order to use other Active Directory properties, you can use ADExplorer to check name of properties and use it in PropertyCollection class (get_item() method)

Hope this could be helpful.

J.