Tuesday, April 28, 2015

Reduce SSRS deployment time for static reports in AX2012

Are you wasting minutes deploying and redeploying static SSRS reports in all the languages provided with AX2012? If you only need a handful of them, you might just as well consider disabling the licenses for the unwanted languages. You can enable them back if you need them later.

Now disabling one language at a time manually might not be your cup of tea, so I would like to share a small job that disables all languages except the ones you want to keep enabled. Just create a new job and paste in the code below. Use at own risk of course, take backups and backups of the backups etc (you know the drill).

// Remove licence codes for unwanted languages
static void AdLanguageRemover(Args _args)
    SysConfig                   sysConfig;
    SysRemoveLicense            remLic;
    Query                       query;
    QueryBuildDataSource        qbd;
    QueryBuildRange             qbr;
    QueryRun                    queryRun;
    FormRun                     confirmForm;
    Set                         languagesToKeep = new Set(Types::String);
    Set                         licenseCodeSet  = new Set(Types::Integer);
    SetEnumerator               it;
    int                         confCount       = 0;
    boolean                     licenseChanged  = false;
    Args                        args            = new Args(formStr(SysLicenseCompareForm));    
    boolean                     proceed         = false;
    SysLicenseCodeDescription   codeDescription;
    str                         currentLanguageId;
    int                         pos, sysConfigId;    
    // List of languages to keep. Add, remove, change to fit your preference
    query = new Query();
    qbd = query.addDataSource(tableNum(sysConfig));
    qbr = qbd.addRange(fieldNum(SysConfig,ConfigType));
    qbr = qbd.addRange(fieldNum(SysConfig,Id));
    queryRun = new QueryRun(query);
    delete_from remLic;
    while (queryRun.next())
        if (queryRun.changed(tableNum(sysConfig)))
            sysConfig = queryRun.get(tableNum(sysConfig));
        codeDescription     = SysLicenseCodeReadFile::codeDescription(sysConfig.Id);
        pos                 = strFind(codeDescription,'(',strLen(codeDescription),-strLen(codeDescription));
        currentLanguageId   = subStr(codeDescription,pos+1,strLen(codeDescription)-pos-1);
        if (!languagesToKeep.in(currentLanguageId))
            warning(strFmt('Removing language %1',SysLicenseCodeReadFile::codeDescription(sysConfig.Id)));
            remLic.LicenseCode = sysConfig.Id;
            remLic.Description = SysLicenseCodeReadFile::codeDescription(sysConfig.Id);
            info(strFmt('Keeping language %1',SysLicenseCodeReadFile::codeDescription(sysConfig.Id))); 
    if (licenseCodeSet.elements())
        // if not valid code, then we should display the warning            
        confCount   = SysLicenseCodeReadFile::findConfigKeysFromLicenseCodeSet(licenseCodeSet);

        confirmForm = classfactory.formRunClass(args);
        if (confirmForm.closedOk())
            it = licenseCodeSet.getEnumerator();
            while (it.moveNext())
                sysConfigId = it.current();
                update_recordSet sysConfig 
                    setting value = '' 
                    where sysConfig.id == sysConfigId;

Allow for a synchronization to run through after the licenses are modified. Remember that this may impact the database schema, but if you really do not want the (ie.) Norwegian language to be enabled, it should be safe to disable. Thanks for reading!

Saturday, March 7, 2015

ExchangeRateEffectiveView not returning cross rates to the Cubes in AX2012

I was asked to assist figuring out why exchange rates wouldn't always be retrieved to the Sales Order Cube in Dynamics AX2012. The solution became a journey through various interesting topics (for me at least):

  • Views in AX
  • View methods
  • Advanced Query range
  • Reverse engineering the SQL behind the views
Starting with the view that did not return all the expected data, we have the SalesOrderCube View. 
Now the Query in itself isn't all that fascinating, but I wasn't aware that you could add ranges across datasources like done in this Query. That is pretty handy!

Notice how the Query uses another View as DataSource. There are plenty of examples of Views and Queries being nested, and this is a powerful way to create results from a rather complex ERP data model. 
Notice also there is a custom Range on ValidFrom and ValidTo. The Ranges compare the Dates from ExchangeRateEffectiveView with the CreatedDateTime from SalesTable.

If we look at the definition of the ExchangeRateEffectiveView we see that ValidTo is a field of type Date. Furthermore we see the field is coming from a View Method. But we know CreatedDateTime on SalesTable is a DateTime.
How can it compare a Date with a DateTime? The answer is that actual date is stored in the database as a datetime where the time part is 00:00:00.000.   

So it compares the values and that works like charm. 

The problem

What happens if you have daily exchange rates in your system? Then your ValidFrom and ValidTo becomes the exact same date and more importantly the same time. This will not work correctly since CreatedDateTime also keeps track of what time on the day a Sales Order was created. 

So let's look at one specific example where we have rates for the 9th of December 2014.

And if we run the Sales Order Cube view, and modify the selected columns so we can see the problem, we will notice that the query is unable to collect the rates. The values from Exchange Rates is NULL.

The Solution

There are probably many ways to solve this, but the solution I went for was to make sure that the ValidTo always returns the time part at the max value, which is 23:59.59.000 (AX doesn't operate on the milliseconds, as far as I know).

So compare the results coming from the ExchangeRateEffectiveView before I apply the change.

By doing some small changes to the validTo View method, I can give the time part a better value.

And the result is that the Sales Order Cube now has the Cross Rates as expected. 

I hope you enjoyed this post. I sure enjoyed solving this challenge.
Here is the method body (ExchangeRateEffectiveView.validTo):
private static server str validTo(int _branchNum)
    str returnString, fieldString, fieldString2, fieldString3;
    boolean generateCode = false;
    DictView dictView;

    // Axdata.Skaue.04.03.2015 
    // Fix ValidTo with proper time 00:00:00.000 -> ->
    str adFixValidToString = 'DateAdd(ss,-1,DateAdd(d,1,%1))';
    // Axdata.Skaue.04.03.2015 <-

    dictView = new DictView(tableNum(ExchangeRateEffectiveView));

    switch (_branchNum)
        case 1:
            returnString = dictView.computedColumnString('VarToVarBefore', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            returnString = strFmt(adFixValidToString, returnString); // Axdata.Skaue.04.03.2015

        case 2:
            fieldString = dictView.computedColumnString('DenToVarEuroBefore', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            fieldString2 = dictView.computedColumnString('DenToVarEuroBefore', 'FixedStartDate1', FieldNameGenerationMode::FieldList, true);
            // Axdata.Skaue.04.03.2015 ->
            fieldString  = strFmt(adFixValidToString, fieldString); 
            fieldString2 = strFmt(adFixValidToString, fieldString2); 
            // Axdata.Skaue.04.03.2015 <-
            generateCode = true;

        case 3:
            fieldString = dictView.computedColumnString('DenToDenBefore', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            fieldString2 = dictView.computedColumnString('DenToDenBefore', 'FixedStartDate1', FieldNameGenerationMode::FieldList, true);
            fieldString3 = dictView.computedColumnString('DenToDenBefore', 'FixedStartDate2', FieldNameGenerationMode::FieldList, true);
            // Axdata.Skaue.04.03.2015 ->
            fieldString = strFmt(adFixValidToString, fieldString); 
            fieldString2 = strFmt(adFixValidToString, fieldString2);
            fieldString3 = strFmt(adFixValidToString, fieldString3);
            // Axdata.Skaue.04.03.2015 <-
            returnString = 'CASE when ' + fieldString + ' <= ' + fieldString2 +
                ' and ' + fieldString + ' <= ' + fieldString3 + ' then ' + fieldString +
                ' when ' + fieldString2 + ' <= ' + fieldString3 + ' then ' + fieldString2 + ' - 1 ' +
                ' else ' + fieldString3 + ' - 1  end';

        case 4:
            returnString = dictView.computedColumnString('SameFromTo', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            returnString = strFmt(adFixValidToString, returnString); // Axdata.Skaue.04.03.2015

        case 5:
            returnString = '\'21541231\'';

        case 6:
            returnString = '\'21541231\'';

        case 7:
            returnString = dictView.computedColumnString('DenToVarAfter', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            returnString = strFmt(adFixValidToString, returnString); // Axdata.Skaue.04.03.2015

        case 8:
            returnString = dictView.computedColumnString('DenToVarAfterRecipical', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            returnString = strFmt(adFixValidToString, returnString); // Axdata.Skaue.04.03.2015

        case 9:
            returnString = dictView.computedColumnString('VarToDenAfter', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            returnString = strFmt(adFixValidToString, returnString); // Axdata.Skaue.04.03.2015

        case 10:
            returnString = dictView.computedColumnString('VarToDenAfterRecipical', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            returnString = strFmt(adFixValidToString, returnString); // Axdata.Skaue.04.03.2015

        case 11:
            returnString = '\'21541231\'';

        case 12:
            fieldString = dictView.computedColumnString('DenToDenAfterStart1', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            fieldString2 = dictView.computedColumnString('DenToDenAfterStart1', 'StartDate2', FieldNameGenerationMode::FieldList, true);
            // Axdata.Skaue.04.03.2015 ->
            fieldString  = strFmt(adFixValidToString, fieldString); 
            fieldString2 = strFmt(adFixValidToString, fieldString2); 
            // Axdata.Skaue.04.03.2015 <-
            generateCode = true;

        case 13:
            fieldString = dictView.computedColumnString('DenToDenAfterStart1Recipical', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            fieldString2 = dictView.computedColumnString('DenToDenAfterStart1Recipical', 'StartDate2', FieldNameGenerationMode::FieldList, true);
            // Axdata.Skaue.04.03.2015 ->
            fieldString  = strFmt(adFixValidToString, fieldString); 
            fieldString2 = strFmt(adFixValidToString, fieldString2); 
            // Axdata.Skaue.04.03.2015 <-
            generateCode = true;

        case 14:
            fieldString = dictView.computedColumnString('DenToDenAfterStart2', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            fieldString2 = dictView.computedColumnString('DenToDenAfterStart2', 'StartDate1', FieldNameGenerationMode::FieldList, true);
            // Axdata.Skaue.04.03.2015 ->
            fieldString  = strFmt(adFixValidToString, fieldString); 
            fieldString2 = strFmt(adFixValidToString, fieldString2); 
            // Axdata.Skaue.04.03.2015 <-
            generateCode = true;

        case 15:
            fieldString = dictView.computedColumnString('DenToDenAfterStart2Recipical', 'ValidTo', FieldNameGenerationMode::FieldList, true);
            fieldString2 = dictView.computedColumnString('DenToDenAfterStart2Recipical', 'StartDate1', FieldNameGenerationMode::FieldList, true);
            // Axdata.Skaue.04.03.2015 ->
            fieldString  = strFmt(adFixValidToString, fieldString); 
            fieldString2 = strFmt(adFixValidToString, fieldString2); 
            // Axdata.Skaue.04.03.2015 <-
            generateCode = true;


    if (generateCode)
        returnString = 'CASE when ' + fieldString + ' <= ' + fieldString2 + ' then ' + fieldString +
            ' else ' + fieldString2 + ' - 1 end';

    return returnString;

Saturday, January 17, 2015

Supporting multiple Enterprise Portals for AX2012

With AX2012 you can setup the SharePoint sites named "Enterprise Portal". These sites run perfectly well on the free SharePoint Foundation version, and it also runs on SharePoint Foundation 2013 if you have the necessary updates. In this post I will discuss some considerations for supporting multiple environments, which might be needed if you want to support some development and testing scenarios in addition to a production environment.

Installing and configuring SharePoint is in many aspects a separate skill set, so I totally get why IT Pros prefer to leave that to dedicated SharePoint consultants. Having said that, if you just need to setup Enterprise Portal for the purpose of supporting Role Centers and giving your AX2012 install a nice looking Home page, then your SharePoint install doesn't have to be too complicated.

I will use Foundation as example, but SharePoint Server 2013 (Standard or Enterprise) obviously works with AX2012 as well. Foundation is free, but does have the necessary features for supporting Dynamics AX Enterprise Portal with it's role centers. If you plan for utilizing Power BI, OData or any of the more advanced features, you should know that upgrading Foundation to Standard or Enterprise is not supported.

Assuming you're starting with a blank server, you can download SharePoint Foundation 2013 with SP1 and begin installing the prerequisites. If any of the prerequisites doesn't install successfully, you can browse through the log file and look for the download URL and try install the failed component manually. I've experienced having to install prerequisites manually before. Eventually, you should have the necessary binaries installed and you are ready for installing SharePoint binaries.

I recommend you do not run the Configuration Wizard just yet. Rather continue with installing the updates for SharePoint. Head over to the overview of updates and download the most recent cumulative update and install it. With the latest updates installed, you are ready to initialize your SharePoint Foundation 2013 Farm.

A couple of points here:
  • Consider the account you are using to install the SharePoint farm. Typically this account is referred to the SharePoint Setup and Farm account, and you use it again to configure the farm and potentially install more SharePoint servers into the same Farm. You may need to share the credentials of this account with other IT Pros, so avoid using your own personal account.
  • Normally you want a dedicated service account for SharePoint. This is an unattended account that has broad permissions on SharePoint. The administration web application will run under this account. 
  • It is also normal to have a dedicated service accounts for several of the various services you can setup in SharePoint, but that is out of scope for this article. 
After having run the Configuration Wizard, you have the option to run the wizard that creates your first SharePoint Site. I recommend skipping this wizard, and instead create the necessary web applications manually.

Before installing the very first Enterprise Portal, I recommend the following:

  • Install AX2012 Client and Management Utilities. Point the configuration to the environment you want to install the first SharePoint site. Both the local configuration and the business connector configuration should be pointing correctly and have working WCF configurations.  
  • From the SharePoint Administration Site, you need to manually create a new "Managed Account". From the Home page, under Security and General Security, you will find "Configure managed accounts" and from there you can register the business connector account as a new managed account. The SharePoint sites running Enterprise Portal needs to run under this managed account. 
  • From the SharePoint Administration Site you also need to start the Claims to Windows Token Service (aka C2WTS). From Home, under System Settings and Servers you will find "Manage Services on Server". Locate the C2WTS and start it from here. If you start this from Services under Windows Administrative Tools (Control Panel) and not from SharePoint itself, the service will be in a faulty state and you'll get in trouble when installing Enterprise Portal. Trust me, I've been there.
Now you should be ready first install of Enterprise Portal on this SharePoint Farm. The following steps can be repeated for each environment you want to support, let it be multiple DEVs or TESTs. Obviously, this limits itself performance wise if your box can't handle the load. So lets begin:
  1. Make sure Dynamics AX local configuration and business connector configuration points to right AOS, and do not forget to refresh and save the WCF configuration to this config.
  2. Create a new Web Application. Each Portal needs to run on its own Web Application and isolated application pool. Give it a good name, both the site and the application pool. Also give the Content Database a correlating and good name. The managed account must be the one you created earlier using the business connector account. I like to put these sites on ports like 81, 82, 83, etc.
  3. When the application is created, you are ready to install Enterprise Portal using AX2012 Setup. On the step where you select Web Application you choose the one you created in step 2. Give the site a good name, like "DynamicsAXDev" or something that makes it easy to understand what environment this site will support. Imagine looking at the URL in the browser and you can easily see from the address what environment you're currently at.
  4. Assuming the installation at step 3 went through successfully,  your next step is to make sure the new site always connects to the right environment. Copy a working AX Configuration file (AXC-file) locally to the server. Make sure it has an updated and correct WCF-configuration in it. I tend to put the file under c:\inetpub\wwwroot\. Give it a good name (like DEV.axc). I know the official documentation says the file can be on a UNC-share, but I never got that to work, so a local file seems to work ok. Finally, you need to put a reference to this file inside the web.config for this particular Web Application. The file is normally located under C:\inetpub\wwwroot\wss\VirtualDirectories\81 (given this application runs on port 81). Open the file in some notepad or text editor and put in a new XML section:

    Now you can be sure the Web Application points to the right environment independently of whatever is changed in the business connector configuration settings on this server. I put the section after the System.Web section.
  5. Finally, I recommend loading the site itself and edit the Site Permissions. You probably want to make sure either Domain Users or some dedicated AD User Group has at least Read permissions. 
You can repeat these five steps for each environment you want to support. How cool is that? 

Now, what if you need to copy the AX2012 data between the environments? Well, that can be a problem, because when you install Enterprise Portal, setup connects to the AOS and adds data to the database. These data include the URL and the unique ID of the site you installed. If you start copying data around, you might end up with multiple environments pointing at the same Enterprise Portal, and this Portal points to just one of the AOSes, and that isn't very helpful. 

We need to fix that! :-)

Use this PowerShell command to identify the unique ID (GUID) for each site:

Get-SPSite http://fancysharepointserver:81/sites/dynamicsaxtest | 
Select -ExpandProperty AllWebs | 
where {$_.Url -notmatch "dynamicsaxtest/"} | ft -a ID, Url

This will reveal the ID, and you can copy it over to the following SQL command:

    @EPURL                  AS VARCHAR(255),
    @EPGUID                 AS VARCHAR(255)

    @EPGUID                 = 'e3b7b289-cb17-4c38-8e98-858181af88a5'        ,
    @EPURL                  = 'http://fancysharepointserver:81/sites/dynamicsaxtest'


       SITEID      = @EPGUID

The URL and GUID above is just examples, and will obviously differ from your environment, but you get the idea. 

Now save the SQL Command and make sure to include it in your routines when copying data from one environment to another. 

With all of this, you should be good to go and able to have multiple SharePoint applications running Enterprise Portals for different environments, all on the same server. 

Saturday, November 29, 2014

Flowchart for Batch in Dynamics AX

Are you utilizing batch for your daily business operations? I've been thinking about blogging about batch in AX for a long time, and a few weeks ago I attended one of the Microsoft webinars sessions where the topic was batch in AX. The support engineer had a brilliant flowchart and I was allowed to publish it. I've reworked it a bit, hoping it will fit the blog space better, but the credit goes to Microsoft's support engineers. :-)

Batch is just yet another great feature of Dynamics AX where you can have operations scheduled to run, sort of like Task Scheduler in Windows. You can have the task be repeated automatically and perhaps have it run during night time when your users are sleeping or partying.

Each Batch Task exists inside what is called a Batch Job, and a Batch Job can have one or more tasks, and the tasks may even be dependent on each other. You may want one task to wait until another one has completed before it starts, and both of them are part of the same Batch Job.

Another important feature of batch is that it runs the operations on the server only. In AX2012 and above, this means it will run the operations in CIL (.Net), which improves performance. That's nice!

I find it interesting to know that the AOS kernel looks for new tasks to fire up every 60 seconds, and it will spawn two threads per CPU core. Also, any AOS can run batch tasks, so you can define dedicated AOSes for batch and perhaps cluster multiple AOSes together. If your AOSes are spread over multiple timezones you can have the same AOS handle users on daytime and batch on nighttime, and visa versa for your other AOSes. Pretty neat!

Back in 2011 Tariq Bell wrote this nice post on how Batch works under the hood. It explains the steps and parts of the code that is involved in the processing of batch tasks. Having a flowchart while reading the textual outline is always helpful.

Understanding how batch works in AX is especially helpful when investigating why tasks remain in Executing Status. In my upgrade projects, batch is heavily involved for running the upgrade scripts, and understanding batch is essential.

From the flowchart above, you see there are tables holding the tasks (Batch) and the "header" of the tasks (BatchJob). There is also a table, BatchGlobal, to help the framework keep track of any tasks running across multiple AOSes. The potential constraints and dependencies between tasks in a job is persisted in the BatchConstraints-table. These are just a few of the elements involved in this framework, as there are quite a few elements involved.

The Premier Field Engineers blogged about tweaking the batch threads settings for improved performance for AX2009, but I expect the post to be just as interesting for those who want to learn for AX2012.

Feel free to reuse the flowchart. Thanks for reading!

Wednesday, October 1, 2014

My 2nd Dynamics AX MVP Award

I don't like to brag, it makes me feel awkward, but today I received the so much awaited email from Microsoft announcing my renewal of the MVP Award for Dynamics AX. I feel like I've been holding my breath for the last weeks and trying not to get my hopes too high. I am humble and thankful for the Award and the support by the AX community, fellow MVPs and Microsoft employees. From the bottom of my heart, THANK YOU!

The last year has been increasingly interesting and fun, and at times perhaps a bit exhausting. The number of requests from AX professionals have increased and I try my best to give valuable answers to those who reach out. I love to share the knowledge and my passion for Dynamics AX, and I do have high hopes for Dynamics AX as a great platform for building the best ERP solution out there.

A lot of the AX giants out there have been extremely valuable for me when I've been searching forums and blogs for AX content. Being an MVP has made it a lot easier to get to know more people who share the same passion and glow for AX. The AX community is still growing at a fast pace, and all the time new amazing professionals are discovering Dynamics AX, which is great. This makes me humble for having the pleasure of getting this Award, and I probably can't emphasize that enough.

I aim to keep pushing what I hope is valuable content to the community, and if you also have a sharing mentality, you should consider create a site or blog and share. At least join our Dynamics AX Community Site.

Thank you for reading, and thank you for your support!

Monday, September 8, 2014

Installing Management Reporter 2012 for AX2012

In this post I will go through the process of installing Management Reporter 2012 using its own Setup experience. I will also add a Data Mart for Dynamics AX 2012. You know you can install Management Reporter 2012 using Dynamics AX Setup, but you can also install it using the regular MR 2012 Setup. The Dynamics AX Setup experience is a bit easier because it hides some options which the regular installer doesn't. Hence this little blog post.

Installing the Management Reporter Server

Start by downloading, extracting and running Management Reporter Setup.

When the binaries are installed, choose to let the Configuration Console open after pressing Finish.

In this example I will opt for only installing the Application Service and the Process Service. I could select the Dynamics AX Data Mart now, but I'll set it up afterwards to keep this guide a bit simpler.

I don't need the Access Database engine, so I'll ignore this warning.

You need to define a service account for the service and I have already setup and defined an account in the Active Directory for this.

I will create a new database so I need to provide the SQL Server Instance, the name of the database, a passphrase for encrypting the content of the MR database and finally the account that will be automatically added as first admin of the Management Reporter installation.

By default, the Application Service will listen on 4712 by default. I'll stick with the defaults for the sake of everyone's sanity.

Grab yourself a cup of coffee and let it configure.

 When it is done, you have two running services but no configured applications yet. From File menu, choose Configure and run the Configuration wizard.

Choose the proper application, in my example the Dynamics AX 2012 Data Mart.

It will need information on how to connect to the right AOS, so provide the name and port for the AOS, and also the SQL Server instance and database name.

It also wants to know where it can put the Data Mart database. If you're running this wizard in the context of a user that can create the necessary database artifacts you can simply let it do so using Windows authentication.

 When it is done, I recommend restarting both services from the Configuration Console.

You will notice a new task pops up in the Task List asking you to enable the integration.
Before doing so, you need to make sure the Management Reporter 2012 service account has the necessary permissions to Dynamics AX, so the recommended step is to add the service account to AX as a user with System Administrator permissions. Not only will it read out financial data, but also information about companies and users.

When clicking the "Enable Integration" you will be prompted for some credentials. If you user context has sufficient permissions to enable the integration, you can choose Use Windows authentication.

When the integration is enabled, it will immidiately start collecting data from the Dynamics AX database. The next important step is to install the client and apply the Management Reporter license. Otherwise, only two users will be able to play with the reporting.

Installing the client is super easy. The only thing you may need to know is what to put in as server connection for the client.

When you have installed the client, open the designer and open the Tools and Registration to paste in your license key.

Now that was pretty easy and straight forward, wasn't it? :-)

Saturday, August 30, 2014

Permission error when setting up additional SSRS instances

Perhaps you read my article on setting up additional SSRS instances and now you notice each time the Report Server instance is restarted it causes reports to throw a weird error like this:

"Error while setting server report parameters. Error message: The DefaultValue expression for the report parameter 'AX_CompanyName' contains an error:"

You can reproduce this by restarting the SSRS instance and try run any report. The first time it fails, but the second run goes through ok. Now, we can't have that can we?

Back in 2012 Microsoft blogged about this issue and the solution so far is to make some minor adjustments on one of the configuration files.

So let's try that and see if that fixes it.

Head over to the location where the rssrvpolicy.config file is and see if you can open and edit it. My preference is NotePad, but any text editor will obviosuly work.

This file may contain a lot of content, so try search for "Report_Expressions_Default_Permissions" and you should only find one occurrence of that text. Now the value you're looking for is "Execute" and you want to change that to "FullTrust".

Save the file and restart SSRS. Head back to AX and observe the reports run perfectly on the first run!

Good job!