Thursday, December 26, 2013

How to make your ProgressBar progress on server

In AX 2012 we deal with three types of progress bars:

  • SysOperationProgress
  • SysOperationProgressEmbedded
  • SysOperationProgressServer
that initialized in RunBase.progressInit method.

What concerns the latter that runs on the server side for batch jobs and tasks, I have never seen before that the Progress field changed during batch tasks execution. 



This is because RunbaseProgress Construct method is called without _disableProgressWithoutGUI parameter; nevertheless, it exists in its own New method. This parameter is always TRUE.

I do not know if this option is disabled by purpose having some other developing perspectives in mind, or simply by mistake.

In fact, it is not enough just to extend constructors accordingly to pass this parameter from your own processing class down to SysOperationProgressServer methods. There is another bug in Construct.

This bug does not take into account the caller class that creates its progress bars passing an empty GUID, so that Update method always refreshes the first progress bar found in SysProgress table. In other words, if you started a few batch tasks simultaneously, you will see an interesting progressing: after 100% it starts from zero and so on until the last task finishes. No progressing for the rest of the tasks.

The third bug is in Reset method, which actually run in turn by progress.Kill method (now marked as obsolete). Unfortunately, I do not inderstand the logic of this approach to reset the progress bar when my tasks is finished. From my point of view, it must be really 'killed' it in the end.

All in all, the following changes in forementioned objects fix the problem.

Wednesday, October 9, 2013

Beginner, Expert, or Both? from sqlservercentral.com

This short notice by Steve Jones on sqlservercentral.com drew my attention because, even though he talks about SQL, but in fact, this is the exactly same question that I always pose to myself working with AX.



It seems that there's no shortage of interviewers complaining about the lack of qualified candidates for open positions. It seems to me that the best thing you could do to get a better job is work through all the articles and lots of forum posts from SQLServerCentral, complete all the exercises in your favorite SQL Server book, and improve your skills so that you shine in an interview. If most candidates are really that bad, it can't be that hard to make yourself stand out with a little work.
However it seems few people do that. In fact, it seems that quite often we find people that have 5 years of experience with SQL Server really have 2-4 months of experience repeated 15-30 times. What's more disconcerting is that they don't realize how much knowledge they lack.
This article might provide some of the reasons why people think they're more experienced than they are. They're expert beginners, and since they can accomplish the things they're asked to do at their jobs, they think they're competent and become complacent. They have success because they're not asked to do more, perhaps because their employer only needs a small fraction of SQL Server knowledge to keep their systems running along.
I think this is a hard trend to break. Employers don't want to pay you to learn skills that aren't important to them, and they certainly don't want to pay you to learn things that will move you to a new employer. However that doesn't mean you should have the same attitude.
I think you should continue to expand your skills, grow your knowledge, and try new techniques. The best people in all professions continually question their current skills and techniques, looking for better ways to accomplish tasks. It's good to use what you know works, have experience with, and ensure stability in your systems, but always be prepared to evolve if you find something that works better. 
And keep looking for ways to do better.




The discussion may be of your interest too. 

Wednesday, September 18, 2013

Caching display methods with Client modifier

Caching display methods can increase performance in some cases. But be cautious when you are adding them in datasource Init method. If by mistake you add one method that defined with Client modifier on the table, it will fail right calculations for them all for the first line in the grid.

For example, you have one method on the table with Client modifier:

display client Integer isLineChanged()
{
    #ResAppl

    if (this.Changed)
    {
        return #ImageInfo;
    }

    return 0;
}

On the form you add this method amongst others in cache (not the last one!):

// Form DataSource Init method
public void init()
{
    super();
    this.cacheAddMethod(tablemethodstr(myTable, errorExist));
    this.cacheAddMethod(tablemethodstr(myTable, isLineChanged));
    this.cacheAddMethod(tablemethodstr(myTable, itemName));
    this.cacheAddMethod(tablemethodstr(myTable, categoryName));
    this.cacheAddMethod(tablemethodstr(myTable, subCategoryName));
}

You will get the following result because all of these methods will be calculated based on client side buffer that is not fetched yet.


It does not matter if you place a cached display method in the form or not -- it is calculated anyway.

Friday, September 13, 2013

How to compare two records of the same table

Amongst other mundane chores, from time to time AX consultants and programmers need to check if two table records are indentical and what the difference is if they are not.

Here is a small project for AX 2012 with tmxTableBufferOperation class that provides the following static methods: 
  • getOneRecordFieldList(Common _record1) 
  • getTwoRecordsFieldList(Common _record1, Common _record2) 
  • areIdentical(Common _record1, Common _record2)
  • getDifference(Common _record1, Common _record2)
Their names are mnemonical so you won't misunderstand what they do.

There is also a batch demonstrating how to use them:



The main idea is to use reflection DictTable class to enumerate all the fields of the given table buffer and then populate containers with its field ids, names and values.



static public List getTwoRecordsFieldList(Common _record1, Common _record2)
{
    Common              buffer1;
    Common              buffer2;

    List                list        = new List(Types::Container);   //list of all the fields, field names and values
    List                bufferList  = new List(Types::Container);   //final list of all the fields, field names and values of these two records
    ListEnumerator      le;

    int                 i;
    fieldId             fieldId;
    DictTable           dictTable;
    DictField           dictField;

    container           c;

    if(_record1.TableId != _record2.TableId)
    {
        error(strFmt('Both records are supposed to be of the same table type!'));
    }

    dictTable = new DictTable(_record1.TableId);

    if (dictTable)
    {
        // create the list of all the fields in the table
        for (i = dictTable.fieldCnt(); i; i--)
        {
            fieldId = dictTable.fieldCnt2Id(i);
            dictField = new DictField(dictTable.id(), fieldId);
            list.addEnd([fieldId, fieldId2name(dictTable.id(), fieldId)]);
        }

        buffer1 = dictTable.makeRecord();
        buffer2 = dictTable.makeRecord();

        select buffer1 where buffer1.RecId == _record1.recId;
        select buffer2 where buffer2.RecId == _record2.recId;

        le = list.getEnumerator();
        while (le.moveNext())
        {
            c = le.current();
            bufferList.addEnd([[conPeek(c,1), conPeek(c,2), buffer1.(conPeek(c,1))],
                               [conPeek(c,1), conPeek(c,2), buffer2.(conPeek(c,1))]]);
        }
    }
    return bufferList;
}

And how you can use it.

static void tmxCompareFixedAssets(Args _args)
{
    #define.FA1('MACH10DMO-000018')
    #define.FA2('MACH20DMO-000009')
    
    List                    list;
    ListEnumerator          le;
    container               c;
    
    list = TmxTableBufferOperation::getDifference(AssetTable::find(#FA1), AssetTable::find(#FA2));
    le      = list.getEnumerator();
    info('Difference 1 <> 2');
    while (le.moveNext())
    {
        c = le.current();
        info(strFmt("%1: %2 <> %3", conPeek(conPeek(c,1), 2), conPeek(conPeek(c,1), 3),  conPeek(conPeek(c,2), 3)));
    }
    
}

Result:

Thursday, August 29, 2013

Wednesday, July 31, 2013

PowerShell Script to Run As A Different User

Short script for PowerShell that allows you to start, for example, Dynamics AX client under a different user account.

The main idea is to store the user's password as a secured string in a file, then to restore it from there and create credentials to launch the program.

First, run the script to save the password:


Write-Host "Input password for USERNAME..."
read-host -assecurestring | convertfrom-securestring | out-file C:\Scripts\USERNAMEpwd.txt

The following script can be saved in a file like RunDynamicsAXasUSERNAME.ps1 to be run as you need:


#Alex Voytsekhovskiy 2013-07-29
# Based on the links:
# http://blogs.technet.com/b/robcost/archive/2008/05/01/powershell-tip-storing-and-using-password-credentials.aspx
# http://jdhitsolutions.com/blog/2012/04/scripting-with-pscredential/
###############################################################################################
# This script is to run Dynamics AX under a different user account
# Without user input for credentials
###############################################################################################
[String]$Username = 'DOMAIN\USERNAME'

# Please check the following paths
# Absolute path to Dynamics AX shortcut
[string]$DynamicsAXAbsolutePath = 'S:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin\Ax32.exe'

# Absolute path to the secured password file
[string]$PasswordAbsolutePath  = 'C:\Scripts\USERNAMEpwd.txt'

# Get the secured password from the file
$password = get-content $PasswordAbsolutePath | convertto-securestring

# Create credentials for the program launch
$credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $Username,$password

# Launch the program
echo "Trying to launch AX 2012..."
start-Process -FilePath $DynamicsAXAbsolutePath  -Credential $credentials 


I used the following links to create this script: http://blogs.technet.com/b/robcost/archive/2008/05/01/powershell-tip-storing-and-using-password-credentials.aspx
http://jdhitsolutions.com/blog/2012/04/scripting-with-pscredential/

Small update for the case when we want to launch AX with a particular configuration file.

# Please check the following paths
# Absolute path to Configuration file
[string]$DynamicsAXC = 'S:\ConfigurationFiles\MyFavouriteConfiguration.axc'

###############################################################################################
# the password should be saved previously as a secured string with the following script
# Write-Host "Input password..."
# read-host -assecurestring | convertfrom-securestring | out-file C:\Users\USERNAME\Documents\AX-DEVS\Scripts\USERNAMEpwd.txt
###############################################################################################

# Launch the program
echo "Trying to launch AX 2012..."
start-Process -FilePath $DynamicsAXAbsolutePath -argumentlist $DynamicsAXC -Credential $credentials