MSDyn365FO. How to playback a task recording from X++.

s-l300

Warmup of a VM is an old issue and everyone tries to find a workaround. It is critical for demo VMs that are turned off most of the time and you don’t want to demo a product that is slow as hell.

Well-known workaround is to manually open all the forms you are going to use but it’s boring and time-consuming task. One of the yammer users came up with an idea to use task recording and here is an implementation in its dirtiest way (calling private methods on object without any validation) that is good enough for demo anyway, so feel free to improve this code:

class WarmUpRunnableClass
{
    public static void main(Args _args)
    {
        var currentRecording = SysBPMHelper::LoadLocalizedRecordingFromLCS(80244103); //BpmLineId here.

        Args args = new Args();
        args.name(formstr(SysTaskRecorderPane));

        FormRun formRun = ClassFactory.formRunClass(args);
        formRun.init();
        formRun.run();
        formRun.loadRecording(currentRecording);
        formRun.maintainExistingRecording();
        formRun.playToStep(100000);
        formRun.wait();
    }
}

This runnable class could be executed from URL, so you can add it to Windows Task Scheduler and execute it on the VM start automatically.

80244103 is a task recording id that is hardcoded here for simplicity. Previously I recorded it and saved to BPM. You can improve this code and add some smarties to grab it from BPM using search by keyword or any other logic you like.

For those who think that hardcode is good enough to get this number you need to go to LCS, open BPM and find task recording you want to run. PLID value from URL is a number you need:

PLID.png

 

Advertisements

MSDyn365FO. How to switch from IIS Express to IIS on development VM.

All new environments starting from PU12 are using IIS Express in debug mode and often due to random issues we have to restart it using “Restart IIS Express” option in the Dynamics 365 menu. There are multiple discussions on community and yammer forums regarding these issues and Joris de Gruyter mentioned that it is possible to switch back to IIS if you have admin access (environment deployed to customer’s or partner’s own Microsoft Azure subscription), but did not explain how 🙂

All we need to do is to edit DynamicsDevConfig.xml file that could be found in ‪K:\AosService\PackagesLocalDirectory\Bin folder (drive letter could be different for your VM). Change RuntimeHostType value from IISExpress to IIS and that’s all.

DynamicsDevConfig

You may find same file under %userprofile%\Documents\Visual Studio 2015\Settings\ , however, Visual Studio rolls back any changes to RuntimeHostType if value is different to value in \AosService\PackagesLocalDirectory\Bin\DynamicsDevConfig file, so don’t try to change it for individual user.

D365FO. “The authentication process was not successful. Please contact your system administrator.” error during Connect to Lifecycle Services setup.

As we know, there are hundreds of task recordings available out of the box in LCS library. Before first use, system administrator should authorize D365FO to access LCS and often it is not possible due to the old bug. When system administrator goes to Settings -> Task recorder -> Play recording as guide -> Open from Lifecycle Services and clicks “Select the Lifecycle Services library” “Connect to Lifecycle Services” dialog pops up.

Connect to Lifecycle Services

There is a link “Click here to connect to Lifecycle Services” and when administrator clicks on it he may receive “The authentication process was not successful. Please contact your system administrator.” error.

It’s is caused by empty value of “LCS.GettingStartedLibrary” parameter in web.config file.

To fix it go to K:\AosService\WebRoot and edit web.config file. Find LCS.GettingStartedLibrary tag and set value to any integer that is greater than 0, save file and restart IIS. Don’t forget to backup web.config before doing any changes.

WebConfigLCSGettingStarted

Probably LCS should put BPM library id there, however, it works with any random positive integer as well. To find BMP project id, go to LCS, open your project and click on “Business process modeler” tile. Then select library that contains task recording.  In the URL of the page grab value of FWK parameter

LCSBPMURL.png

Hopefully, this bug will be fixed soon and we won’t have to play with web.config file anymore.

Advanced cross-reference search.

Cross-references search

Cross-reference is one of the best tools we have in AX that is used by developers daily, however, not everyone is using it for 100%.

Recently, I was asked how to find cross-references for a kernel method that is not defined on a table, because you cannot right click on it 😊   And that’s a good question, often we want to find if there is a call to doUpdate or doInsert somewhere.

In AX 2012 go to Tools -> Cross-reference -> Names and filter by table or method name and then click Used by.

SalesLine.DoUpdate_Names

SalesLine.DoUpdate_UsedBy

Using this form, we can find usage of CRL types as well, for example, Microsoft.Dynamics.IntegrationFramework.  It is defined in AOT under references node, however, from there you cannot find any references.  Names would show you everything!

CLRTypeUsage

What about D365FOE?

As we know, it’s a bit different here. Cross-references data is moved to DYNAMICSXREFDB database.

DYNAMICSXREFDB

And it makes perfect sense because you need to pay for each GB of DB space in Production.

Using next SQL statement, we can find usage of any object. For example, Microsoft.Dynamics.IntegrationFramework :

SELECT
	sourceName.[Path] as sourcePath,
	targetName.[Path] as targetPath,
	[Kind],
	[Line],
	[Column],
	sourceModule.[Module] as sourceModule,
	targetModule.[Module] as targetModule
	FROM dbo.[References]
	INNER JOIN dbo.[Names] sourceName
		ON dbo.[References].[SourceId] = sourceName.[Id]
	INNER JOIN dbo.[Modules] sourceModule
		ON sourceName.[ModuleId] = sourceModule.[Id]
	INNER JOIN dbo.[Names] targetName
		ON dbo.[References].[TargetId] = targetName.[Id]
	INNER JOIN dbo.[Modules] targetModule
		ON targetName.[ModuleId] = targetModule.[Id]
	WHERE targetName.[Path] like '%Microsoft.Dynamics.IntegrationFramework%'

CLRTypeUsage_D365FOE

Where Kind is:

    /// <summary>
    /// Types of Cross References
    /// </summary>
    public enum CrossReferenceKind
    {
        /// <summary>
        /// Type not specified. Used for queries
        /// </summary>
        Any = 0,

        /// <summary>
        /// Indicates that the reference is a Method Call
        /// </summary>
        MethodCall = 1,

        /// <summary>
        /// Type reference
        /// Indicated that the type is used (variable and field declaration, attributes, function return type, etc)
        /// </summary>
        TypeReference = 2,

        /// <summary>
        /// Interface implementation
        /// Indicates that the source entity is implementing this interface
        /// </summary>
        InterfaceImplementation = 3,

        /// <summary>
        /// Class Extended
        /// Indicates that the source entity is extending this class or interface
        /// </summary>
        ClassExtended = 4,

        /// <summary>
        /// Test Call
        /// Indicates that the source entity (test) directly or indirectly calls an application method.
        /// </summary>
        TestCall = 5,

        /// <summary>
        /// Property
        /// Indicates that the source entity has a certain property.
        /// </summary>
        Property = 6,

        /// <summary>
        /// Attribute reference
        /// Indicated that an Attribute is used
        /// </summary>
        Attribute = 7,

        /// <summary>
        /// Test Helper Call
        /// Indicates that the source entity is a test helper.
        /// </summary>
        TestHelperCall = 8,

        /// <summary>
        /// Metadata or code Tag reference
        /// Indicates that the source tag is used on a metadata element, class or a method or a line of code.
        /// </summary>
        Tag = 9,
    }

Let’s try doUpdate:

DoUpdate_UsedBy_D365FOE

As we can see, result is different to AX 2012, where we could search for an individual table, now all Common methods have reference to Common.

 

D365FOE. FormHasMethod extension for form extension methods.

Recently I have seen multiple people asking how to check if form has method added by extension at run-time.  For form methods we can use Global::formHasMethod but it does not work with form extensions. I advised people to use new metadata API to do this and finally community user Axaptus wrote the code!

I tweaked it a bit to ignore method’s name case as AX does and to exclude private methods. Also I used it to extend standard formHasMethod method

/// <summary>
/// The class <c>Global_Extension</c> contains extension methods for the <c>Global</c> class.
/// </summary>
[ExtensionOf(classStr(Global))]
public static final class Global_Extension
{
    static boolean formHasMethod(FormRun fr, IdentifierName methodName)
    {
        boolean ret = next formHasMethod(fr, methodName);

        if (!ret)
        {
            ret = Global::formExtensionHasMethod_IM(fr, methodName);
        }

        return ret;
    }

    private static boolean formExtensionHasMethod_IM(FormRun _formRun, IdentifierName _methodName)
    {
        if (!_formRun || !_methodName)
        {
            return false;
        }

        try
        {
            System.Object[] extensions = Microsoft.Dynamics.Ax.Xpp.ExtensionClassSupport::GetExtensionsOnType(_formRun.GetType(), true);

            if (extensions)
            {
                System.Type    formRunExtensionType;
                System.Reflection.MethodInfo    methodInfo;
                
                //extension methods are always static
                var  bindingFlags = BindingFlags::Public | BindingFlags::Static | BindingFlags::IgnoreCase;

                for (int i = 0; i < extensions.Length; i++)
                {
                    formRunExtensionType = extensions.GetValue(i);

                    var methodsInfo = formRunExtensionType.GetMethods(bindingFlags);

                    for (int n = 0; n < methodsInfo.get_Length(); n++)
                    {
                        methodInfo = methodsInfo.getValue(n);
                        if (methodInfo.Name == _methodName)
                        {
                            return true;
                        }
                    }
                }
            }
        }
        catch (Exception::CLRError)
        {
            error(CLRInterop::getLastException().ToString());
        }

        return false;
    }
}

Extending standard method has its pros and cons. From one side it will slow down execution of standard code that calls it when method does not exist, but it’s a rare case. From another side it allows you to reuse standard code without changing it and it could be handy in various places where AX looks for a method on a form.

Source code is available on GitHub