VMware Aria Automation offers a polyglot programming environment, with JavaScript, PowerShell and Python as programming languages. We have the opportunity to expand this range to include additional programming languages. This post shows how the TypeScript programming language can be seamlessly used.

Integrate TypeScript Programming Language

Here are the necessary actions required for this:
  1. testTypeScript: Contains the TypeScript source code.
  2. getActionAsText: JavaScript that reads the TypeScript source code.
  3. handler: JavaScript to call the transpilation with the TypeScript compiler.
  4. transpileTypeScript: JavaScript to transpile TypeScript code.
  5. invokeTypeScript: JavaScript to invoke TypeScript code.
  6. invokeMixScript: JavaScript to invoke TypeScript code with Aria Automation objects.
To get the source code move your mouse pointer into the source code section and press on the upper right side the copy button. This copies the source code to the clipboard and now it can be pasted and saved e.g. in an editor on the target system. Or download it from my GitHub account.

Save TypeScript Code as Action

In the first step we build an action, who we called testTypeScript, in which we store the TypeScript code to be executed. We simply save the TypeScript source code here, regardless if errors are displayed or not.

function addNumbers(a: number, b: number) {
  return a + b;
}

function main() {
  const sum: number = addNumbers(10, 15);
  System.log("Sum of the two numbers is: " + sum);
}

main();

vmware aria automation typescript code stored as action

As we can see, this code doesn't do anything special. It is a standard example that is used in many internet sources. Only two numbers are added and the result is returned. Not imaginative, but sufficient for our example.

Read Action as Text

In the second step we build a JavaScript action, called getActionAsText. As the name suggests, the content of an action is read out as text. With this action we read the TypeScript code and pass it for transpilation or execution.


Here all actions are filtered, first by the module name and then by the action name, to return the content of the specified action.

Transpile TypeScript to JavaScript

To provide the functionality of transpiling, we need to provide the TypeScript compiler with an interface in the third step.

/**
 * Handler to call the transpilation.
 *
 * @param {string} typescriptSource
 * @param {string} actionName
 * @returns {Properties}
 *
 * @author Stefan Schnell <mail@stefan-schnell.de>
 * @license MIT
 * @version 0.6.0
 *
 * Checked with Aria Automation 8.13.1 and 8.16.0
 */

exports.handler = (context, inputs, callback) => {

  "use strict";

  const os = require("node:os");
  const fs = require("node:fs");
  const typescript = require('./typescript.js');

  // Compiler options for transpilation to JavaScript for the Rhino engine
  const compilerOptions = {
    module: typescript.ModuleKind.CommonJS,
    target: typescript.ScriptTarget.ES5,
    strictNullChecks: true
  };

  // Checking the TypeScript code and error output if present
  const inMemoryFileName = `${os.tmpdir()}/${inputs.actionName}.ts`;
  try {
    fs.accessSync(fileName, fs.constants.F_OK);
    fs.rmSync(fileName, { force: true } );
  } catch (exception) {
  }

  const host = typescript.createCompilerHost(compilerOptions);
  host.writeFile(inMemoryFileName, inputs.typescriptSource);
  const program =
    typescript.createProgram([inMemoryFileName], compilerOptions, host);
  const emitResult = program.emit();
  const allDiagnostics =
    typescript.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
  let diagnosticMessages = "";
  if (allDiagnostics.length > 0) {
    allDiagnostics.forEach(diagnostic => {
      if (diagnostic.file) {
        let { line, character } = typescript.getLineAndCharacterOfPosition(
          diagnostic.file,
          diagnostic.start
        );
        let message = typescript.flattenDiagnosticMessageText(
          diagnostic.messageText,
          "\n"
        );
        diagnosticMessages +=
          `[Line ${line + 1}, Column ${character + 1}] ${message}\n`;
      } else {
        diagnosticMessages += typescript.flattenDiagnosticMessageText(
          diagnostic.messageText,
          "\n"
        );
      }
    });
  }

  // Transpile TypeScript to JavaScript
  const javascriptSource = typescript.transpileModule(
    inputs.typescriptSource,
    compilerOptions
  );

  callback(undefined, {
    status: "done",
    javascriptSource: javascriptSource.outputText,
    diagnosticMessages: diagnosticMessages
  });

};

The handler has two essentially blocks. The first defines the compiler options and the second do the transpilation. The block for checking the TypeScript code should only help to find errors that have occurred.

vmware aria automation zip package of transpile typescript to javascript

Now the handler with the TypeScript compiler and the necessary declaration files must be packed into a zip package. This package can now be loaded into VMware Aria Automation and provided as an action.

Hint: To get the TypeScript compiler and the definition files, download the latest release of TypeScript.

Hint: With the current release 5.3.2 of TypeScript is it better to add also the declaration files lib.dom.d.ts, lib.scripthost.d.ts and lib.webworker.importscripts.d.ts. Transpiling works perfectly without these files, but otherwise a diagnostic message occurs, that one of these files are missing.

vmware aria automation script transpile typescript to javascript

vmware aria automation properties transpile typescript to javascript

To be able to easily use this transpilation, in conjunction with TypeScript code stored in an action, here an action that makes this possible.

/**
 * Transpiles TypeScript code.
 *
 * @function transpileTypeScript
 *
 * @param {string} in_moduleName - Module name which contains the action
 * @param {string} in_actionName - Action name which contains the TypeScript code
 * @param {boolean} in_showOutput - Flag whether output should be displayed
 * @returns {string} result - Result of TypeScript code transpilation
 *
 * @author Stefan Schnell <mail@stefan-schnell.de>
 * @license MIT
 * @version 0.6.0
 *
 * @example
 * var in_moduleName = "de.stschnell";
 * var in_actionName = "testTypeScript";
 * var in_showOutput = false;
 *
 * Checked with Aria Automation 8.13.1 and 8.16.0
 */

var _transpileTypeScriptNS = {

  main : function(moduleName, actionName, showOutput) {

    if (typeof showOutput !== "boolean") {
      showOutput = false;
    }

    try {

      var typescriptSource = System.getModule("de.stschnell")
        .getActionAsText(moduleName, actionName);
      if (showOutput) {
        System.log(typescriptSource);
      }

      var transpileResult = System.getModule("de.stschnell.typescript")
        .transpileTypeScriptToJavaScript(typescriptSource, actionName);
      if (showOutput) {
        System.log(transpileResult.javascriptSource);
      }

      System.log(transpileResult.diagnosticMessages);

      return transpileResult.javascriptSource;

    } catch (exception) {
      System.log(exception);
    }

  }

};

if (
  String(in_moduleName).trim() !== "" &&
  String(in_actionName).trim() !== ""
) {
  return _transpileTypeScriptNS.main(
    in_moduleName,
    in_actionName,
    in_showOutput
  );
} else {
  throw new Error("in_moduleName or in_actionName argument can not be null");
}

Here an example of a transpilation. In the upper area the TypeScript source code and in the lower area the JavaScript code generated by the transpilation.

2023-11-04 05:01:30.708 +02:00INFO(de.stschnell/transpileTypeScript) 
function addNumbers(a: number, b: number) {
    return a + b;
}

function main() {
  var sum: number = addNumbers(10, 15);
  return "Sum of the two numbers is: " + sum;
}

main();

2023-11-04 05:01:32.087 +02:00INFO(de.stschnell/transpileTypeScript)
function addNumbers(a, b) {
    return a + b;
}
function main() {
    var sum = addNumbers(10, 15);
    return "Sum of the two numbers is: " + sum;
}
main();

Invoke TypeScript

In fourth step the transpiled TypeScript code is executed with the Rhino Engine from VMware Aria Automation. With this action we achieve a seamless embedding of TypeScript.

Hint: For this approach it is necessary to set the system property com.vmware.scripting.javascript.allow-native-object to true or add in the shutter file the org.mozilla.javascript.* package, or a superset of it.

/**
 * Invokes TypeScript code which is stored in an action.
 *
 * @function invokeTypeScript
 *
 * @param {string} in_moduleName - Module name which contains the action
 * @param {string} in_actionName - Action name which contains the TypeScript code
 * @param {boolean} in_showOutput - Flag whether output should be displayed
 * @returns {string} result - Result of TypeScript code execution
 *
 * @author Stefan Schnell <mail@stefan-schnell.de>
 * @license MIT
 * @version 0.6.0
 *
 * @example
 * var in_moduleName = "de.stschnell";
 * var in_actionName = "testTypeScript";
 * var in_showOutput = false;
 *
 * Set com.vmware.scripting.javascript.allow-native-object in the
 * system properties to true.
 *
 * Checked with Aria Automation 8.13.1 and 8.16.0
 */

var _invokeTypeScriptNS = {

  main : function(moduleName, actionName, showOutput) {

    if (typeof showOutput !== "boolean") {
      showOutput = false;
    }

    // var cx = org.mozilla.javascript.ContextFactory.getGlobal().enterContext();
    var cx = org.mozilla.javascript.Context.enter();

    try {

      // Reads the TypeScript source
      var typescriptSource = System.getModule("de.stschnell")
        .getActionAsText(moduleName, actionName);
      if (showOutput) {
        System.log(typescriptSource);
      }

      if (typescriptSource.trim() !== "") {

        // Transpiles the TypeScript to JavaScript
        var transpileResult = System.getModule("de.stschnell.typescript")
          .transpileTypeScriptToJavaScript(typescriptSource, actionName);
        if (showOutput) {
          System.log(transpileResult.javascriptSource);
        }

        if (showOutput) {
          System.log(transpileResult.diagnosticMessages);
        }

        // Executes the JavaScript
        var scope = cx.initStandardObjects();
        var sourceName = "export";
        var lineNumber = 1;
        var securityDomain = null;

        var result = cx.evaluateString(
          scope,
          transpileResult.javascriptSource,
          sourceName,
          lineNumber,
          securityDomain
        );
        if (showOutput) {
          System.log(String(result));
        }

        return String(result);

      }

    } catch (exception) {
      System.log(exception);
    } finally {
      cx.exit();
    }

  }

};

if (
  String(in_moduleName).trim() !== "" &&
  String(in_actionName).trim() !== ""
) {
  return _invokeTypeScriptNS.main(
    in_moduleName,
    in_actionName,
    in_showOutput
  );
} else {
  throw new Error("in_moduleName or in_actionName argument can not be null");
}

Here is the example result of an execution.

vmware aria automation invoke typescript code

Invoke TypeScript with Aria Automation Objects

The final and fifth step shows how TypeScript can also be used in conjunction with Aria Automation objects. The approach is very simple, the transpiled source code is saved as an action and this is then executed. Creation, execution and deletion of the action are done via the REST interface of Aria Automation.

/**
 * Invokes TypeScript code mixed with Aria Automation objects.
 *
 * @function invokeMixScript
 *
 * @param {string} in_userName - Name of the user
 * @param {SecureString} in_password - Password of the user
 * @param {string} in_moduleName - Name of the module which contains the action
 * @param {string} in_actionName - Name of the action
 * @returns {Properties}
 *
 * @author Stefan Schnell <mail@stefan-schnell.de>
 * @license MIT
 * @version 0.6.0
 *
 * Set com.vmware.scripting.javascript.allow-native-object in the
 * system properties to true.
 *
 * Checked with Aria Automation 8.16.0
 */

var _actionActivities = function() {

  this._url = null;
  var fqdn = this.getFQDN();
  if (fqdn !== null) {
    this._url = "https://" + fqdn;
  } else {
    throw new Error("Error at FQDN detection");
  }

  this._httpRestHost = null;
  if (this._url !== null) {
    this._httpRestHost = RESTHostManager.createTransientHostFrom(
      RESTHostManager.createHost("dynamicRequest")
    );
    this._httpRestHost.operationTimeout = 60;
    this._httpRestHost.connectionTimeout = 30;
    this._httpRestHost.hostVerification = false;
    this._httpRestHost.url = this._url;
  } 

  this.bearerToken = null;

};

_actionActivities.prototype = {

  /**
   * Detects the Full Qualified Domain Name (FQDN)
   *
   * @function getFQDN
   *
   * @returns {string} FQDN
   */
  getFQDN : function() {
    var fqdn = "";
    var jvmOpts = java.lang.System.getenv("JVM_OPTS");
    if (jvmOpts !== null) {
      var options = jvmOpts.split(" ");
      options.forEach( function(option) {
        if (option.substring(0, 19) === "-Dvco.app.hostname=") {
          fqdn = option.substring(19, option.length);
        }
      });
    }
    if (fqdn !== "") {
      return fqdn;
    } else {
      return null;
    }
  },

  /**
   * Retrieves Bearer token
   *
   * @function retrieveBearerToken
   *
   * @param {string} username - Name of the user
   * @param {string} password - Password of the user
   */
   retrieveBearerToken : function(username, password) {

    if (this._url === null) {
      return;
    }

    var httpRestHost = this._httpRestHost.clone();

    var jsonLogin = {
      "username": username,
      "password": password
    };
    var login = JSON.stringify(jsonLogin);

    var request = httpRestHost.createRequest(
      "POST",
      "/csp/gateway/am/api/login?access_token",
      login
    );
    request.contentType = "application/json";

    var response = request.execute();
    if (response.statusCode === 200) {
      var oRefreshToken = JSON.parse(response.contentAsString);
      var refreshToken = "{\"refreshToken\":\"" +
        oRefreshToken.refresh_token + "\"}";
      request = httpRestHost.createRequest(
        "POST",
        "/iaas/api/login",
        refreshToken
      );
      request.contentType = "application/json";

      response = request.execute();
      if (response.statusCode === 200) {
        var oBearerToken = JSON.parse(response.contentAsString);
        this.bearerToken = oBearerToken.token;
      } else {
        System.error("Error at retrieving bearer token");
      }
    }

  },

  /**
   * Creates a new action
   *
   * @function createAction
   *
   * @param {string} moduleName - Module name that should contain the action
   * @param {string} actionName - Name of the action to be created
   * @param {string} code - Source code in the action
   * @returns {string} actionId
   */
  createAction : function(moduleName, actionName, code) {

    var httpRestHost = this._httpRestHost.clone();

    var jsonScript = {
      "name": actionName,
      "module": moduleName,
      "version": "0.1.0",
      "description": "Test",
      "script": code
    };
    var script = JSON.stringify(jsonScript);

    var request = httpRestHost.createRequest(
      "POST",
      "/vco/api/actions?uniqueName=false",
      script
    );

    request.contentType = "application/json";
    request.setHeader("Accept", "application/json");
    request.setHeader("Authorization", "Bearer " + this.bearerToken);

    var response = request.execute();
    if (response.statusCode === 201) {
      var jsonResponse = JSON.parse(response.contentAsString);
      return jsonResponse.id;
    } else {
      System.error("Error creating action");
      throw new Error("Error creating action");
    }

  },

  /**
   * Runs an action
   *
   * @function executeAction
   *
   * @param {string} actionId
   * @returns {string} executionId
   */
  executeAction : function(actionId) {

    var httpRestHost = this._httpRestHost.clone();

    var jsonBody = {
      "async-execution": false
    };
    var body = JSON.stringify(jsonBody);

    var request = httpRestHost.createRequest(
      "POST",
      "/vco/api/actions/" + actionId + "/executions",
      body
    );
    request.contentType = "application/json";
    request.setHeader("Accept", "application/json");
    request.setHeader("Authorization", "Bearer " + this.bearerToken);

    var response = request.execute();
    if (response.statusCode === 200) {
      var jsonResponse = JSON.parse(response.contentAsString);
      return jsonResponse["execution-id"];
    } else {
      System.log("Error at execution\n" + response.contentAsString);
    }

  },

  /**
   * Gets the action run logs
   *
   * @function getActionLog
   *
   * @param {string} executionId
   * @returns {string} log
   */
  getActionLog : function(executionId) {

    var httpRestHost = this._httpRestHost.clone();

    var request = httpRestHost.createRequest(
      "GET",
      "/vco/api/actions/" + executionId + "/logs?maxResult=2147483647"
    );
    request.setHeader("Accept", "application/json");
    request.setHeader("Authorization", "Bearer " + this.bearerToken);

    var response = request.execute();
    if (response.statusCode === 200) {
      return response.contentAsString;
    } else {
      System.log("Error at get log");
    }

  },

  /**
   * Deletes an action
   *
   * @function deleteAction
   *
   * @param {string} actionId
   */
  deleteAction : function(actionId) {

    var httpRestHost = this._httpRestHost.clone();

    var request = httpRestHost.createRequest(
      "DELETE",
      "/vco/api/actions/" + actionId
    );
    request.setHeader("Authorization", "Bearer " + this.bearerToken);

    var response = request.execute();
    if (response.statusCode !== 200) {
      System.log("Error at deletion");
    }

  }

};

function main(userName, password, moduleName, actionName) {

  if (
    String(userName).trim() !== "" &&
    String(password).trim() !== "" &&
    String(moduleName).trim() !== "" &&
    String(actionName).trim() !== ""
  ) {

    var actionActivities = new _actionActivities();
    actionActivities.retrieveBearerToken(userName, password);
    if (actionActivities.bearerToken === null) {
      throw new Error("No Bearer token available");
    }

    var code = System.getModule("de.stschnell").getActionAsText(
      moduleName,
      actionName
    );
    if (String(code).trim() === "") {
      throw new Error("The action contains no code");
    }

    var transpiledCode = System.getModule("de.stschnell")
      .transpileTypeScriptToJavaScript(code, actionName).javascriptSource;

    var tempActionName = "x" + System.nextUUID().replace(/-/g, "_");
    var actionId =
      actionActivities.createAction("temp", tempActionName, transpiledCode);

    var action = [];
    while (action.length === 0) {
      System.sleep(500);
      var actions =
        System.getModule("com.vmware.library.action").getAllActions();
      action = actions.filter( function(item) {
        return item.module.name === "temp" && item.name === tempActionName;
      } );
    }

    var executionId = actionActivities.executeAction(actionId);

    actionActivities.deleteAction(actionId);

    System.sleep(2500);

    var actionLog = actionActivities.getActionLog(executionId);

    return { "executionId": executionId, "actionLog": actionLog };

  } else {
    throw new Error(
      "userName, password, moduleName or actionName argument can not be null"
    );
  }

}

// Main
return main(in_userName, in_password, in_moduleName, in_actionName);

Here is an example that uses the system object.

let multilineString: string = `
  This is a multiline string.
  In TypeScript, we use backticks.
  It makes the code more readable.
`;
System.log(multilineString)

enum CardinalDirections {
  North,
  South,
  East,
  West
}

let currentDirection = CardinalDirections.North;
System.warn("Current Direction is: " + currentDirection);

When transpiling from TypeScript to JavaScript, the use of the system object is output as an error. As a result, we get a JSON object that contains the outputs via the system object.

vmware aria automation invoke typescript code with aria automation objects

The following approach reads the single entries of the JSON object.

var userName = "holadmin";
var password = "VMware1!";
var moduleName = "de.stschnell";
var actionName = "testTypeScript";

var result = System.getModule("de.stschnell").invokeMixScript(
  userName,
  password,
  moduleName,
  actionName
).actionLog;

var jsonResult = JSON.parse(result).logs;

Object.keys(jsonResult).forEach( function(item) {
  System.log(jsonResult[item].entry["short-description"]);
});

Conclusion

This approach shows that it is very easy to integrate TypeScript directly into VMware Aria Automation, and also save the source code. On this way we can use TypeScript seamlessly in VMware Aria Automation. We can also use it to cover two development scenarios. On the one hand we can use the transpiler to validate our TypeScript code before, or transform it to JavaScript. On the other hand we can also execute the TypeScript code directly, with full use of all Aria Automation objects.



This site is part of blog.stschnell.de