Wednesday, October 4, 2017

How to synchronize manually database and model on Dynamics 365 for Operations

Hi everyone,

Today, another tips to help you synchonize manually some objets between models and your database (Table, Type, Views, Entities ...)

Visual Studio uses actually a command to do this job on each process of synchronization from IDE (on Visual Project contextual menu for example or in the Dynamics 365 Menu).

To use this command ourself, we need to know it !
Type C:\AosService\PackagesLocalDirectory\Bin\SyncEngine.exe /? in a command prompt and you could see command options


Usage: 

SyncEngine -option=[value]
-metadatabinaries   Specify the metadata provider path
-binfolder   Specify the location of the bin folder
-metadatafolder   Specify the location of the metadata
-connect   Specify the business database connection string
-syncmode   Specify the sync mode. Supported mode: {fullall | initialschema | fullsecurity | fullids | fulltablesandviews | partiallist | drop | drop,partiallist | partialsecurity | partiallist,partialsecurity}
-synclist   Specify list of tables or views that need be synced
-droplist   Specify list of tables or views that need be dropped
-rolelist   Specify list of roles that need be synced
-roleExtensionlist   Specify list of role extensions that need be synced
-dutylist   Specify list of duties that need be synced
-dutyExtensionlist   Specify list of dutie extensions that need be synced
-privilegelist   Specify list of privilegs that need be synced
-policylist   Specify list of security policies that need be synced
-droproles   Specify list of security roles that need be dropped
-droproleextensions   Specify list of security role extensions that need be dropped
-dropduties   Specify list of security duties that need be dropped
-dropdutyextensions   Specify list of security duty extensions that need be dropped
-dropprivileges   Specify list of security privileges that need be dropped
-droppolicies   Specify list of security policies that need be dropped
-midisplaylist   Specify list of display menu items that was changed for the table permission synchronization
-miactionlist   Specify list of action menu items that was changed for the table permission synchronization
-mioutputlist   Specify list of output menu items that was changed for the table permission synchronization
-formlist   Specify list of forms that was changed for the table permission synchronization
-reportlist   Specify list of reports that was changed for the table permission synchronization
-sqltimeout   Specify Sql Command timeout in minutes
-verbosity   Specify logging verbosity level. Supported level: {Normal | Diagnostic}
-ignoreIndexList   Specify list of table with index that need to be ignored during Sync. The Expected format : {TableA.IndexA, TableB.IndexB}


Some examples of the command :
Usual command for a full compil :


C:\AosService\PackagesLocalDirectory\bin\syncengine.exe -syncmode="fullall" -metadatabinaries="C:\AosService\PackagesLocalDirectory" -connect="Data Source=localhost;Initial Catalog=AxDataBase;Integrated Security=True;Enlist=True;Application Name=AXVSSDK" -verbosity="Minimal"


Command for a partial compil of object's list (here just the CustCustomerEntity)


C:\AosService\PackagesLocalDirectory\Bin\SyncEngine.exe -syncmode="partiallist" -metadatabinaries=C:\AosService\PackagesLocalDirectory -connect="Data Source=AxServer.database.windows.net;User ID=admin@AxServer.database.windows.net;Password=xxxxxxxxx;Initial Catalog=DataBaseName;Integrated Security=false;Enlist=True;Application Name=SyncEngine" -verbosity="Diagnostic" -synclist="CustCustomerEntity"






Monday, October 2, 2017

Duplicates ID on two models in Dynamics 365 for operations

Hi everyone,

Today, I would like to speak about Ids issue during environment update with Microsoft KBs.
I don't know if this solution is a best practice, but it works, and that's all I wanted at this time.

Update process contains two main steps : Preparation and installation.
During installation process, the system could notice you of a duplicate Id issue because a package (a custom!) has the same Id as a new one, present in the update.

In this case, you could : Export all code of your package, uninstall it, install the new standard package with the famous reserved Id, then create a new custom package for your customization and import your code in.
It's a bummer like the "Dude" will say, isn't it ? :)

So the shortest way could be : Stop IIS, edit descriptor of your custom package, change the ID, save and start IIS. Just to be safe, you could open Visual and compil all your application.

Launch a new installation of your previous preparation update, and Duplicate Ids issue will in theory vanish.

Happy Dynamics update!

Monday, July 31, 2017

How to deploy SSRS reports manually

Hi everyone,

Today a new tips to deploy SSRS Report on Dynamics 365 For Operations.
The new ERP release use automatic deployment features from LifeCycle Services, with packages.

To deploy some reports manually, you could use 2 powershell scripts available on Dynamics 365 for operations Virtual Machine :

With a powershell (run as administrator), execute the command
"Set-ExecutionPolicy Unrestricted", then call :

For Local environment
C:\Packages\Plugins\AxReportStartVmRoleStartupTask\DeployAllReportsToSSRS.ps1
For Azure environment

C:\AosService\PackagesLocalDirectory\Plugins\AxReportVmRoleStartupTask\DeployAllReportsToSSRS.ps1 -PackageInstallLocation “C:\AosService\PackagesLocalDirectory”

Adapt drive letter of each command (J: or something else)

Tuesday, June 6, 2017

Tips : How to override super call by event Handler

Hi everyone,

Today, just a tips, maybe usefull for someone (I hope).

With Dynamics 365 for operations, developer used to implement Event Handler method, based on standard code.
Sometimes, it would be interesting to override the super of the event handler caller, if we want to cancel for example the process in an iteration.

That's possible by a simple way, too little documented in my opinion.

Here a sample of code :

Source


    /// 
    ///Override the create method logic
    /// 
    /// 
    /// 
    [FormDataSourceEventHandler(formDataSourceStr(SalesTable, SalesTable), FormDataSourceEventType::Creating)]
    public static void SalesTable_OnCreating(FormDataSource sender, FormDataSourceEventArgs e)
    {
        FormDataSourceCancelEventArgs ce = e as FormDataSourceCancelEventArgs;

        ce.cancel(true); // remove the super call

    }

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.