Deploying and Managing Models in AX 2012

Microsoft had introduced the Model concept in Dynamics AX 2012, in order to help customers more easily to install and maintain multiple solutions side by side in same layer. It is recommended to use models and the model store to deploy customizations or entire solutions.

 

Model management should be very well known topic for build managers, but sometimes developers need to do the same task for any purpose.

 

For that purpose, our team made instructions using two command-line tools:

 

  • Windows PowerShell
  • AXUtill

 

How to Create an Empty Model that Has Default Manifest Properties

 

(AXUtil)

On the Start menu, click Command prompt.

 

Navigate to the directory for the management utilities. Typically, the location of this directory is %ProgramFiles%\Microsoft Dynamics AX\60\ManagementUtilities. At the command prompt, type the following command, and then press ENTER.

 

axutil create /model:<ModelName>/layer:<LayerName>

 

(Windows PowerShell)

 

On the Start menu, click All Programs -> Administrative Tools -> Microsoft Dynamics AX Management Shell. At the Windows PowerShell command prompt, PS C:\>, type the following command, and then press ENTER.

 

New-AXModel –Model <ModelName> -Layer <LayerName>

 

How to Export a Model

 

(AXUtil)

 

On the Start menu, click Command prompt.

 

Navigate to the directory for the management utilities. Typically, the location of this directory is %ProgramFiles%\Microsoft Dynamics AX\60\ManagementUtilities. At the command prompt, type the following command, and then press ENTER.

 

axutil export /model:<modelname> /file:<filename> /verbose

 

(Windows PowerShell)

 

On the Start menu, click All Programs -> Administrative Tools -> Microsoft Dynamics AX Management Shell. At the Windows PowerShell command prompt, PS C:\>, type the following command, and then press ENTER.

 

Export-AXModel -Model <ModelName> -File <FileName.axmodel>

 

These commands export the model to an .axmodel file

 

 

How to Uninstall a Model

 

 

(AXUtil)

 

On the Start menu, click Command prompt. Navigate to the directory for the management utilities. Typically, the location of this directory is %ProgramFiles%\Microsoft Dynamics AX\60\ManagementUtilities.

 

At the command prompt, type the following command, and then press ENTER.

 

axutil delete /model:<name>

 

(Windows PowerShell)

 

On the Start menu, click All Programs -> Administrative Tools -> Microsoft Dynamics AX Management Shell. At the Windows PowerShell command prompt, PS C:\>, type the following command, and then press ENTER.

 

Uninstall-AXModel –Model <ModelName>

 

It is recommended to perform this action only if the intention is the model to be permanently removed. But if the goal is to upgrade a model or replace a model with newer version, it is better to import the model over the existing model, because element IDs and element handles are assigned at installation.

 

If you delete a model, and then import the same model or a model with newer version, element IDs and element handles are randomized, and that affects data integrity.

 

How to Import a Model

 

(AXUtil)

 

On the Start menu, click Command prompt. Navigate to the directory for the management utilities.

 

Typically, the location of this directory is %ProgramFiles%\Microsoft Dynamics AX\60\ManagementUtilities. At the command prompt, type the following command, and then press ENTER.

 

axutil import /file:<filename> /verbose

 

(Windows PowerShell)

 

On the Start menu, click All Programs -> Administrative Tools -> Microsoft Dynamics AX Management Shell. At the Windows PowerShell command prompt, PS C:\>, type the following command, and then press ENTER.

 

Install-AXModel –File <FileName.axmodel> -Details

 

These commands install the specified .axmodel file in the same layer that it was exported from.

 

When you import a model, elements in the model that you are importing may conflict with another model in the same layer or may conflict with overlaying version for these elements in upper layer. These conflicts must be resolved. There are several ways to resolve the conflicts occurred during the model import process, and we will write about it too.

 

When you install or uninstall a model, it is recommended, before taking the action, to create a backup of the database and after the performed action you can run a full compile, synchronize the database and generate Full CIL.

Print Multiple Invoices into One PDF File in AX7

The Problem

 

One of the improvements that we had to do for one of our clients was the possibility to select multiple invoices from the grid control and then print them all into one PDF, instead of the default action of creating a separate PDF file per invoice.

 

print multiple invoices into one pdf

 

In our particular case, the print destination settings are maintained for all the invoices and confirmed on the press of the OK button.

 

print destination settings

The Solution

 

To perform the operation, we had to extend the system SRSReportRunController class and to create an additional method “runFromArgs(Args _args)” where we replicated all the steps that are performed when the report is run, with the purpose of getting the rendered report bytes for the selected invoices.

 

The next step was to put the data into a container and use the merging method where the container is being read as a stream and afterwards written to a file and sent to the user.

 

In this process, we used the PDFsharp library (PDRSharp.dll) which needed to be added as a reference to the project.

 

public static void runFromArgs(Args _args)
{
if (!_args || _args.dataset() != tableNum(InvoiceTable))
{
            throw error(“@SYS22996”);
}

InvoiceTable invoice = _args.record() as InvoiceTable;

Map selectedRecordMap =          Map::create(Helpers::getFormRecordOrderedByField(FormDataUtil::getFormDataSource(invoice), fieldNum(InvoiceTable,InvoiceNum)));

MapEnumerator mapEnum = selectedRecordMap.getEnumerator();

boolean firstReport = true;
boolean printMediumFile;
container con;
str mergedFileName;
boolean proceed = true;
SRSPrintMediumType firstMediumType;

while (mapEnum.moveNext())
{
   PdfSharp.Pdf.PdfDocument initialDocument;
   invoice = mapEnum.currentValue();
           ExtendedInvoiceReportController controller = new ExtendedInvoiceReportController();
           controller.parmReportName(invoice.PSOInvoiceLayoutTable().ReportName);
           _args.record(invoice);
           controller.parmArgs(_args);
   controller.parmShowDialog(firstReport==true);

   SrsReportDataContract reportContract = controller.parmReportContract();
   SRSReportExecutionInfo executionInfo;
   guid reportRunId = newGuid();
   executionInfo = new SRSReportExecutionInfo();
   executionInfo.parmReportRunId(reportRunId);
   reportContract.parmReportExecutionInfo(executionInfo);
           reportContract.parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration());

   SRSPrintDestinationSettings printSettings;
   printSettings = reportContract.parmPrintSettings();

   if(firstReport)
   {
controller.prompt();
   }
   else
   {
printSettings.printMediumType(firstMediumType);
printSettings.fileFormat(SRSReportFileFormat::PDF);
printSettings.overwriteFile(true);
   }
   if(controller.dialogCanceled && firstReport == true)
   {
proceed = false;
firstReport = false;
   }
   if(proceed)
   {
             if(printSettings.printMediumType() == SRSPrintMediumType::File)
             {
                 firstMediumType = printSettings.printMediumType();
                 controller.runReport();

Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[]  parameterValueArray;
Map reportParametersMap;
rsReportRunPrinter reportRunPrinter;

SrsReportRunService service = new SrsReportRunService();
service.getReportDataContract(invoice.InvoiceLayoutTable().ReportName);
service.preRunReport(reportContract);

reportParametersMap = service.createParamMapFromContract(reportContract);

parameterValueArray = SrsReportRunUtil::getParameterValueArray(reportParametersMap);
reportRunPrinter = new SrsReportRunPrinter(reportContract, parameterValueArray);

SRSReportFileFormat fileFormat = printSettings.fileFormat();
System.Byte[] reportBytes;

SRSProxy proxy = reportRunPrinter.parmSrsProxy();
                reportBytes = proxy.renderReportToByteArray(reportContract.parmReportPath(),
                                                  parameterValueArray,
                                                  fileFormat,
                                                  printSettings.deviceInfo());

                str content = System.Convert::ToBase64String(reportBytes);

                if(firstReport)
                {
                           mergedFileName = printSettings.parmFileName();
                }
                con += content;
                firstReport = false;

                printMediumFile = true;

                }
                else
                {
                   irstMediumType = printSettings.printMediumType();
                   controller.runOperation();
                   firstReport = false;
                }
             }
         }
         if(printMediumFile)
        {
           Helper::mergePDFs(con, mergedFileName);
        }

  }

 

The Method

 

Further on, the actual method for merging of the PDFs is as follows:

 

   static PdfSharp.Pdf.PdfDocument mergePDFs(container _con, str _mergedName)
    {
        PdfSharp.Pdf.PdfDocument outPutPDFDocument = new PdfSharp.Pdf.PdfDocument();
        PdfSharp.Pdf.PdfDocument inputPDFDocument = new PdfSharp.Pdf.PdfDocument();
        PdfSharp.Pdf.PdfPages pdfPages;
        container con = _con;
        int i, j, pageCount;
        FileName pdfFile;
        InteropPermission permission;
        str errorMessage;
        System.IO.MemoryStream mergedStream;
        str tempDir;
        str mergedName = ‘INVOICES_’ + _mergedName;

        try
        {
            permission = new InteropPermission(InteropKind::ClrInterop);
            permission.assert();

            for (i = 1; i <= conLen(con); i++)
            {

                System.Byte[] reportBytes;
                System.IO.MemoryStream stream;

                reportBytes = System.Convert::FromBase64String(conPeek(con,i));

                stream  = new System.IO.MemoryStream(reportBytes);
inputPDFDocument = PdfSharp.Pdf.IO.PdfReader::Open(stream,PdfSharp.Pdf.IO.PdfDocumentOpenMode::Import);
                outputPDFDocument.set_Version(inputPDFDocument.get_Version());
                pageCount = inputPDFDocument.get_PageCount();
                pdfPages = inputPDFDocument.get_Pages();

                if (pageCount > 0)
                {
                    for (j = 0 ; j < pageCount; j++)
                    {
                        outputPDFDocument.AddPage(pdfPages.get_Item(j));
                    }
                }
            }
            new InteropPermission(InteropKind::ClrInterop).assert();

    tempDir = ‘C:\\tempDir’;
            System.IO.Directory::CreateDirectory(tempDir);

            CodeAccessPermission::revertAssert();
            outputPDFDocument.Save(tempDir + ‘\\’ + mergedName);
            System.Byte[] outBytes = System.IO.File::ReadAllBytes(tempDir + ‘\\’ + mergedName);
            mergedStream = new System.IO.MemoryStream(outBytes);
            File::SendFileToUser(mergedStream, mergedName);
            CodeAccessPermission::revertAssert();

            new InteropPermission(InteropKind::ClrInterop).assert();
            PSOHelpers::clearDirectory(tempDir);
            System.IO.Directory::Delete(tempDir);
            CodeAccessPermission::revertAssert();
        }
        catch(Exception::CLRError)
        {
            errorMessage = AifUtil::getClrErrorMessage();
            CodeAccessPermission::revertAssert();
            throw error(errorMessage);
        }
        return outputPDFDocument;
    }

    static void clearDirectory(Filename _folderName)
    {
        System.String[] files;
        int             filecount;
        int             i;

        if (WinAPI::folderExists(_folderName))
        {
            files = System.IO.Directory::GetFiles(_folderName);
            filecount = files.get_Length();

            for(i=0; i < filecount; i++)
            {
                System.IO.File::Delete(files.get_Item(i));
            }

        }

   }

 

Side Effects

 

As a side effect, we faced the problem that extra PDF files were being created for all the invoice reports that were additionally going to be sent to the client along with the merged file, which would be pretty annoying for the client.

 

We managed to resolve that problem with the use of a post handler for the “onToFile” method from the SRSPrintDestinationSettingsDelegates class, where the method was set to not return a value in a set condition.

 

[PostHandlerFor(classStr(SRSPrintDestinationSettingsDelegates), methodStr(SRSPrintDestinationSettingsDelegates, onToFile))]
public static void SRSPrintDestinationSettingsDelegates_Post_onToFile(XppPrePostArgs args)
{
        ProjectHoursContract projectHoursContract;
        SrsReportDataContract dataContract;

        dataContract = args.getArgNum(2);
        projectHoursContract = dataContract.parmRdpContract() as ProjectHoursContract;

        if(!projectHoursContract)
        {
            args.setReturnValue(false);
        }
}