Author: tw

  • Powershell script: Copy DEV Layers to LIVE

    The following Powershell script copies the AX2009 DEV Layers to LIVE thereby checking that the services have been stopped and also copying the previous layers into old and backing up old in subfolders of old:

    [void][System.Reflection.Assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
    $AXSERVER_DEV = 'srvaos2'
    $AXSERVICE_DEV = 'AOS50?01' # ? stands for $
    $AXPATH_DEV = '\\'+ $AXSERVER_DEV + '\c$\Program Files\Microsoft Dynamics AX\50\Application\Appl\AX09_DEV'
    $AXPATH_DEV_OLD = '\\'+ $AXSERVER_DEV + '\c$\Program Files\Microsoft Dynamics AX\50\Application\Appl\AX09_DEV\old'
    $AXSERVER_PROD = 'srvaos1'
    $AXSERVICE_PROD = 'AOS50?02' # ? stands for $
    $AXPATH_PROD = '\\'+ $AXSERVER_PROD + '\c$\Program Files\Microsoft Dynamics AX\50\Application\Appl\AX09_LIVE'
    $AXPATH_PROD_OLD = '\\'+ $AXSERVER_PROD + '\c$\Program Files\Microsoft Dynamics AX\50\Application\Appl\AX09_LIVE\old'
    $LABELFILES = 'axsa*.ald'
    $LAYERFILES = 'axcus*.aod'
    $AOIFILES = '*.aoi'
    $ALIFILES = '*.ali'
    
    function copyAXAppl
    {
        #DEV and PROD services must be stopped
        echo ('checking service ' + $AXSERVICE_DEV + ' on ' + $AXSERVER_DEV)
        if((get-service -ComputerName $AXSERVER_DEV -name $AXSERVICE_DEV).status -eq 'Stopped')
        {
            echo 'ok' ('checking service ' + $AXSERVICE_PROD + ' on ' + $AXSERVER_PROD)
            if((get-service -ComputerName $AXSERVER_PROD -name $AXSERVICE_PROD).status -eq 'Stopped')
            {
                echo 'ok' ('copying from ' + $AXPATH_DEV + ' to ' + $AXPATH_PROD)
                #CREATE PROD\OLD BACKUP DIR (YYYYMMDD)
                if(-not (test-path (join-path $AXPATH_PROD_OLD (get-date -uformat '%Y%m%d'))))
                {
                    new-item -path $AXPATH_PROD_OLD -name (get-date -uformat '%Y%m%d') -itemtype directory
                }
                #BACKUP PROD (only CUS Files)
                (dir -path $AXPATH_PROD -name $LABELFILES ) |
                foreach-object {copy $_.pspath (join-path $AXPATH_PROD_OLD (get-date -uformat '%Y%m%d')) }
                (dir -path $AXPATH_PROD -name $LAYERFILES) |
                foreach-object {copy $_.pspath (join-path $AXPATH_PROD_OLD (get-date -uformat '%Y%m%d')) }
                #COPY PROD\OLD\{14 days old} -> PROD\OLD
                if(test-path (join-path $AXPATH_PROD_OLD (get-date (get-date).addDays(-14) -uformat '%Y%m%d')))
                {
                    (dir -path (join-path $AXPATH_PROD_OLD (get-date (get-date).addDays(-14) -uformat '%Y%m%d')) -name '*.ald' ) |
                    foreach-object {copy $_.pspath $AXPATH_PROD_OLD }
                    (dir -path (join-path $AXPATH_PROD_OLD (get-date (get-date).addDays(-14) -uformat '%Y%m%d')) -name '*.aod' ) |
                    foreach-object {copy $_.pspath $AXPATH_PROD_OLD }
                }
                #CREATE DEV\OLD BACKUP DIR (YYYYMMDD)
                if(-not (test-path (join-path $AXPATH_DEV_OLD (get-date -uformat '%Y%m%d'))))
                {
                    new-item -path $AXPATH_DEV_OLD -name (get-date -uformat '%Y%m%d') -itemtype directory
                }
                #BACKUP DEV (only CUS Files)
                (dir -path $AXPATH_DEV -name $LABELFILES ) |
                foreach-object {copy $_.pspath (join-path $AXPATH_DEV_OLD (get-date -uformat '%Y%m%d')) }
                (dir -path $AXPATH_DEV -name $LAYERFILES) |
                foreach-object {copy $_.pspath (join-path $AXPATH_DEV_OLD (get-date -uformat '%Y%m%d')) }
                #COPY DEV -> DEVOLD
                (dir -path $AXPATH_DEV -name '*.ald' ) |
                foreach-object {copy $_.pspath $AXPATH_DEV_OLD }
                (dir -path $AXPATH_DEV -name '*.aod' ) |
                foreach-object {copy $_.pspath $AXPATH_DEV_OLD }
                
                #COPY DEV->PROD (only CUS Files)
                (dir -path $AXPATH_DEV -name $LABELFILES ) |
                foreach-object {copy $_.pspath $AXPATH_PROD }
                (dir -path $AXPATH_DEV -name $LAYERFILES ) |
                foreach-object {copy $_.pspath $AXPATH_PROD }
                #DELETE PROD AOI
                (dir -path $AXPATH_PROD -name $AOIFILES ) |
                foreach-object {remove-item $_.pspath}
                #DELETE DEV AOI
                (dir -path $AXPATH_DEV -name $AOIFILES ) |
                foreach-object {remove-item $_.pspath}
                #DELETE PROD ALI
                (dir -path $AXPATH_PROD -name $ALIFILES ) |
                foreach-object {remove-item $_.pspath}
                #DELETE DEV ALI
                (dir -path $AXPATH_DEV -name $ALIFILES ) |
                foreach-object {remove-item $_.pspath}
            }
            else
            {
            [System.Windows.Forms.MessageBox]::Show('First stop '' + (get-service -name AOS50?02).Displayname + ''')
            }
        }
        else
        {
            [System.Windows.Forms.MessageBox]::Show('First stop '' + (get-service -name AOS50?01).Displayname + ''')
        }
    }
    

    Edit 20.06.2014: variable servers

  • Report Print Preview: “Go to main table” link for display method fields

    Situation:
    A report containing e.g. PurchParmLine.itemId() will not show the orange hyperlink in the print preview screen that allows the user to jump to the main table.

    Solution:
    To make add a link to a display method field on a report you must replace the display method field by a temporary table datasource. Populate the desired field and send the temporary datasource.

    public void executeSection //e.g. located in PurchParmLine body
    {
        InventTable tmpPrintItemId; //any table with the ItemId field will do
        ;
        tmpPrintItemId.ItemId = PurchParmLine.itemId();
        element.send(tmPrintItemId);
        super();
    }

    Now you can add a Field with Properties Table = Inventtable and DataField = ItemId, which will print the contents of PurchParmLine.itemId() and will have the orange hyperlink in the print preview that allows the user to jump to the main table.

    See also msdn How to

  • Reset program cache file for all clients

    I had a problem with some clients running on old code (e.g. experiencing bugs that have already been fixed).

    The following job resets the GUID which in turn forces all clients to create a new program cache file (AX_*.auc). A restart of the AOS is required.

    static void forceNewAUC(Args _args)
    {
        #AiF
        SysSQMSettings sysSQMSettings;
        ;
        ttsbegin;
        update_recordset sysSQMSettings setting GlobalGUID = str2Guid(#EmptyGuidString);
        ttscommit;
        info("Restart the AOS Service to generate a new GlobalGUID.");
    }

    (Source: http://blogs.msdn.com/emeadaxsupport/archive/2010/01/25/identical-ax-2009-auc-file-created-for-multiple-ax-installations.aspx)

    Another option would be adding the following to the Info.startupPost() method, however I’m not sure if this is a cleaver thing to do routinely, but if you cannot restart the AOS it is you only option.

    xSession::removeAOC();
    SysTreeNode::refreshAll();
    SysFlushDictionary::doFlush();
    SysFlushAOD::doFlush();
    xSession::updateAOC();

    (Source: http://dynamics-ax.blogspot.com/2006/04/flush-aos-cache-from-code.html)

  • Get screen width, height etc. in X++ code

    I took me a bit longer than expected to Google the solution for this Dynamics AX X++ coding question, as I was did not have the keyword “system metrics” in mind. Maybe this post will help someone to be 2 minutes faster in finding the solution.

    static void test_WinAPI_getSystemMetrics(Args _args)
    {
        #WinAPI
        int screenWidth, screenHeight;
        ;
        screenWidth = WinAPI::getSystemMetrics(#SM_CXSCREEN);
        screenHeight = WinAPI::getSystemMetrics(#SM_CYSCREEN);
        info(strfmt("Screen resolution: %1 x %2",screenWidth, screenHeight));
    
        //other parameters:
        //SM_CXSCREEN = 0        ' Width of screen
        //SM_CYSCREEN = 1        ' Height of screen
        //SM_CXFULLSCREEN = 16   ' Width of window client area
        //SM_CYFULLSCREEN = 17   ' Height of window client area
        //SM_CYMENU = 15         ' Height of menu
        //SM_CYCAPTION = 4       ' Height of caption or title
        //SM_CXFRAME = 32        ' Width of window frame
        //SM_CYFRAME = 33        ' Height of window frame
        //SM_CXHSCROLL = 21      ' Width of arrow bitmap on
        //                       '  horizontal scroll bar
        //SM_CYHSCROLL = 3       ' Height of arrow bitmap on
        //                       '  horizontal scroll bar
        //SM_CXVSCROLL = 2       ' Width of arrow bitmap on
        //                       '  vertical scroll bar
        //SM_CYVSCROLL = 20      ' Height of arrow bitmap on
        //                       '  vertical scroll bar
        //SM_CXSIZE = 30         ' Width of bitmaps in title bar
        //SM_CYSIZE = 31         ' Height of bitmaps in title bar
        //SM_CXCURSOR = 13       ' Width of cursor
        //SM_CYCURSOR = 14       ' Height of cursor
        //SM_CXBORDER = 5        ' Width of window frame that cannot
        //                       '  be sized
        //SM_CYBORDER = 6        ' Height of window frame that cannot
        //                       '  be sized
        //SM_CXDOUBLECLICK = 36  ' Width of rectangle around the
        //                       '  location of the first click. The
        //                       '  second click must occur in the
        //                       '  same rectangular location.
        //SM_CYDOUBLECLICK = 37  ' Height of rectangle around the
        //                       '  location of the first click. The
        //                       '  second click must occur in the
        //                       '  same rectangular location.
        //SM_CXDLGFRAME = 7      ' Width of dialog frame window
        //SM_CYDLGFRAME = 8      ' Height of dialog frame window
        //SM_CXICON = 11         ' Width of icon
        //SM_CYICON = 12         ' Height of icon
        //SM_CXICONSPACING = 38  ' Width of rectangles the system
        //                       ' uses to position tiled icons
        //SM_CYICONSPACING = 39  ' Height of rectangles the system
        //                       ' uses to position tiled icons
        //SM_CXMIN = 28          ' Minimum width of window
        //SM_CYMIN = 29          ' Minimum height of window
        //SM_CXMINTRACK = 34     ' Minimum tracking width of window
        //SM_CYMINTRACK = 35     ' Minimum tracking height of window
        //SM_CXHTHUMB = 10       ' Width of scroll box (thumb) on
        //                       '  horizontal scroll bar
        //SM_CYVTHUMB = 9        ' Width of scroll box (thumb) on
        //                       '  vertical scroll bar
        //SM_DBCSENABLED = 42    ' Returns a non-zero if the current
        //                       '  Windows version uses double-byte
        //                       '  characters, otherwise returns
        //                       '  zero
        //SM_DEBUG = 22          ' Returns non-zero if the Windows
        //                       '  version is a debugging version
        //SM_MENUDROPALIGNMENT = 40
        //                       ' Alignment of pop-up menus. If zero,
        //                       '  left side is aligned with
        //                       '  corresponding left side of menu-
        //                       '  bar item. If non-zero, left side
        //                       '  is aligned with right side of
        //                       '  corresponding menu bar item
        //SM_MOUSEPRESENT = 19   ' Non-zero if mouse hardware is
        //                       '  installed
        //SM_PENWINDOWS = 41     ' Handle of Pen Windows dynamic link
        //                       '  library if Pen Windows is
        //                       '  installed
        //SM_SWAPBUTTON = 23     ' Non-zero if the left and right
        //                       ' mouse buttons are swapped
    }
    
  • Bug: spaces when pressing shift

    Am I the only one who noticed this? Is there fix?

    When writing with the <shift>-key pressed, AX does not accept spaces.

  • Set up a new number sequence (e.g. for Blanket Orders)

    1. New Data Type
    2. Add a new SalesParameters method:

      static client server NumberSequenceReference  numRefSalesIdBlanket()
      {
          return NumberSeqReference::findReference(typeId2ExtendedTypeId(typeid(SalesIdBlanket)));
      }

    3. Assign SalesId in the somewhere during the creation of the blanket order:

      salesTable.SalesId = NumberSeq::newGetNum(SalesParameters::numRefSalesIdBlanket()).num();

    4. Add new reference to NumberSeqReference_SalesOrder class (used to add the new number sequence to the list in the CustParameters form)

      protected void loadModule()
      {
          NumberSequenceReference numRef;
          ;
      //blanket order number sequence
          numRef.DataTypeId              = typeId2ExtendedTypeId(typeid(SalesIdBlanket));
          numRef.ReferenceHelp           = literalstr("@SYS53960");
          numRef.ReferenceLabel          = literalstr("@CUS525");
          numRef.WizardManual            = NoYes::No;
          numRef.WizardAllowChangeDown   = NoYes::No;
          numRef.WizardAllowChangeUp     = NoYes::No;
          numRef.SortField               = 1;
          this.create(numRef);
          …
      }

    5. Set up the new number sequence in the customer parameters form.

    Done.

  • Dialogs

    I read in some other blog how to apply customer methods such as lookup() to a standard dialog. The trick is to apply a Field Identifier to the dialog field, and then create a method that follows the naming convention so it will be found at runtime. See the example below:

    A)

    class myClass extends RunBase
    {
        Dialog              dialog;
        #define.FID_OPPORTUNITYID(903)
    }

    // Add fields to the dialog
    protected Object dialog(DialogRunbase _dialog, boolean _forceOnClient)
    {
        DialogRunbase  dialog = super();
        DialogGroup     dialogGroup;
        ;
        dialogGroup = dialog.addGroup("@SYS102068");
        dlgFldOpportunityId = new DialogField(dialog, typeid(smmOpportunityId), #FID_OPPORTUNITYID);
        dialog.addCtrlDialogField(dlgFldOpportunityId.name());
        dlgFldOpportunityId.init(dialog);
        dlgFldOpportunityId.value(opportunityId);
        return dialog;
    }

    //prefiltered lookup for opportunityId
    private void fld903_1_lookup()
    {
        FormStringControl control = dialog.formRun().controlCallingMethod();
        SysTableLookup    sysTableLookup = SysTableLookup::newParameters(tablenum(smmOpportunityTable), control);
        Query             query = new Query() ;
        QueryBuildDataSource    qbds;
        ;
        sysTableLookup.addLookupfield(fieldnum(smmOpportunityTable, OpportunityId));
        sysTableLookup.addLookupfield(fieldnum(smmOpportunityTable, Subject));
        qbds = query.addDataSource(tablenum(smmOpportunityTable));
        if(partyId)
            SysQuery::findOrCreateRange(qbds,fieldnum(smmOpportunityTable, PartyId)).value(partyId);
        sysTableLookup.parmQuery(query);
        sysTableLookup.performFormLookup();
    }

    This is very neat if you need to enhance only one special field, however I quickly noticed, that when you start improving the user interface, you will add the next function and the next and so on. Then the trick above is not the way to go. It is much easier to add a customer dialog as follows:

    B)

    class myClass extends RunBase
    {
        Dialog              dialog;
    }

    protected Object dialog(DialogRunbase _dialog, boolean _forceOnClient)
    {
        DialogGroup dialogGroup;
        ;
        //dialog = super();
        dialog = Dialog::newFormnameRunbase(formstr(CustomDialog),this);
        dialog.caption(Runbase::getDescription(classidget(this)));
        dialogGroup = dialog.addGroup("@SYS102068");
        //add custom fields, without special functionality

        return dialog;
    }

    The corresponding form needs to have the following groups and buttons to work. In addition to those groups you can add various other fields, datasources etc. with all the handlers – such as lookup() – you like. You can pass the results by extracting myClass with element.args().caller().runbase() and implementing some parm methods on myClass, which you can access in edit methods.

    //edit methods can be drag and dropped into the design section
    edit ItemId itemId(boolean _set, ItemId _itemId)
    {
        ItemId ret;
        ;
        if(_set)
        {
            myClass.parmItemId(_itemId);
        }
        ret = myClass.parmItemId();
        return ret;
    }

    Thomas

  • Talking about Developing a new Workflow

    Sumit created nice overview of workflow-implementations in AX:

    Developing a new Workflow

    The section below describes briefly, the process of developing a new workflow.

    To develop a new workflow, following artifacts or objects need to be created / modified:

    • Workflow Categories
    • Workflow Templates
    • Workflow Query (Document)
    • Workflow Approvals and Tasks (Tasks are optional)
    • Enabling the workflows on the form
    • Workflow submission classes

    Let us go through each of these artifacts one by one:

    Create Workflow Categories

    A workflow category defines the module in which the workflow will be available. Modules are defined by the SysModule enum. You will be doing following here:

    • Create a new category in workflow categories node (AOT>>Workflow>>Workflow Categories)
    • Specify the name and module to which it belongs

    Create Workflow Templates (AX 2012: Workflow Types)

    A workflow template brings all the different elements of the workflow together. Workflow configurations are created based on a template, and many configurations can be based on the same template. The template defines which actions are allowed and which are required.

    You will be doing following here:

    • Create a new template in the workflow templates node (AOT>>Workflow>>Workflow Templates) (AX 2012: AOT>>Workflow>>Workflow Types)
    • Specify a name and category to which it belongs

    Next we create a work flow document.

    Create Workflow Document

    A query defines what tables are used to determine that a workflow can be initiated. Here you will do the following

    • Create a new query (AOT>>Queries)
    • Specify a name and Add required tables to the data source.
    • Create a new class extending from WorkFlowDocument class (AOT>>Classes)
    • Override the method getQueryName and return the name of newly created query
    • Save the class
    • Once query and its supporting class is created, attach this to Workflow template (Specify it under the Document property of template; AX 2012: type)

    Next step is to attach Approvals and / or Tasks

    Create Workflow Approvals (or Tasks):

    An approval route may contain a number of outcomes. It may be approved, rejected, returned or a change may be requested (For task it will be complete, reject or request change). The Workflow Approval element determines which of these outcomes is allowed and what happens in the event of each outcome. Each outcome can trigger specific code by specifying a menu item for each item. Do the following to create an approval

    • Create a new approval object in the Approvals node. (AOT>>Workflow>>Approvals)
    • Specify a unique name for the approval
    • Specify the Document (The class created for query) in the Document property
    • Specify ParticipantProvider. Normally you specify WorkflowUserGroupParticipantProvider, but you can create your own participant provider. (Missing in AX 2012)
    • Specify DueDateProvider. Normally you specify WorkflowWorkCalendarDueDateProvider, but you can create your own due date provider. (Missing in AX 2012)
    • Specify HierarchyProvider. Normally you specify WorkflowLimitHierarchyProvider, but you can create your own hierarchy provider. (Missing in AX 2012)
    • Set the DocumentMenuItem to form menu item where you want the workflow to appear (Example sales order etc.)
    • Approval Outcomes:
      • Use a standard class that acts as an engine for all approval outcomes
      • You are not required to do anything but set the workflow to applicable outcomes, therefore call the same class from different menu items. The menu items simply allow you to use two different labels. In more complex workflows it may be necessary to override or copy and modify this class rather than use it directly. Example: If you have to set an outcome for Approved do the following:
        1. Create a new Action type menu item and specify a name to it
        2. Set ObjectType property to Class and Object property to WorkflowWorkItemActionManager
        3. Now get to Workflow>>Approvals>>Approval_you_created>>Outcomes>>Approve node and specify ActionMenuItem as new menuitem created before.
        4. Repeat Step 2 for all the outcomes you need
      • If you do not need an outcome you can simply disable it by changing Enabled property to No
    • Once completed, drag and drop this approval (or task) into the previously created workflow template (AX 2012: Worfklow Type)

    Enable workflow on a form

    Now that the workflow template is defined, you can specify which forms will use this template. Do the following:

    • Add a WorkflowState field (More can be added by CreatedBy, Time, Status etc.) to the required table
    • In the desired form, go to the design node properties and set property WorkFlowEnabled to “Yes”, set the WorkFlowDataSource (the table of the previous step) and (AX 2012 only) set the WorkflowType as defined above.
    • If you want, you can override the method canSubmitToWorkflow on the form and specify conditions when user can submit the record to workflow

    After enabling the workflow on class, create a class to submit the records to workflow

    Create a Submit to Workflow class

    To submit a document to workflow, call standard code to prompt the user for a comment and to process the submission. (Look at PurchReqWorkflow class for an example)

    • After creating the submit to workflow class, create a corresponding action menu item
    • Now traverse to the workflow template (AX 2012: Worfklow Type) and specify this submit to workflow class menu item on the SubmitToWorkFlowMenuItem property
    • Once this is done you can then configure the workflow and test it. (You can refer the Workflows – Installation and Configuration document that I sent earlier for configuring the workflows)

    This will give you a broad idea as to what needs to be done for creating a workflow.

    [reformatted on 07/07/2014]

  • Links in Infolog

    Just a short reminder for myself.

    If you want your info text ( or warning or error) to link to a record, then you can just add a SysInfoAction_TableField class to the call:

    info(“info text”, “help text”, SysInfoAction_TableField::newBuffer(salesTable));

    But if there is the link is not defined by the record e.g. in the case of returns, you need to use the SysInfoAction_FormRun class:

    SysInfoAction_FormRun sysInfoAction_FormRun;
    ;
    sysInfoAction_FormRun = SysInfoAction_FormRun::newFormname(formstr(ReturnTable));
    sysInfoAction_FormRun.parmCallerBuffer(salesTable);
    info(“info text”, “help text”, sysInfoAction_FormRun);