Author: tw

  • GDPdU Sample Configuration

    AX 2009 offers the GDPdU (Grundsätze zum Datenzugriff und zur Prüfbarkeit digitaler Unterlagen) Export in the German Layer for which there is a nice Description:

    However, the rest is left completely up to the user to start from scratch. The following is a sample GDPdU definition as a starting point for your configuration (Import using Basic>>Periodic>>Import/Export>>Import)

  • Recreate AX2009 AIF Messages (save to file)

    After a BizTalk mishap a customer wanted to recreate the xml files for all AIF Messages in a certain timeframe. The following job does exactly this.

    static void AIFMessagesRecreate(Args _args)
    {
        AifMessageLog   AifMessageLog;
        AifDocumentLog  aifDocumentLog;
        AifMessage      aifMessage;
        Dialog          dialog = new Dialog("Recreate AIF Messages to file");
        int             i;
        Filename        filePath;
        DialogField     dlgFilePath;
        Query           q = new Query();
        QueryRun        qr;
        QueryBuildDatasource    qbds;
        #file
        TextIo          diskFile;
        //copied and enhanced - Source AifMessage::serialize()
        AifMessageXml serialize(AifMessage message, AifDocumentXml _AifDocumentXml)
        {
            #Aif
            AifMessageXml           messageXml;
            XmlTextWriter           xmlTextWriter;
            XmlTextReader           xmlTextReader;
            AifDocumentXmlNamespace documentNamespace;
            AifXmlEncoding encoding;
            ;
            encoding = AifUtil::updateEncodingAttribute(message.encoding());
            documentNamespace = #MessageNamespace;
            xmlTextWriter = XmlTextWriter::newXml();
            xmlTextWriter.formatting(XmlFormatting::None);
            AifUtil::writeXmlDeclaration(xmlTextWriter, encoding);
            //Write the Envelope element
            xmlTextWriter.writeStartElement2(#MessageEnvelope, documentNamespace);
            //Write the Header element
            xmlTextWriter.writeStartElement(#MessageHeader);
            xmlTextWriter.writeElementString(#MessageId, guid2str(message.messageId()));
            // The source endpoint user is not sent on outbound messages for security reasons
            if (message.direction() != AifMessageDirection::Outbound)
                xmlTextWriter.writeElementString(#MessageSourceUser, message.sourceEndpointUserId());
            xmlTextWriter.writeElementString(#MessageSourceEndpoint, message.sourceEndpointId());
            xmlTextWriter.writeElementString(#MessageDestEndpoint, message.destinationEndpointId());
            xmlTextWriter.writeElementString(#MessageAction, message.externalAction());
            if(message.requestMessageId())
                xmlTextWriter.writeElementString(#MessageRequestId, guid2str(message.requestMessageId()));
            //End the Header
            xmlTextWriter.writeEndElement();
            //Write the Body element
            xmlTextWriter.writeStartElement(#MessageBody);
            try
            {
                xmlTextReader = XmlTextReader::newXml(_AifDocumentXml);
                xmlTextReader.whitespaceHandling(XmlWhitespaceHandling::None);
                //Move past the declaration
                xmlTextReader.moveToContent();
                xmlTextWriter.writeRaw(xmlTextReader.readOuterXml());
                xmlTextReader.close();
            }
            catch(Exception::Error)
            {
                //Unable to serialize the contents of the Xml property.
                throw error(strfmt('@SYS89763'));
            }
            //End the Body
            xmlTextWriter.writeEndElement();
            //End the Envelope
            xmlTextWriter.writeEndElement();
            messageXml = xmlTextWriter.writeToString();
            xmlTextWriter.close();
            return messageXml;
        }
        ;
        dlgFilePath = dialog.addFieldValue(typeId(FilePath),'C:\\Temp\\');
        if(dialog.run())
        {
            filePath = dlgFilePath.value();
            qbds = q.addDataSource(tablenum(AifMessageLog));
            qbds.addRange(fieldnum(AifMessageLog,createdDateTime));
            qbds.addRange(fieldnum(AifMessageLog,DestinationEndpointId));
            qr = new QueryRun(q);
            if(qr.prompt())
            {
                while (qr.next())
                {
                    if(qr.changed(tablenum(AifMessageLog)))
                    {
                        aifMessageLog = qr.get(tablenum(AifMessageLog));
                        select firstonly aifDocumentLog where aifDocumentLog.MessageId == aifMessageLog.MessageId;
                        aifMessage = new AIFMessage(AifMessageLog.MessageId,AifMessageLog.SourceEndpointUserId,aifMessageLog.SubmittingUserId);
                        aifMessage.destinationEndpointId(AifMessageLog.DestinationEndpointId);
                        aifMessage.sourceEndpointId(AifMessageLog.SourceEndpointId);
                        diskFile = new TextIo(filePath + guid2str(AifMessageLog.MessageId) + ".xml", #io_Write, 65001); //UTF-8
                        diskFile.write(serialize(aifMessage,aifDocumentLog.DocumentXml));
                        diskFile = null;
                        i++;
                    }
                }
            }
        }
        info(strfmt("%1 files saved",i));
    }
    
  • Version Control: Enforce some, but not all Best Practice Rules

    I’m not saying that having disabled best practice checks is desired, but it might be helpful, when enabling version control on a system with lots of legacy development, thereby still requiring some minimal best practice checks.

    The know location for enforcing all best practices in version control is here: Menu>>Tools>Development tools>>Version control>>Setup>>System settings:

    image

     

    Exclude certain best practice requirements by editing SysTreeNode.allowCheckIn:

    image

  • Controlled Release of new Developments

    Issue

    Various Developments are in different stages of development, testing and release.
    It can be challenging to keep track of all changes and keep the different systems in sync.

    Release management option 1: Use XPOs

    Process

    • Development system (DEV): Development w/o special consideration of release process
      When done, move completed developments via xpo to TEST.
    • Test system (TEST): Testing
      When done, move successfully tested object to PROD (or REL) using xpo
    • Release system (REL): Create release layer if desired
      When done move Layer to PROD.
    • Productive system (PROD): Active code base

    Issues

    • Manual identification and extraction of relevant objects required
    • Three different code bases
    • Danger of object-Ids being out of sync
    • Objects can simultaneously contain modifications in different stages of development; some should be release, others not

    Release management option 2: Use configuration keys

    Process

    • Development system (DEV): Development adding configuration keys allowing to deactivate the newly created code parts. (Do not use configuration keys for Database objects – PROD, TEST and DEV databases should be identical –> Enabling/Disabling configuration keys will never result in database issues).
      When done, move entire layer to TEST (synchronization between developers required, to ensure that all have disable their changes using configuration keys.)
    • Test system (TEST): Activate Configuration Keys ready for testing; Test. Additional checks required that deactivated developments do not have unintended side effects.
      When done, move entire layer to PROD. (In some cases the code might have been release earlier, so only enabling configuration keys is required in PROD.)
    • Production system (PROD): Enable configurations keys.

    Advantages (referring to option 1)

    • Simple identification and activation using configuration keys
    • One code base
    • Identical object-ids in all systems
    • No issues in with objects simultaneously containing modifications in different stages of development.

    Disadvantages

    • Incorrectly disabled code can cause issues à additional testing required (release document needs to identify new added – but not enable code)

    My conclusion

    In my opinion the advantages of Option 2 outweigh the disadvantage of the additional “disabled”-testing required.

    Practical aspects

    Configuration key structure

    Add a configuration key parent for pending objects and one for released objects. In DEV all configurations are always enabled (this can be ensured by script – see below), in TEST only some configurations in the “Release pending” group might be enabled. In PROD, no configurations in the “Release pending” group might are enabled.

    image

    After successful testing the configuration keys are moved into the released group. Note: Moving between the “Release pending” and the “Released” group does nothing in itself – it is recommended for keeping track of the status of the various objects and can be used by scripts to identify the status of configuration keys.

     

    Enable DEV/TEST configuration keys after restore

    The following code enables all config keys under ReleasePending. It is useful after a restore of the PROD database into test and can be run as manually executed job or can be included in Info.startupPost() to automatically run when the current database is TEST (SysSQLSystemInfo::construct().getloginDatabase()).

    /// <summary>
    /// Activate all configuration keys under ReleasePending ==> for test system only!
    /// </summary>
    static void enableControlledReleaseConfigs()
    {
        int i;
        Dictionary dict = new Dictionary();
        SysDictConfigurationKey sDCK;
        ConfigurationKeySet configurationKeySet;
        ;
        configurationKeySet = new ConfigurationKeySet();
        configurationKeySet.loadSystemSetup();
    
        for (i=dict.configurationKeyCnt(); i; i--)
        {
            sDCK= new SysDictConfigurationKey(dict.configurationKeyCnt2Id(i));
            if(sDCK.parentConfigurationKeyId() == configurationKeyNum(ReleasePending))
            {
                configurationKeySet.enabled(sDCK.id(),true);
            }
        }
        SysDictConfigurationKey::save(configurationKeySet.pack());
        SysSecurity::reload(true,true,true,false);
    }
    
  • Excel Sheetname Function

    [Important Edit: The definitive solution to the issue below is using CELL(“filename”,A1). The reference ensures the refresh to the current worksheet.]

    Issue description

    As many of you might have found out, you can get the sheet name using the formula

    =MID(CELL(“filename”);FIND(“]”;CELL(“filename”))+1;99)

    The issue with this solution is however that the sheet name does not refresh immediately when changing sheet.

    The following screenshots show how this as well as a proposed function you can add as add-in:

    Sheet 1: all good!
    image

    Sheet 2: field A1 has not refreshed
    image

    Sheet 2: after a press of F9 A1 is recalculated (Note that setting the recalculation options to “automatic” does not help.)
    image

    Proposed solutions

    • Get Microsoft to add a new function “SHEETNAME”
    • Add a custom function “SHEETNAME” yourself.

    Create custom function “SHEETNAME

    Create a new Excel File

    Go to the VB-Editor (Alt-F11)

    Create a new module containing the following code:

    Function SHEETNAME(reference)
    SHEETNAME = reference.Parent.Name
    End Function

    Go back to the normal Excel View

    Save As, pick Type “Excel Add-In (*.xlam)”, the path will change automatically, Save
    image

    Add the Add-In in Excel Options:
    image

    Browse for the file you created an add the Add-In
    image

    Now the new function “SHEETNAME” can be used.

    Usage

    =SHEETNAME(reference)

    It’s however reasonable to add an IFERROR to capture the error caused by not having the custom function – of course the updating issue of the standard method will occur.

    =IFERROR(SHEETNAME(A1);MID(CELL(“filename”);FIND(“]”;CELL(“filename”))+1;99))

    Sheet 1: without Add-in
    image

  • Missing image resource browser

    My only trick to find the resource number of an image is to use the resource browser.

    In one installation I missed the resources browser (which should be found in the Menu: Tools –> Development tools –> Embedded Resource)

    I didn’t find out why the menu option was missing but the workaround is easy:
    in the AOT you can look for the form SysImageResources, which still works fine:

    SNAP

  • Global::str2con() converts strings to numbers, if it can.

    I don’t like that the function Global::str2con(str _value, str 10 _sep = ‘,’) converts strings to numbers, if it can. It does this on a field by field basis, i.e. the resulting container might be a mixed bag of strings and numbers, which is difficult to process.

    I added my own function to avoid this:

    public static container str2strCon(str _value, str 10 _sep = ',')
    {
        #define.SC(&quot;¶&quot;) //special character (for reversibility of strCon2str with str2strCon)
        int length = strlen(_value), sepLength = strlen(_sep);
        int i = 1;
        int j = strscan(_value, _sep, 1, length);
        container ret;
        ;
        while (j)
        {
            ret += strReplace(substr(_value, i, j-i),#SC,_sep);
            i = j+sepLength;
            j = strscan(_value, _sep, i, length);
        }
        ret += strReplace(substr(_value, i, length-i+1)),#SC,_sep);
        return ret;
    }
    

    While we’re at it:
    The reverse function con2str does not guarantee reversibility – if a component of the container contains the separator, then the reversing function (str2Con) will result in a larger container. So I added a similar function strCon2str() to generate strings from a container while removing the separator character from strings in a container. This guarantees the reversibility of the function. My str2strCon() function above restores the separator character in the component-strings.

    static str strCon2str(container c, str 10 _sep = ',')
    {
        #define.SC(&quot;¶&quot;) //special character (for reversibility of strCon2str with str2strCon)
        str ret, s;
        int i;
        ;
        for (i=1; i&lt;= conlen(c); i++)
        {
            s = conpeek(c,i);
            ret = (ret?ret + _sep:&quot;&quot;) + strreplace(s,_sep,#SC);
        }
        return ret;
    }
    
  • Fetch number of records in a FormDataSource (e.g. of a Grid)

    SysQuery::getTotal works fine, but the trick is how to handle temporary data sources where getTotal does not work:

    if(!formDataSource.curser().isTmp())
    {
        recordCount = SysQuery::getTotal(formDataSource.queryRun());
    }
    else
    {
        while(!formDataSource.allRowsLoaded())
        {
            formDataSource.getNext();
        }
        recordCount = formDataSource.numberOfRowsLoaded();
    }
    

    Now recordCount contains the number of Records in the FormDataSource irrespective of the Tmp status of the data source. Of course the whole tmp-data source has been loaded in the process, which might be an issue in some cases.

  • SQL Script: After Restore from PROD to TEST

    Do you want PROD data in your TEST System? Easy, just restore a backup of PROD into your TEST-DB. Run the following script to clean up some AOS specific data:

    Update [AX09_TEST].[dbo].[BatchServerGroup] set SERVERID = ’01@SRVAOS1′  where SERVERID = ’02@SRVAOS1′
    –01: DEV, 02: PROD, 03: MIG

    Update [AX09_TEST].[dbo].[BATCHSERVERCONFIG] set SERVERID = ’01@SRVAOS1′  where SERVERID = ’02@SRVAOS1′
    –01: DEV, 02: PROD, 03: MIG

    Update [AX09_TEST].[dbo].[SYSSERVERCONFIG] set SERVERID = ’01@SRVAOS1′ , SERVERGUID = newid() where SERVERID = ’02@SRVAOS1′
    –01: DEV, 02: PROD, 03: MIG

    Update [AX09_TEST].[dbo].[Batch] set SERVERID = ’01@SRVAOS1′  where SERVERID = ’02@SRVAOS1′
    –01: DEV, 02: PROD, 03: MIG

    delete [AX09_TEST].[dbo].[SYSCLIENTSESSIONS]

    –the following has nothing to do with the restore to TEST
    –remove obsolete [SYSCLIENTSESSIONS] from PROD

    select * from [AX09_LIVE].[dbo].[SYSCLIENTSESSIONS] where STATUS = 0

    delete [AX09_LIVE].[dbo].[SYSCLIENTSESSIONS] where STATUS = 0

  • 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