Monday, 7 March 2016

Error while activating inbound port

Last week we received an error while trying to activate a previously working WCF inbound port. The error message was
The service '<SERVICE_NAME_HERE>' could not be generated.\n  Error: SysDictType object not initialized.

Stack trace

(S)\Classes\AifServiceDataTypeGenerator\generateType - line 76
(S)\Classes\AifServiceDataTypeGenerator\generateDataContractClass - line 127
(S)\Classes\AifServiceDataTypeGenerator\generateType - line 54
(S)\Classes\AifServiceDataTypeGenerator\generate - line 68
(S)\Classes\AifMessageContractGenerator\generate - line 22
(S)\Classes\AifServiceGenerator\generate - line 30
(S)\Classes\AifServiceGenerationManager\generateServices - line 102
(S)\Classes\AifPortManager\deployPort - line 22
(C)\Forms\AifInboundPort\Designs\DesignList\DeployPort\Methods\Clicked - line 12
The error message was shown on "AIF services" form "General" fast tab.

On investigation we found out that there was a code merge between two TFS branches and somehow one of the EDT used in the service contract was removed. When AIF was trying to generate service artifacts, it could not find the EDT and hence was throwing above error. Once the EDT was re-created, the error disappeared and service activated successfully.

This posting is provided "AS IS" with no warranties. Use code at your own risk.

Sunday, 21 February 2016

A call to SSPI failed, AX-BizTalk WCF service

Recently we developed integration between AX and an external system through BizTalk. The data flow from BizTalk to AX was using a WCF service. Everything worked fine in dev/test/uat systems. Once the code was deployed to production system and configured to talk to BizTalk we started getting the following error.

A call to SSPI failed, see inner exception. ---> System.ComponentModel.Win32Exception: The target principal name is incorrect

On investigation we found that User Principal Name (UPN) was not setup properly on the BizTalk Send port. The UPN is usually the AD user under which AX service is running (in an email format e.g. axServiceUser@companydomain.com). It can also be viewed from the service WSDL URI. Find the service from the inbound ports and copy the WSDL URI.


Paste the URI in internet explorer and WSDL will be shown. Scroll to the end and UPN is shown (highlighted below).


Copy this value and paste it in the "Endpoint Identity" in the Send port of BizTalk application as shown below


The error message should disappear.

Note: There may be other causes (Kerberos/Double hop issues) of this error message. In our cause this was the only issue.

This posting is provided "AS IS" with no warranties. Use code at your own risk.

Monday, 15 February 2016

Error when accessing AX7 VM

For last few days error started appearing when trying to login to local AX7 VM as shown below


It seems the account we were using was created by MS and was provisioned as Admin in the AX7 VM. For some reason MS have now disabled the account and as a result we cannot access the AX7 environment.

The solution is to provision another user as Admin in the AX7 VM environment. While signing up for AX7 preview (blog post here), you must have gone to portal.office.com and created a user there. The user login is in the form username@businessname.onmicrosoft.com. This user can be provisioned as an admin on the AX7 VM.

On the AX7 VM desktop find the icon shown below


Double click it and enter the user that was created on portal.office.com and click "Submit" button.


It takes some time and then you should get the following message


Now if you use the same user (that you provisioned above) to login to AX7, it should work.

This posting is provided "AS IS" with no warranties. Use code at your own risk.

Monday, 4 January 2016

AX7 Application Code Store (ModelStore in AX2012)

In different versions of Dynamics AX, application code is stored at different places and in different formats. Table below summaries this for last few versions.


Pre-AX2012
AX2012
AX7
RTM
R2-R3
Location
File System
Database - With Business Data
Database - Separate From Business Data
File System
Format
Proprietary Format
Tables
Tables
XML Files

In AX7 Microsoft has not only changed the way application code is stored but also how it is grouped. Application code, that should stay together and is distributed/compiled as a unit, is called a Package.

Location:
In AX7 the application code is saved in file system in a directory and is called PackageDirectory. The location of directory (along with other settings) is saved in a web.config file. Why web.config? Because the AX7 AOS is implemented as ASP.NET web application running on an IIS.

In IIS right click on the site "AOSWebApplication" and click on Explore as shown below.


In the folder, find web.config, open it with notepad and search for "Aos.PackageDirectory". This will have the location of application directory.


In this instance we can see that application code is located in C:\Packages. This file also contains the information of business database location.

Folder Structure:
Following diagram highlights the structure of package directory in AX7.



  • Application code consists of many packages including the standard ones that Microsoft shipped (ApplicationFoundation, ApplicationPlatform, ApplicationSuite, Calendar etc) and any custom packages. 
  • In individual package folder, a folder named "Descriptor" is present. This holds the information of the package, the packages it references and the modes that are part of this package. The file is in XML format.
  • Each package consists of one or more models (Model 1, Model 2...Model n). 
  • In the model folder (Model 1 in the above screenshot) there is a folder for each AX object type (AxClass, AxEDT, AxTable etc).
  • The information (code etc) for individual objects is stored in a XML file (shown from FleetManagment package in the above screenshot).

This posting is provided "AS IS" with no warranties. Use code at your own risk.

Thursday, 31 December 2015

Bulk disable of indexes

Recently we did some performance assessment of our system using DynamicsPerf and identified unused indexes. Some of these indexes were heavily updated but were not used. So a decision was made to disable these indexes. The indexes were in a csv file (in the format tableName, indexName).

Following script was used to disable the indexes instead of manually doing the task. As we use source control, the script checks if the table is in source control and if so, it checks it out before disabling it. If not it adds the table to source control after disabling the index. It also creates a private project with all the affected tables added to it. DB synchronization will have to be done manually.

 static void readAndDisableIndexes(Args _args)  
 {  
   #AOT  
   #File  
   #SysVersionControl  
   
   TreeNodePath        path;  
   TextIo              file;  
   container           data;  
   FileIOPermission    permission;  
   int                 totalTableAdded, totalIndexesDisabled;  
   str                 fileName = 'C:\\Temp\\UnUsedIndex.csv';  
   Set                 set = new Set(Types::String);  
   Set                 setAddedToVS = new Set(Types::String);  
   boolean             inVS;  
   boolean             vcsEnabled = SysVersionControlParameters::isVCSEnabled();  
   
   UtilElements        currUtilElement;  
   IdentifierName      tableName, indexToDisable;  
   IdentifierName      projectName = 'MSPerfPorject';  
   
   ProjectSharedPrivate    projectType = ProjectSharedPrivate::ProjPrivate;  
   ProjectNode             projectNode = SysTreeNode::getPrivateProject().AOTfindChild(projectName);  
   SysVersionControlSystem vcsSys = VersionControl.parmSysVersionControlSystem();  
   
   ProjectNode createProject(IdentifierName _projectName, ProjectSharedPrivate _projectType)  
   {  
     ProjectNode projectNodeLoc = SysTreeNode::getPrivateProject().AOTfindChild(_projectName);  
   
     info(strFmt('Project name : %1', _projectName));  
     if (projectNodeLoc)  
     {  
       projectNodeLoc.AOTdelete();  
       info('Project deleted as it already existed.');  
     }  
   
     projectNodeLoc = SysTreeNode::createProject(_projectName, _projectType);  
     info('Project created.');  
   
     return projectNodeLoc;  
   }  
   
   void addTableToProject(ProjectNode _projectNode, IdentifierName _tableToAddName)  
   {  
     _projectNode.addUtilNode(UtilElementType::Table, _tableToAddName);  
     info(strFmt('Table added: %1', _tableToAddName));  
   }  
   
   boolean checkoutIfInVS(SysVersionControlSystem _vcsSys, UtilElements _utilElement)  
   {  
     boolean ret = false;  
     TreeNode treeNode;  
     SysVersionControllable controlable;  
   
     treeNode = SysTreeNode::findNodeInLayer(_utilElement.recordType, _utilElement.name, _utilElement.parentId, currUtilElement.utilLevel);  
     controlable = SysTreeNode::newTreeNode(treeNode);  
   
     if(!_vcsSys.allowCreate(controlable))  
     {  
       ret = true;  
       _vcsSys.commandCheckOut(controlable);  
       info('Table checked out as it is part of VCS.');  
     }  
   
     return ret;  
   }  
   
   void addToVS(SysVersionControlSystem _vcsSys, UtilElements _utilElement)  
   {  
     TreeNode treeNode;  
     SysVersionControllable controlable;  
   
     treeNode = SysTreeNode::findNodeInLayer(_utilElement.recordType, _utilElement.name, _utilElement.parentId, currUtilElement.utilLevel);  
     controlable = SysTreeNode::newTreeNode(treeNode);  
   
     if(_vcsSys.allowCreate(controlable))  
     {  
       _vcsSys.commandAdd(controlable);  
       info('Table added to VCS.');  
     }  
   }  
     
   void disableIndex(IdentifierName _tableName, IdentifierName _indexNameSQL)  
   {  
     DictIndex  dictIndex;  
     DictTable  dictTable = new DictTable(tableName2id(_tableName));  
     int counter;  
       
     if(dictTable)  
     {  
       counter = dictTable.indexNext(counter);  
       while (counter)  
       {  
         dictIndex = dictTable.indexObject(counter);  
   
         if(dictIndex.enabled() && dictIndex.name(DbBackend::Sql) == _indexNameSQL)  
         {  
           dictIndex.modify(false, dictIndex.allowDuplicates(), false);  
           info(strFmt('Disabled index : %1 - %2', dictIndex.name(), _indexNameSQL));  
         }  
   
         counter = dictTable.indexNext(counter);  
       }  
     }  
   }  
   
   // project initialisation  
   projectNode = createProject(projectName, projectType);  
   projectNode.lockUpdate();  
   
   // file operations  
   permission = new FileIOPermission(fileName, #io_read);  
   permission.assert();  
   file = new TextIO(fileName, #io_read);  
   file.inFieldDelimiter(",");  
   
   //Read file  
   While (file.status() == IO_Status::Ok)  
   {  
     data = file.read();  
   
     if (conlen(data))  
     {  
       inVS = false;  
   
       tableName   = conPeek(data, 1);  
       indexToDisable = conPeek(data, 2);  
   
       // check and add table to project  
       currUtilElement = xUtilElements::find(UtilElementType::Table, tableName);  
       path = xUtilElements::getNodePath(currUtilElement);  
   
       if(!set.in(path))  
       {  
         addTableToProject(projectNode, tableName);  
         set.add(path);  
   
         if(vcsEnabled)  
         {  
           inVS = checkoutIfInVS(vcsSys, currUtilElement);  
         }  
       }  
         
       // Make the change  
       disableIndex(tableName, indexToDisable);  
   
       // if not in VS Add to Version Control  
       if (vcsEnabled && !inVS && !setAddedToVS.in(tableName))  
       {  
         addToVS(vcsSys, currUtilElement);  
       }  
       setAddedToVS.add(tableName);  
     }  
   }  
   
   // Save the project  
   projectNode.unlockUpdate();  
   projectNode.AOTsave();  
   projectNode.AOTrestore(true);  
     
   // Close file  
   file = Null;  
   CodeAccessPermission::revertAssert();  
   
   info('Finished process.');  
 }  

This posting is provided "AS IS" with no warranties. Use code at your own risk.