PowerShell y los comandos de inicio en Dynamics AX 2012 (PS-III)

En los primeros artículos de esta serie, aprendimos las bases de PowerShell en general, y cómo utilizar librerías de cmdlets extra, incluyendo las que vienen con Microsoft Dynamics AX 2012 y permiten muchas acciones que facilitan la administración y el mantenimiento. PowerShell - jaestevan.com

Pero para poder utilizar PowerShell en nuestros sistemas DevOps reales, debemos unirlo todo y utilizar tanto cmdlets específicos, como la potencia de PowerShell para poder ejecutar todos los pasos necesarios en un despliegue típico de AX, y en muchas otras tareas de mantenimiento.

Empecemos con PowerShell estándar

Por ejemplo podemos utilizar los comandos base de PowerShell para buscar si en el sistema existe un AOS con un determinado nombre, e iniciarlo si no lo está, de esta forma (probablemente no es la mejor forma, pero resulta ilustrativa para este caso):

powershell-aos-service

$AxAOSName = "MicrosoftDynamicsAX"

$svcAOS = Get-Service AOS60* | Where { $_.DisplayName.EndsWith($AxAOSName) } -ErrorAction SilentlyContinue
if (-not ($svcAOS.Length -gt 0))
{
    throw "AOS service " + $AxAOSName + " can not be found."
}

Write-Host "AOS:" $svcAOS.DisplayName

if ($svcAOS.Status -ne 'Running')
{
    Start-Service $svcAOS -PassThru
}

Continue Reading…

[AX TIP] Errores Interop CLR y nivel de transacciones

Hace un tiempo publiqué un artículo explicando las buenas prácticas recomendadas para el manejo de excepciones CLR y cómo mostrarlas correctamente en el InfoLog de Dynamics AX. Sin embargo olvidé entonces un detalle importante que hay que tener en cuenta al capturar estas excepciones, que es el nivel de transacciones en la base de datos (TTS Level).

En el siguiente código de ejemplo, nuestro try..catch NO funcionará. Si se produce un error en el try, por ejemplo, si el método get_Content() no existe en el objeto en tiempo de ejecución, la ejecución del código terminará directamente sin mostrar nada en el InfoLog, lo que no es muy deseable.

Esto es debido a que, para poder capturar una excepción CLR, el nivel de transacciones debe ser 0. O lo que es lo mismo, no podemos capturar errores del Interop dentro de una transacción. Para que este código funcione tendremos que re-escribirlo para que el manejo de ese objeto CLR genérico se realice antes de abrir la transacción.

CLRObject       obj;


ttsBegin;  // ttsLevel + 1

info(strFmt("TTS level: %1", appl.ttsLevel())); // TTS level: 1

try
{
    obj = response.get_Content();
    if (!CLRInterop::isNull(obj))
    {
        // ...
    }
}
catch(Exception::CLRError) // Este catch nunca se ejecutará porque ttsLevel > 0
{
    error(AifUtil::getClrErrorMessage());
}

ttsCommit; // ttsLevel - 1

En realidad, y debido al alto riesgo de que se produzcan errores CLR que deben capturarse y procesarse de forma especial, el manejo de objetos CLR debe encapsularse siempre en clases o métodos específicos donde quede claro su funcionamiento y aislado el riesgo de problemas.

Desarrollo en Dynamics AX 7: La nueva arquitectura

Mientras esperamos que se publique la versión definitiva de “El Nuevo” AX o Dynamics AX 7, podemos empezar a comprender las bases del sistema utilizando la información publicada y la Preview pública. Antes de entrar a analizar los detalles de la nueva versión, que son muchos y nos va a llevar bastante tiempo, hay que comprender las bases que han motivado todos esos cambios y que nos van a permitir comprender mejor la nueva forma de desarrollar, que aunque utiliza herramientas parecidas a las versiones anteriores, veremos como son totalmente diferentes.

Model Store vs Local Repository

En versiones anteriores el código y los metadatos se almacenaban en ficheros de capa (AX 2009) y después en bases de datos, ya sea una base de datos dedicada (AX 2012 R2+) o en la propia base de datos de negocio (AX 2012 RTM). Este simple hecho condicionada las herramientas que podíamos utilizar para trabajar con este código y metadatos. Esto es, programar, crear nuevos objetos, hacer pruebas, compilar, etc…

En AX 7 el código se almacena en ficheros en el disco, como en la mayoría de lenguajes modernos, donde tenemos la versión de los ficheros que nosotros estamos modificando. No en ficheros XPO como en ficheros anteriores, sino en un formato nuevo del que hablaremos más adelante, y estos nuevos ficheros pueden abrirse desde Visual Studio utilizando Proyectos y Soluciones propias de este editor para organizar nuestros desarrollos.

Modelos vs Paquetes

Aparte de este cambio en el almacenaje y gestión de los metadatos, en la nueva versión X++ es un lenguaje completo y nativo de la plataforma .NET, y por tanto el compilador y la ejecución son la misma que en los lenguajes .NET. El resultado más evidente de este cambio es que el código compilado (que en la versión anterior se guardaba, también, en base de datos) genera ensamblados (librerías DLL) que se ejecutan en CLR, como el resto de la plataforma .NET.

packages models projects

Continue Reading…

[HOWTO] Impersonar otro usuario para ejecutar código en X++

A pesar de que esto no es habitual y va contra unas cuentas Best Practices, en algunos casos muy específicos podemos necesitar ejecutar un framento de X++ (una llamada a un método) en el contexto de un usuario que no es el que está ejecutando la sesión actual, ya sea una sesión de cliente o un proceso Batch. Para ejecutar una llamada a un método con un usuario concreto, usamos este código:

// ...

// Usuario que va a ejecutar el método
UserId postingUserId = parms.PostingUserId != '' ? parms.PostingUserId : curUserId();

new RunAsPermission(postingUserId).assert();

// Llamada a: MyClass.myClassMethod(methodParm1, methodParm2)

// BP Deviation Documented
runAs(postingUserId, classNum(MyClass), 'myClassMethod', [methodParm1, methodParm2]);

CodeAccessPermission::revertAssert();

// ...

Insisto en que esto no es habitual y tiene, como siempre que hacemos estas cosas, algunos riesgos de seguridad. Pero dejo aquí el código para buscarlo la próxima vez que lo necesite 😉

En busca de la tabla perdida… herencia de tablas en AX 2012

Si alguien ha creado o mantiene un desarrollo que acceda directamente a la base de datos de Microsoft Dynamics AX 2012 (por ejemplo, algún sistema externo de Business Intelligence), se habrá encontrado con el fenómeno de la “tabla fantasma“. Hay muchos casos, pero analicemos por ejemplo la tabla OMOperationUnit. Esta tabla existe en el AOT, podemos explorarla desde Dynamics AX y ver los datos que contiene, por lo que esos datos deben estar almacenados en alguna parte. Sin embargo la tabla no existe en la base de datos SQL Server subyacente. Entonces, ¿Dónde están los datos?

En primer lugar hay que fijarse que este fenómeno sucede siempre en tablas heredadas o que soportan herencia, por ejemplo:

Tablas heredadas en Microsoft Dynamics AX 2012

Como la tabla existe y almacena datos en el AOT, es obvio que la tabla debe existir en la base de datos. Una buena herramienta de la que disponemos en X++ es la función getSQLStatement que podemos usar en variables de tipo buffer o en consultas, de esta forma:

static void Job43(Args _args)
{
    OMOperatingUnit tabla;

    select generateOnly tabla;

    info(tabla.getSQLStatement());
}

Esta función nos devuelve el SQL que se enviará a la base de datos para obtener los datos que se requieren desde X++. Aquí tenemos la explicación al problema de la tabla fantasma:

Consulta SQL de tablas heredadas en Microsoft Dynamics AX 2012

Si analizamos bien el SQL obtenido, incluso si lo probamos directamente contra la base de datos SQL Server, descubrimos la estructura interna que el AOT ha desarrollado en la base de datos para almacenar los datos de las tablas heredadas:

Consulta SQL de tablas heredadas en Microsoft Dynamics AX 2012

En este caso, la tabla heredada obtiene los datos de tablas totalmente diferentes mediante algunos filtros. Dependiendo del tipo de tabla heredada, las tablas físicas pueden tener otra forma, pero en cualquier caso de esta forma obtenemos una consulta válida que podemos utilizar para acceder a los datos desde un sistema externo, o podemos crear una vista con esa consulta para simular la existencia de la tabla, si fuera necesario.

HOWTO: Información de Active Directory desde X++

Tenía este Job por aquí desde una vez que tuve que utilizarlo para activar/desactivar usuarios automáticamente en Microsoft Dynamics AX (probado en AX 2009) cuando éstos se desactivaban en el dominio. Lo publico para que no se me pierda y por si alguien pudiera sacarle partido :).

static void JAEE_IterateActiveDirectoryUsers(Args _args)
{
    str                 computer = new xSession().clientComputerName();
    xAxaptaUserManager  mgr = new xAxaptaUserManager();
    xAxaptaUserDetails  usr;
    container           doms;
    int                 d, u;
    str                 dom, login, name, sid, email;
    ;

    // iterate AD domains
    doms = mgr.enumerateDomains(computer);
    for (d = 1; d <= conlen(doms); d++)
    {
        dom = conpeek(doms, d);
        setprefix(dom);
       
        // iterate AD domain users
        usr = mgr.enumerateDomainUsers(dom);
        for (u = 0; u < usr.getUserCount(); u++)
        {
            if (usr.isUserEnabled(u) && !usr.isUserExternal(u))
            {
                // get information from AD
                login = usr.getUserLogin(u);
                name = usr.getUserName(u);
                sid = usr.getUserSid(u);
                email = usr.getUserMail(u);

                // stuff happens here, you can compare AD data with AX User info
               
                info(strfmt("%1 - %2 - %3 - %4 - %5", dom, login, name, email, sid));
            }
        }
    }
}

Descarga