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.


Wednesday, October 5, 2016

Duplication SSRS for AX 2012 R3

Here is a light post to duplicate SSRS report on Microsoft AX Dynamics.
This is not a standard feature of AX Dynamics (with a good wizard for example). So your need to do that manually, adapting all components of your new report and business logic.


SSRS report is composed by many class and objets, so to duplicate an existing report, you need to duplicate certain or all composants. I listed main components and steps just below :

- Duplicate Controller, Contract, and Data Provider classes on AOT (MorphX) (& Tmp tables "attached" on Data provider if you need to use them)
- Duplicate the query associated on the Data Provider Class
- If controller is not overhead because your report template use super classes (srsReportRunController to start operation), you could create it duplicating other one. Check Type Hiearachy Browser on SrsReportRunController class to show all extended controller class. Adapt main class and modify/create class to custom it (prePrompt, preRunModifyContract for example)
- Duplicate SSRS report with Visual Studio AOT solution explorer (this option is available only in VS)
- Duplicate also the Business Logic class (C# project) if your template report contains business methods. You need to do that, export C# Projetct in XPO and modify name of class, Origin GUID and name of .cs. (See link below for that)
- Create a new project on Visual with the new name of report and add the duplicated report
You need after set the report and parameters properties with new objects :
* Data provider to fill DataSets (this is the query which select data from data provider, a propertie in the DataSets of your report.
* Check parameters (after duplication, parameters is sometime duplicate also)
* Change the business logic of report (properties of the report --> Data Method Library which contains methods of the report.

Build the report (without any error) and publish it with Microsoft AX Dynamics Management Shell program with the command :
Publish-AXReport -ReportName myReport

Don't forget to debug your report to :
- Activate breakpoint in Server configuration
- Run as administrator the debugger
- Put a breakpoint keyword in your code (process report for example)

Thank you to this post : http://axvuongbao.blogspot.fr/2013/08/how-to-duplicate-ssrs-report-in-ax2012.html for Business Logic duplication.

There is a tool to duplicate report on AX 2012 (provide by Sreenath) but if you could to this manually, it's a good training to keep the logic of SSRS feature in Dynamics AX.


Wednesday, September 14, 2016

DIXF : Language and Entities

Today, I'm writing a new post about DIXF on Dynamics R3, specially on entities name.
After a new installation of AX Dynamics on a french operating system (things we never should implement), I checked entities of the new DIXF system and many errors on those.
On few entities, edition et mapping generation didn't work with strange message :
"Column is empty" on edition of an entities for example or also The ' ' character, hexadecimal value 0x27, cannot be included in a name (DMFXMLWizard execution)...

After debug, the problem was localized on entity name which was in french character.
DMFEntityName table initializes default entities with the current language of current user when AX Dynamics is installed.

I had to reinitialize default entities name by standard table method and regenerate mapping on DIXF.

I join my job to do that.


  // Job to regenerate default entity on the right language   
  // Login with EN-US language and run the job.   
  static void regenerateDefaultEntity(Args _args)   
  {   
   DMFEntity     dmfEntityOLD, dmfEntityUpd;   
   void deleteDMFTargetMapping(DMFEntityName _oldEntityName)   
   {   
    DMFTargetXMLToEntityMap  dmfTargetXMLToEntityMap;   
    // delete specified target Mapping   
    ttsBegin;   
     delete_from dmfTargetXMLToEntityMap where dmfTargetXMLToEntityMap.Entity == _oldEntityName;   
    ttsCommit;   
    info(strFmt("Entity %1 deleted",_oldEntityName));   
   }   
   void regenerateDMFTargetMapping(DMFEntityName _newEntityName)   
   {   
    DMFTargetXMLToEntityMap  dmfTargetXMLToEntityMap;   
    DMFEntity     dmfEntity;   
    // Regenerate Target Mapping for specified Entity   
    select dmfEntity where dmfEntity.EntityName == _newEntityName;   
    {   
     DMFTargetXMLToEntityMap::generateMapping(dmfEntity);   
    }   
    info(strFmt("Entity %1 generated",_newEntityName));   
   }   
   ;   
   if(Box::yesNo("Do you want to regenerate DMF entities?", DialogButton::Yes))   
   {   
    while select forUpdate dmfEntityOLD   
     //where dmfEntityOLD.EntityName == "Codes Taxe" // for test   
    {   
     if (dmfEntityOLD.EntityType == DMFEntityTypes::Entity)   
     {   
      ttsBegin;   
      dmfEntityUpd = dmfEntity::find(dmfEntityOLD.EntityName,true);   
      dmfEntityUpd.defaultModule(dmfEntityOLD.EntityTypeName);   
      dmfEntityUpd.update();   
      ttsCommit;   
      deleteDMFTargetMapping(dmfEntityOLD.EntityName);   
      regenerateDMFTargetMapping(dmfEntityUpd.EntityName);   
     }   
    }   
    info("Process over");   
   }   
   else   
   {   
    info("Process cancelled");   
   }   
  }   


Hope it helps someone with the same problem.
Thank you for this post which resolve for me regeneration !


Thursday, February 13, 2014

Backup du versioning MorphX VCS

Voici un post intéressant sur AX 2009 qui est compatible pour AX Dynamics 2012, pour sauvegarder et porter les données liées au versioning d'objet MorphX.

En effet, en cas de rechargement de base de données sur un environnement de développement, les objets développés sont portables en AX Model (selon la couche de développemen) voirvia  un modelstore (toutes les couches compilées).

Pour éviter la perte des versions antérieurs stockés via l'outil VCS intégré à Dynamics, il est nécessaire de sauvegarder les tables SQL "SYSVERSION*" de la base d'origine :

- SYSVERSIONCONTROLMORPHXITEMTABLE --> contient les enregistrements d'objets avec leur chemin
- SYSVERSIONCONTROLMORPHXLOCKTABLE --> contient les objets en cours d'utilisation
- SYSVERSIONCONTROLMORPHXREVISIONTABLE --> contient les données de révision
- SYSVERSIONCONTROLPARAMETERS --> paramétrage de versioning (VCS, TFS etc..)
- SYSVERSIONCONTROLSYNCHRONIZELOG --> Utiliser pour TFS donc vide à priori en cas d'utilisation de VCS.

Un modèle simple mais qui nécessite une fois exporté/importé dans la nouvelle base, une correction du compteur de recID afin de repartir d'un nombre correct pour les prochains enregistrements de versioning.

Lien vers l'article