Tip around System.log() for Big Workflows with Scriptable tasks and actions

  1. Introduction
  2. Concern
  3. Possible Solution
    1. Workflows
    2. Actions


It’s quite evident that vRO Workflows can grow huge for major provisioning tasks like hardware deployments, vApp deployments or tenant provisioning etc. just to name a few. And in a big environment, such workflows can come in large numbers and may be coupled with various other workflows quite generally for eg. using Nested WFs.


Now, the real pain starts when you have to debug them. for Debugging, developers relies mostly on System.log() or System.debug() to know about the statuses and variable values etc. up to a particular point, which is really great.

If I talk about myself, I even uses a start and completion log for every Scriptable task in a workflow. This always pinpoints to the scriptable task that was started but couldn’t complete due to any reason and therefore couldn’t print the end log. Let me explain it. There is a workflow (Create a VM) and there is a scriptable task (Get All VM Names). In this scriptable task, I would add some thing like

System.log("\"Get all VM Names\" script started");

and at the end of this script

System.log("\"Get all VM Names\" script completed");

Imagine this in a very large workflow, this can really help. But I knew this needs to be improved. Sometimes, you changes the content of a scriptable task and its definition changes, so update it’s name -> Get All VM Names to Get All VM Names and IPs. In such cases, you have to update those start and end log statements. And I hate that!

Possible Solution

I referred to the vRO community (link) and found one interesting way out.


We can use this,

System.log("\""+System.currentWorkflowItem().getDisplayName()+"\"  script started");
System.log("\""+System.currentWorkflowItem().getDisplayName()+"\"  script completed");

This will automatically print the updated name of the scriptable task.

vRO Workflow Screenshot with scriptable tasks | Image by Author

You can also print the item# if you want, using this command



Now, this is a little tricky. We can’t do the same for actions. However, if we consider them as a part of a workflow, which means they will act similarly as an item and obviously will have a item number, then we can print their start and end cycle. We can do so by adding the below mentioned script in your actions.

System.log("\"" + System.currentWorkflowItem().getDisplayName() + "\" action started");
System.log("\"" + System.currentWorkflowItem().getDisplayName() + "\" action ended");
A vRO Action | Image by Author
vRO Workflow Screenshot with scriptable tasks and action | Image by Author

Here, we added a condition just to avoid printing Ended "" script if you try to run your actions solely.

A vRO Action | Image by Author

Automated Installation of vRO Plugin using Python

  1. Prerequisites
  2. Script
  3. Procedure

If you are looking for a way to quickly install plugins in your vRealize Orchestrator (.dar or .vmoapp files), you can try this Python (.py) script which uses vRO ControlCenter API at its core.



# -*- coding: utf-8 -*-
Automated Installation of vRO Plugin 1.0
It is a python script to allow quick installation of plugins inside vRealize Orchestrator.
__author__ = "Mayank Goyal, Harshit Kesharwani"
import base64
import csv
import email
import json
import os
import re
import smtplib
import time
import requests
import urllib3
from pathlib import Path
from string import Template
def deployPlugin(vroUrl, username, password, filename):
"""Function to deploy plugin. Calls sub-functions like updateInstallPlugin() & finishInstallPlugin()."""
url = (f"https://{vroUrl}/vco-controlcenter/api/plugins/installation")
files = {'file': open(filename, 'rb')}
headers = {'Accept': 'application/json'}
response = requests.post(url, auth=(username, password), headers=headers, files=files, verify=False)
statusCode = response.status_code
if (statusCode == 201):
responseData = response.json()
payloadToUpdate = (responseData)
pluginInstallationID = responseData['id']
pluginName = payloadToUpdate.get("plugins")[0].get("name")
print(f"pluginInstallationID : {pluginInstallationID}")
updateStatus = updateInstallPlugin(vroUrl, username, password, pluginInstallationID, payloadToUpdate)
if updateStatus:
finishStatus = finishInstallPlugin(vroUrl, username, password, pluginInstallationID,pluginName)
return finishStatus
return False
print(f"[ERROR]: Failed to execute Deploy request. Error code : {statusCode}")
return False
def updateInstallPlugin(vroUrl, username, password, pluginInstallationID, payloadToUpdate):
"""Function to update plugin state."""
url = (f"https://{vroUrl}/vco-controlcenter/api/plugins/installation/{pluginInstallationID}")
#payload = "{\"eulaAccepted\":true,\"forceInstall\":true,\"installPluginStates\":[{\"state\":\"NEW_INSTALLED\"}],\"plugins\":[{\"enabled\":true,\"logLevel\":\"DEFAULT\"}]}"
t1 = payloadToUpdate.get("plugins")[0]
t1.__setitem__("enabled", True)
t1.__setitem__("logLevel", "DEFAULT")
payloadToUpdate.__setitem__("plugins", [t1])
t2 = {
"name": str(payloadToUpdate.get("plugins")[0].get("name")),
"newBuild": str(payloadToUpdate.get("plugins")[0].get("buildNumber")),
"newVersion": str(payloadToUpdate.get("plugins")[0].get("version")),
"state": "NEW_INSTALLED"
payloadToUpdate.__setitem__("installPluginStates", [t2])
payloadToUpdate.__setitem__("eulaAccepted", True)
payloadToUpdate.__setitem__("forceInstall", True)
payload = json.dumps(payloadToUpdate)
headers = {'Content-Type': 'application/json'}
response = requests.request("POST", url, auth=(username, password), headers=headers, data=payload, verify=False)
statusCode = response.status_code
if (statusCode == 200):
return True
print(f"[ERROR]: Failed to execute Update request. Error code : {statusCode}")
return False
def finishInstallPlugin(vroUrl, username, password, pluginInstallationID, pluginName):
"""Function to finalize the plugin installation."""
url = (f"https://{vroUrl}/vco-controlcenter/api/plugins/installation/{pluginInstallationID}/finish")
headers = {'Content-Type': 'application/json'}
response = requests.request("POST", url, auth=(username, password), headers=headers, verify=False)
statusCode = response.status_code
if (statusCode == 200):
responseData = response.json()
print(f"Enable plugin name {pluginName}")
changePluginState(vroUrl, username, password, pluginName)
return True
print(f"[ERROR]: Failed to execute Finish request. Error code : {statusCode}")
return False
def changePluginState(vroUrl, username, password, pluginName):
"""Function to change set enabled=true which actually completes the installation process."""
url = (f"https://{vroUrl}/vco-controlcenter/api/plugins/state")
payload = json.dumps([{"enabled": True, "name": pluginName}])
headers = {'Content-Type': 'application/json'}
response = requests.request("POST", url, auth=(username, password), headers=headers, data=payload, verify=False)
statusCode = response.status_code
if (statusCode == 200):
return True
print(f"[ERROR]: Failed to execute changePluginState request. Error code : {statusCode}")
return False
def checkPluginInstallation(vroUrl, username, password, pluginInstallationID):
"""Function to check if installation is completed or not."""
url = (f"https://{vroUrl}/vco-controlcenter/api/plugins/installation/{pluginInstallationID}")
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
response = requests.request("GET", url, auth=(username, password), headers=headers, verify=False)
statusCode = response.status_code
print(f"statusCode : {statusCode}")
if (statusCode == 200):
return True
print(f"[ERROR]: Failed to execute checkPluginInstallation request. Error code : {statusCode}")
return False
if __name__ == '__main__':
#User provided data
vroUrl = input("Enter vRO FQDN: [Eg: fqdn.vro.local] ")
username = input("Enter username: [Tip: try root] ")
password = input("Enter password: [Tip: try root password] ")
filename = input("Enter full path of plugin: [Eg: C:/Users/abcd/Downloads/plugin_file.vmoapp] ")
deployPlugin(vroUrl, username, password, filename)


For demo purpose, I will show the very few steps that you need to install a vRO plugin named JsonPath in an automated fashion.

  • Copy the full path of downloaded plugin with filename itself.
    • C:\Users\mayank\Downloads\cloudblogger tutorial\o11nplugin-jsonpath-1.0.2.vmoapp
  • Run the script and provide values to required inputs
  • Reload vRO ControlCenter, the desired plugin will be already installed there.

Generate vRO Workflow Runs Report in CSV

TL;DR Download the package from here & edit the inputs and run it to get a CSV Report for last 30 days of Workflow Runs in vRealize Orchestrator.

Many a times, we have environments where we want to monitor the executions of vRealize Orchestrator Workflows, like how many times a workflow is run, who executed the workflow, or what was the success rate of a particular workflow, or maybe just want to check how long it take to execute a workflow. I have seen few customers that were looking for similar solutions. So, If you are also looking for a way to achieve something similar, this article could be of some help.

As we know, vRO offers REST API out-of-the-box for all kind of operations. Same is true for getting the facts and figures of its workflow execution. There are various ways to get that kind of information but I find using the catalog-service-controller module to be the quickest for my use-case. Let’s see it in detail below.

Understand the REST API

The catalog-service-controller module is quite a versatile API module in vRO. You can use it to dig-in information for a extensive set of entities. However, the path and method we are mostly interested in today is, GET /api/catalog/{namespace}/{type}.

Namespace = System
type = WorkflowToken

where date30DaysAgo will get its value from getDate30DaysAgo() action and currentDate from getCurrentDateInISOFormat() action available in the vro-package.

Note You can add more states to this query like waiting, canceled, etc. if you are interested in workflow tokens other than completed and failed.

Understand the Code

In the package attached, we have a workflow Workflow Runs Report Generation which will have the code divided in 3 parts. For ease of operation, the workflow is creating a transient REST host using user provided inputs but you can also add your vRO as a REST Host in the inventory and add this REST URL as a REST Operation with dates as variable input. Also, in Part 2/3, only 5 properties are added to the CSV data. You can lookup for additional properties related to a workflow token if you want a more details-heavy report.

//Part 1/3: Invoke Rest Operation

//Part 2/3: Gather data in CSV format
var reportCSV = "Name,Status,Start Date,End Date,Started by\r\n";
if (response.statusCode != 200) { // 200 - HTTP status OK
    System.warn("Request failed with status code " + response.statusCode);
} else {
    var doc = JSON.parse(response.contentAsString);
    System.log("Number of failed or cancelled tokens: " + doc.total);
    for each(var link in doc.link) {
        for each(var attribute in link.attributes) {
            if (attribute.name == "name")
                var wfName = attribute.value;
            if (attribute.name == "state")
                var wfState = attribute.value;
            if (attribute.name == "startDate")
                var wfStartDate = attribute.value;
            if (attribute.name == "endDate")
                var wfEndDate = attribute.value;
            if (attribute.name == "startedBy")
                var wfStartedBy = attribute.value;
        reportCSV += (wfName + "," + wfState + "," + wfStartDate + "," + wfEndDate + "," + wfStartedBy+"\r\n");

//Part 3/3: Send CSV data as a mail attachment

In Part 3/3, we are simply attaching the CSV data as a MIME attachment and send it to the toAddress.

Let’s Run it

  • Import the package available here. If you need help with package import, follow this guide here.
  • Go to workflow Workflow Runs Report Generation and Click Edit
  • Go to the scriptable task and edit these input values.
  • After editing the values, Save it.
  • Click Run.
  • If the workflow executed successfully, you will see in the logs, the mail is being sent to the provided email address.
  • Check your mailbox. If you can’t see the email in your inbox, probably check the email address that you’ve provided or check your SPAM folder.
  • Download the attached CSV file and Open it. You will see a sheet which will be identical to what you have in your vRO Workflow Runs Tab (Performance view OFF). You may also want to sort this sheet on Start Date column in descending manner.

Download vRO Package

That’s it in today’s post. I hope you will like it.

Working with Date and Time in vRO

  1. Intrinsic Date() Class
  2. Parsing a Date
  3. Working with ISO 8601 Format (YYYY-MM-DDTHH:mm:ss.SSSZ)
    1. Get Current Date in ISO complete format
    2. Get the number of seconds since the Unix/ECMAScript Epoch
  4. Working with past and future dates
    1. Get current time in milliseconds
  5. Comparing Dates
  6. Finding the Difference Between Two Dates
  7. Find Execution time of a f(n)
  8. Extras
  9. Summary
  10. References

Manipulation of Date & Time is overwhelmingly difficult. Mostly because there are so many formats, standards and handling techniques. Being a vRO programmer, you can’t run away from this date manipulation, be it for Reports creation, REST Calls, Data fetching or Interacting with other servers. Ask any programmer about their experience handling dates and time zones and they will probably share some war stories. Handling date and time fields is certainly not rocket science but can often be tedious and error-prone.

In this article, we will explore some of the key concepts that are necessary for manipulating date and time values correctly, formats that are convenient for storing DateTime values and transferring them over APIs, and more. You can consider it as a complete guide to understand the DateTime concepts in vRealize Orchestrator using the system-provided classes.

If you are sending and receiving data through a REST API, you will eventually need to convert the date to a string and vice versa because JSON doesn’t have a native data structure to represent DateTime. 


Intrinsic Date() Class

vRO provides a Date() class to satisfy almost all the date and time hunger. It has a constructor that takes variety of inputs to start off. There are various methods and functions take allows quick shape-shifting of date and time. However, It may lack some quirky features that you may look out for. Let’s start off by taking a look at this Date class in a more comprehensive way.

It starts here. You call the constructor and you will get the current date and time. BOOM!🚀

const currentDate = new Date();

If you don’t pass anything to the Date constructor, the date object returned contains the current date and time.

You can then format it to extract only the date part as follows:

const currentDate = new Date();
const currentDayOfMonth = currentDate.getDate();
const currentMonth = currentDate.getMonth(); // Be careful! January is 0, not 1
const currentYear = currentDate.getFullYear();
const dateString = currentDayOfMonth + "-" + (currentMonth + 1) + "-" + currentYear;
//Output = "19-03-2022"

Caution It’s not getYear(), but getFullYear()

If you instead want to get the current time stamp, you can create a new Date object and use the getTime() method.

const currentDate = new Date();
const timestamp = currentDate.getTime();
System.log(timestamp); // 1647678776796

Tip In JavaScript, a time stamp is the number of milliseconds that have passed since January 1, 1970, which is also known as Unix/ECMAScript Epoch format.

You can also take an input of Type Date in workflow or action and handle it in your scripts just like any other Date object.

Parsing a Date

Converting a string to a JavaScript date object is done in different ways.

The Date object’s constructor accepts a wide variety of date formats:

const date1 = new Date("Wed, 27 July 2022 13:30:00");
const date2 = new Date("Wed, 27 July 2022 07:45:00 UTC");
const date3 = new Date("27 July 2022 13:30:00 UTC+05:30");

or we can also use Date.parse(), that will return timestamp as string

var ms = Date.parse("27 July 2022 13:30:00 UTC+05:30"); 
System.log(ms); // 1469605500000 (timestamp)

Note that you do not need to include the day of week because JS can determine the day of the week for any date.

You can also pass in the year, month, day, hours, minutes, and seconds as separate arguments:

const date = new Date(2022, 6, 27, 13, 30, 0);

or you can even pass the Unix Epoch number directly:

const date = new Date(1647678776796);

that means to get the zeroth timestamp i.e. Jan 1st of 1970 UTC+0, pass 0 as a parameter to Date()

const date = new Date(0);

or you can also use System.getDateFromFormat() to convert a string to Date object. Check more here.

d = System.getDateFromFormat("2019-01-01T01:45:00.100", "yyyy-MM-dd'T'HH:mm:ss.SSS");

Working with ISO 8601 Format (YYYY-MM-DDTHH:mm:ss.SSSZ)

Of course, you can always use this specific ISO date format:

const date = new Date("2022-07-27T07:45:00.000Z"); // Fri Sep 02 2022 07:45:00 GMT-0000 (GMT)

Get Current Date in ISO complete format

Many a times, we need Dates in a complete ISO format [YYYY-MM-DDTHH:mm:ss.SSSZ], for making REST calls etc. We can use the toISOString() or toJSON() methods of the Date object to convert the local DateTime to UTC.

const dateFromUI = "12-13-2022";
const timeFromUI = "15:20";
const dateParts = dateFromUI.split("-");
const timeParts = timeFromUI.split(":");
const date = new Date(dateParts[2], dateParts[0]-1, dateParts[1], timeParts[0], timeParts[1]);
const dateISO = date.toISOString();
System.log(dateISO); //2022-12-13T15:20:00.000Z

Caution Not all variations of ISO 8601 format are supported by Date() constructor in vRO.

const date = new Date("2023-05-08T16:58:00Z"); // Invalid Date

Workaround Convert that Invalid String into Date using some simple string operations.

var dateString = "2023-05-08T16:58:34Z";
const dateFromUI = dateString.split("Z")[0].split("T")[0];
const timeFromUI = dateString.split("Z")[0].split("T")[1];
const dateParts = dateFromUI.split("-");
const timeParts = timeFromUI.split(":");
const date = new Date(dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1], timeParts[2]);
const dateISO = date.toISOString();
System.log(dateISO); //2023-05-08T16:58:34.000Z

If you are sure of the format you want to use, it is best to extract individual bits using the JavaScript functions we covered above and create a string yourself.

var currentDate = new Date();
var date = currentDate.getDate();
var month = currentDate.getMonth(); 
var year = currentDate.getFullYear();

We can get the date in MM/DD/YYYY format as

var monthDateYear  = (month+1) + "/" + date + "/" + year;

The problem with this solution is that it can give an inconsistent length to the dates because some months and days of the month are single-digit and others double-digit. This can be problematic, for example, if you are displaying the date in a table column, because the dates don’t line up.

We can address this by using a “pad” function that adds a leading 0.

function pad(n) {
    return n<10 ? '0'+n : n;

Now, we get the correct date in MM/DD/YYYY format using:

var mmddyyyy = pad(month + 1) + "/" + pad(date) + "/" + year;

If we want DD-MM-YYYY instead, the process is similar:

var ddmmyyyy = pad(date) + "-" + pad(month + 1) + "-" + year;

Let’s up the ante and try to print the date in “Month Date, Year” format. We will need a mapping of month indexes to names:

var monthNames = [
    "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
var dateWithFullMonthName = monthNames[month] + " " + pad(date) + ", " + year;

It is easy to determine the day of week from 0 (Sunday) to 6 (Saturday). The first day is always Sunday, so let’s add that in:

var daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
//var completeDaysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
ordinalDateWithDayOfWeek = daysOfWeek[currentDate.getDay()] + ", " + ordinalDate;

By now, you might understand how to get bits of information out of dates and how to pad them. Now, let’s create an ISO format from scratch like I have done here (contains +00:00 instead of Z as per my requirement).

function getCurrentDateInISOFormat() {
//Desired Format 2016-06-14T00:00:00.000+03:00
var date = new Date();
System.log("Current Date: " + date.toGMTString());
var yyyy = date.getFullYear();
var mm = (date.getMonth() + 1 < 10 ? "0" : "") + (date.getMonth() + 1);
var dd = (date.getDate() < 10 ? "0" : "") + date.getDate();
var HH = (date.getHours() < 10 ? "0" : "") + date.getHours();
var MM = (date.getMinutes() < 10 ? "0" : "") + date.getMinutes();
var SS = (date.getSeconds() < 10 ? "0" : "") + date.getSeconds();
var milli = "";
if (date.getMilliseconds() < 10)
milli = "00" + date.getMilliseconds();
else if (date.getMilliseconds() < 100 && date.getMilliseconds() > 10)
milli = "0" + date.getMilliseconds();
milli = date.getMilliseconds();
System.log(yyyy + "-" + mm + "-" + dd + "T" + HH + ":" + MM + ":" + SS + "." + milli + "+00:00");
return (yyyy + "-" + mm + "-" + dd + "T" + HH + ":" + MM + ":" + SS + "." + milli + "+00:00");

Get the number of seconds since the Unix/ECMAScript Epoch

var seconds = Math.floor(Date.now() / 1000);

Working with past and future dates

The best way to work and calculate present and future dates is by using Unix Epoch format which is conveniently the number of milliseconds after midnight January 1, 1970 till the given date expressed as a string which is IETF format. Let’s see few examples.

Important It should be noted that the maximum Date is not of the same value as the maximum safe integer (Number.MAX_SAFE_INTEGER is 9,007,199,254,740,991). Instead, it is defined in ECMA-262 that a maximum of ±100,000,000 (one hundred million) days relative to January 1, 1970 UTC (that is, April 20, 271821 BCE ~ September 13, 275760 CE) can be represented by the standard Date object (equivalent to ±8,640,000,000,000,000 milliseconds).

Get current time in milliseconds

// vRO System method
System.getCurrentTime() //1647861957381 
//Javascript method
Date.now() //1647861957381 
var date = new Date(); //allows any Date to be used 
System.log(date.valueOf()); //1647861957381

Lets say you want to fetch the date 4 days later relative to today, you can convert the today’s date in Unix Epoch format and add 4 x 24 x 60 x 60 x 1000 milliseconds to it and you will get a date exactly 4 days ahead with same time of the day, that because you have not changed enough milliseconds to modify the time.

var date = new Date(); //Thu Mar 21 2022 11:42:06 GMT-0000 (GMT)
var frameOfTime = date.valueOf() + (4*24*60*60*1000);
var date = new Date(frameOfTime); //Thu Mar 25 2022 11:42:06 GMT-0000 (GMT)

Now, let’s say you want to go back in time 4 hours back, You will subtract 4 x 60 x 60 x 1000 milliseconds.

var date = new Date(); //Thu Mar 21 2022 11:42:06 GMT-0000 (GMT)
var frameOfTime = date.valueOf() - (4*60*60*1000);
var date = new Date(frameOfTime); //Thu Mar 25 2022 07:42:06 GMT-0000 (GMT)

Comparing Dates

First, we need to create date objects. Fortunately, <, >, <=, and >= all work. So comparing July 19, 2014 and July 18, 2014 is as easy as:

const date1 = new Date("July 19, 2022");
const date2 = new Date("July 28, 2022");
if(date1 > date2) {
    System.log("First date is more recent");
} else {
    System.log("Second date is more recent");

Checking for equality is trickier, since two date objects representing the same date are still two different date objects and will not be equal. Comparing date strings is a bad idea because, for example, “July 20, 2022” and “20 July 2022” represent the same date but have different string representations. The snippet below illustrates the first point:

const date1 = new Date("June 10, 2003");
const date2 = new Date(date1);
const equalOrNot = date1 == date2 ? "equal" : "not equal";

This will output not equal.

This particular case can be fixed by comparing the integer equivalents of the dates (their time stamps) as follows:

date1.getTime() == date2.getTime();

Moreover, vRO is not very good with timezones. So, the best is that we should ignore the user’s time zone and use UTC while creating the date object. There are two ways to do it:

  • Create an ISO formatted date string from the user input date and use it to create a Date object. Using a valid ISO date format to create a Date object while making the intent of UTC vs local very clear.
const userEnteredDate = "12/20/1989";
const parts = userEnteredDate.split("/");
const userEnteredDateISO = parts[2] + "-" + parts[0] + "-" + parts[1];
const userEnteredDateObj = new Date(userEnteredDateISO + "T00:00:00.000Z");
const dateFromAPI = new Date("1989-12-20T00:00:00.000Z");
const result = userEnteredDateObj.getTime() == dateFromAPI.getTime(); // true

This also works if you don’t specify the time since that will default to midnight (i.e., 00:00:00Z):

const userEnteredDate = new Date("1989-12-20");
const dateFromAPI = new Date("1989-12-20T00:00:00.000Z");
const result = userEnteredDate.getTime() == dateFromAPI.getTime(); // true

Remember: If the date constructor is passed a string in correct ISO date format of YYYY-MM-DD, it assumes UTC automatically.

  • JavaScript provides a neat Date.UTC() function that you can use to get the UTC time stamp of a date. We extract the components from the date and pass them to the function.
const userEnteredDate = new Date("12/20/1989");
const userEnteredDateTimeStamp = Date.UTC(userEnteredDate.getFullYear(), userEnteredDate.getMonth(), userEnteredDate.getDate(), 0, 0, 0);
const dateFromAPI = new Date("1989-12-20T00:00:00.000Z");
const result = userEnteredDateTimeStamp == dateFromAPI.getTime();
System.log(result); //true

Finding the Difference Between Two Dates

A common scenario you will come across is to find the difference between two dates.

We discuss two use cases:


Convert both dates to UTC time stamp, find the difference in milliseconds and find the equivalent days.

const dateFromAPI = "2022-02-10T00:00:00.000Z";
const now = new Date();
const datefromAPITimeStamp = (new Date(dateFromAPI)).getTime();
const nowTimeStamp = now.getTime();
const microSecondsDiff = Math.abs(datefromAPITimeStamp - nowTimeStamp);
// Math.round is used instead of Math.floor to account for certain DST cases
// Number of milliseconds per day =
//   24 hrs/day * 60 minutes/hour * 60 seconds/minute * 1000 ms/second
const daysDiff = Math.round(microSecondsDiff / (1000 * 60 * 60  * 24));
System.log(daysDiff); //2231


Note: We have a non-standard format. Read the API doc to determine if this means 12 Oct or 10 Dec. Change to ISO format accordingly.

const birthDateFromAPI = "12/10/1989";
const parts = birthDateFromAPI.split("/");
const birthDateISO = parts[2] + "-" + parts[0] + "-" + parts[1];
const birthDate =  new Date(birthDateISO);
const today = new Date();
var age = today.getFullYear() - birthDate.getFullYear();
if(today.getMonth() < birthDate.getMonth()) {
if(today.getMonth() == birthDate.getMonth() && today.getDate() < birthDate.getDate()) {

Find Execution time of a f(n)

You can use these logics to get more purposeful result. You can calculate the execution time of a function you just created and may optimize it.

// To test a function and get back its return
function printElapsedTime(fTest) {
var StartTime = Date.now(),
vReturn = fTest(),
EndTime = Date.now(),
difference = EndTimeStartTime
System.log("Elapsed time: "+difference+" milliseconds") //5001 milliseconds
return vReturn
var yourFunctionReturn = printElapsedTime(sort)
function sort(){ // your function
const array1 = [1, 30, 4, 21, 100000];


vRO does provide various methods to represent date and time in various formats out-of-the-box. Let’s have a look on their output.

var date = new Date(Date.UTC()); //Mon Jan 01 1900 00:00:00 GMT-0000 (GMT)
System.log(date.toString()); //Fri Aug 23 1999 14:53:51 GMT-0000 (GMT)
System.log(date.toTimeString()); //14:53:51 GMT-0000 (GMT)
System.log(date.toLocaleTimeString()); //2:53:51 PM GMT
System.log(date.toLocaleDateString()); //January 6, 2009
System.log(date.toDateString()); //Tue Jan 06 2009
System.log(date.valueOf()); //1455062400000
System.log(date.toUTCString()); // Wed, 10 Feb 2016 00:00:00 GMT


  • Date and time in JavaScript are represented with the Date object. We can’t create “only date” or “only time”: Date objects always carry both.
  • Months are counted from zero (yes, January is a zero month).
  • Days of week in getDay() are also counted from zero (that’s Sunday).
  • Dates can be subtracted, giving their difference in milliseconds. That’s because a Date becomes the timestamp when converted to a number.
  • Use Date.now() to get the current timestamp fast.


Quickly remove Clutter from vRO Logs


vRO provides great bit of logs out-of-the-box, but the problem is, it’s not aesthetically pleasing. Frankly, these long trials of data and time [2022-03-14 02:33:03.144 -04:00INFO] annoys me a lot. If you too don’t want that annoyance or you want to show these logs to someone in most tidiest way, or maybe you simply want to preserve an action results or workflow execution, then probably you can use this trick. Let’s see how I did it.

I think the simplest way would be to find a Regex that removes it all the clutter in one go.


  • After execution of a workflow or action, copy the logs and open Notepad++.
  • Press CTRL + H. It will open Replace Tab.
  • In Find what field copy the below mentioned RegEx and In Search Mode, enable Regular Expression and tick Wrap around.

for workflows



for actions

.+?(?=:)+.+?(?=:)+.+?(?=:)+:(00|30)+(INFO|WARNING|DEBUG|ERROR)+([^)]+)\)+( +?|\t)(?=(([^"]*"){2})*[^"]*$)
  • Click Replace All.

That’s it.


Try yourself at Regexr.com

for Workflows: regexr.com/6haum

for Actions: regexr.com/6hauv

Build a Notification bot with Google Chat for vRealize Orchestrator

Important To use bots in Google Chat, you must use a work or school account.


How about building a Chatbot that brings your vRealize Orchestrator services and resources right into Google Chat, letting users get vital bits of information without leaving the conversation. That would be great, right? Why Chat you may ask if you already have Gmail? I would say because Chat is more intuitive, simple, integrated and fun-to-watch😉. And it doesn’t require any SMTP configuration to be done in vRO.

Potential Use-cases

  • Notifications of successful Workflow Executions on Google Chat Team Spaces
  • Error Notifications and auto-assignments to team member using @mention
  • Daily Reports and Validations on Team Spaces
  • and so many possibilities…

Using Incoming webhooks

Incoming webhooks let you send asynchronous messages into Google Chat from applications that aren’t bots themselves. For example, you can configure vRealize Orchestrator to notify personnel on Google Chat when a particular server goes down. Incoming webhooks serve as a quick and easy way to integrate with existing workflows such as these without the overhead of writing entire bot applications. This is the path for low-effort integrations that provide information feeds or alerts. Let’s dive into the whole process of building a chatbot using webhooks that triggers via vRealize Orchestrator.

Step 1: Register the incoming webhook

  • Open Google Chat in your browser.
  • Create a new space or Go to the space to which you want to add a bot.
  • From the menu at the top of the page, select Manage webhooks.
  • Under Incoming Webhooks, name the new webhook ‘vRO Notifications’ and provide an avatar URL, click SAVE.

Tip Get the 128x128px avatar for your bot here.

Avatar URL: https://user-images.githubusercontent.com/7029361/157434341-d1cd1706-4072-4f32-abe1-2f0459e93879.png

  • Copy the URL listed next to your new webhook and keep it handy. We will use it later on.
  • Click outside the dialog box to close.

Step 2: Import the Node.js script action in vRO

  • Download the vRO package from here.
  • Import this package in your vRO.
  • Check action named sendNotificationsToGoogleChat. This action will be used to send notifications to Google Chat.
  • Run the action and pass webhookURL without https://chat.googleapis.com and a test message. (If you need to find the URL again, go to the Google Chat space, select Manage webhooks, and then copy the URL associated with your incoming webhook.)

If everything is OK, you should see this message in your space.

Step 3: Use sendNotificationsToGoogleChat action in Workflows

To utilize this action, we have to attach it in workflows for automatic notifications. Let’s take a scenario where we want to notify the team when the workflow is failed.

  • Create a new workflow Demo Workflow – Chatbot and add a scriptable task that will fail.
  • Add a Default error handler item and an scriptable task and the action sendNotificationsToGoogleChat.
  • In this new scriptable task, copy-paste this JS script with webhookPath that you have copied from your space.
webhookPath = "/v1/spaces/........";
message = "Important message for <users/all>: The workflow *";
message += workflow.rootWorkflow.name;
message += "* has been failed";
  • Add a error variable and add it to Exception Handling in all the workflow items
  • Time to run, Click Run and wait for it to fail.
  • Check your space. There should receive a message.

Tip Find this workflow in the package for testing purposes.

Limits and considerations

As you configure webhooks, take note of these limits and considerations:

  • If only select organizational units (OUs) in your domain have bots enabled, incoming webhooks created in a space will fail with the following error:
  "error": {
    "code": 403,
    "message": "The caller does not have permission",
    "status": "PERMISSION_DENIED"
  • Incoming webhooks do work in direct messages, but only when all users have bots enabled.
  • Because Manage webhooks is only available from a web browser, webhooks must be set up from the Chat web app. Webhooks are not configurable from the Chat mobile app.

Text Message Formatting Guide

This section describes how to create simple messages that appear inline as if typed by a user. The is basically the message that we are passing to our vRO action.

Basic unformatted messages

Any message in Google Chat is represented as a JSON object. A basic message that specifies just a simple plaintext body could look like the following:

{  'text': 'Your workflow "Create Egde Gateway" has successfully executed'}

If posted into Google Chat (e.g. via incoming webhooks), it would look like the following:

Using formatted text in messages

Google Chat lets you add basic formatting to the message text, including bold, italic, and strikethrough. To format text, wrap it with the following symbols:

Italic_ (underscore)_hello_hello
Monospace` (backquote)`hello`hello
Monospace block“` (three backquotes)“`

For example, consider the following JSON:

{'text' : 'Your workflow *has successfully executed*!\nCustomer created with id: _A21347-VPC_'}

This places a message like this into the Chat space:

If you include a plain link URL in your message text, such as http://example.com/foo, Google Chat uses this as the link text and automatically hyperlinks that text to the specified URL.

To provide alternate link text for your link, use the following syntax:

<https://cloudblogger.co.in|my link text>my link text

The pipe and link text are optional, so that <https://example.com/foo> and https://example.com/foo are equivalent.

Messages that @mention specific users

A bot can @mention a user in a message, providing the user ID in the following syntax:


This string will be substituted with an @mention of the specified user. For example, consider the following JSON:

{"text": "Hello <users/113352777658254482749> from vRO!}

This payload would produce a result like the following:

To determine the USER_ID for a user, In Google Chrome, Click

and Click Inspect on the chat page, and point out to the user,

you will get the USER-ID as data-member-id as shown here.

Important Change user/human/113352777658254482749 to users/113352777658254482749 while pasting it into your code.

Messages that @mention all users

You can use the special user ID all to @mention all the human users in a space. For example:

{    "text": "Important message for <users/all>: Code freeze starts at midnight tonight!"}

Coming Soon -> Card messages

Google Chat supports more complex UIs in the form of cards that can give much more insights of our infrastructure.

A simple example card:

Import a Certificate to the Orchestrator Trust Store using PEM-encoded file

vRealize Orchestrator server must be able to verify their identity, to communicate with other servers (vCenter Server, relational database management system (RDBMS), LDAP, Single Sign-On, and other servers) securely. For this purpose, you might need to import the SSL certificate of the remote entity to the Orchestrator trust store.

To trust a certificate, you can import it to the trust store either by establishing a connection to a specific URL, or directly as a PEM-encoded file.

Import from URL or
proxy URL
The URL of the remote server:
https://your_server_IP_address or your_server_IP_address:port
Import from filePath to the PEM-encoded certificate file.

In this post, we will focus on how to import a certificate using PEM-encoded file. For URL based import, simply use the Library Workflows.

Step 1: Generate PEM file


Find the fully qualified domain name of the server to which you want Orchestrator to connect over SSL.


  • Log in to the Orchestrator Appliance over SSH as root.
  • Run a command to retrieve the certificate of the remote server.
openssl s_client -connect host_or_dns_name:secure_port
  1. a If you use a nonencrypted port, use starttls and the required protocol with the openssl command.
openssl s_client -connect host_or_dns_name:port -starttls smtp
  • Copy the text from the -----BEGIN CERTIFICATE----- to the -----END CERTIFICATE----- tag to a text editor and save it as a file in your local system.

Step 2: Import a PEM-encoded file

We can use either of the following ways for this purpose.

  • Control Center
  • vRO Library Workflow

via Control Center

  • Log in to Control Center as root.
  • Go to the Certificates page.
  • On the Trusted Certificates tab, click Import and select the Import from a PEM-encoded file option.
  • Browse to the certificate file and click Import.

via vRO Workflow

  • Log in to vRO Portal.
  • Go to Workflows under Library > Configuration > SSL Trust Manager.
  • Run workflow Import a trusted certificate from a file
  • Browse to the certificate file and click Run.
  • The workflow should be successfully completed.


You have successfully imported a remote server certificate to the Orchestrator trust store. You can check an entry for the corresponding server in the Control Center Certificates Tab or try running the workflow related to the server for which the certificate was imported. You won’t see this error anymore.

vRODoc – Convert vRO Actions to JSDoc Pages

    1. Introduction
    2. What is vRODoc?
    3. How to get started?
    4. Steps to follow
    5. Future Scope
      1. Originally posted on LinkedIn


    It’s always been hard to find a way to document vRO Workflows whether working on large VMware Automation environments or be it your personal projects. I was quite anxious about it as well and tried to find some easy way to do it.

    Then, one day I found a PS script by Jose Cavalheri & Michael Van de gaer where they were trying to get JS actions and configuration elements out of an unzipped vRO Package. It was quite helpful and I used it as a building block for my project that I will be talking about today. I also used export-package mechanism by Burke Azbill and some bits of vROIDE by Garry Hughes.

    So before we start, let me tell you that if you want to make this functionality to work in your project, you should think of moving your code to vRO actions as much as possible.

    vRO Actions are much more flexible in terms of their usage outside of vRO. As you might know, vRO uses XML based approach to save both workflows and actions. However, due to the varied nature of workflows that can contain almost anything in any format makes them convoluted. Actions on the other hand, not only have a defined scope (takes inputs and return one output) but also much easier to use in vRO as well as they provide code reusability. I know, Not everything in vRO can be and should be converted to vRO actions BUT THAT’S OK. We can deal with it. I would recommend to move to action based approach for anyone working on vRO.

    Ultimately, Our objective is to document most of our code and custom APIs that we have created in form of actions.

    What is vRODoc?

    It is mostly a PowerShell script that connects with your vRealize Orchestrator to fetch a package that contains all your action modules and action items, intelligently add JSDoc annotation to those action items and convert them into html pages which can be presented as a web-based code documentation. See a live example here. You can also add other JSDoc comments to your actions. Learn more about JSDoc comments here.

    How to get started?

    Please understand this flow to get started.

    FlowChart of vRODoc

    Steps to follow

    A Quick demo on how to create a Github Pages based website that documents all your vRO actions.

    • Create a package in vRO which will be used to fetch the code-to-be-documented.
    • Using REST, add action modules to your package using this API
    PS> Install-Script -Name vRODoc
    • Edit local copy of your vrodoc_script.ps1 to set your environment parameters
    • Execute this ps1 file. It may take few minutes depending on package size.
    • In your export path, you will notice 1 .zip file and 1 folder with your package-name.
    • Inside package folder, a folder “Actions” will be created with all the converted JSDoc annotated vRO Actions.
    • By now, you have successfully created your website. Inside package folder, go to docs folder and open index.html

    Now, its time to push the content of docs folder to your repository (can be Github.com, Gitlab .com or Gitlab Self-Hosted) – I will take example of Github.com

    • Assuming the repo already exists in your github account, push it to your repo
    • Go to Settings > Pages and select Branch: main and folder/: docs and wait for few minutes for website to load on Github Pages

    If you reading this line, I hope you like this article. This whole process executes from a Powershell script that can be scheduled nightly or something on one of your machine to see the latest documentation next day.

    Future Scope

    I am also working on making it executable from Github or Gitlab runners. That could be tricky and highly environment specific but will give it a try soon. Stay tuned.

    Originally posted on LinkedIn


    How to unlock AD User account using vRO


    Imagine you got a locked AD account and you don’t have access to Active Directory or don’t have rights to unlock your account but luckily have access to vRO or your colleague has access to the same vRO where your account’s AD is connected or maybe you just want to check and unlock an AD user account programmatically in your use-case (for eg. some service accounts that get locked every time you try to run something), What you should do? Just run a simple vRO script.


    Create action and add input of type AD:User and paste 👇 script & execute it.

    // Input AD:User
    return user;


    • Create a new action and copy-paste the above script.
    • Add input User of type AD:User.
    • Save the action and Run it.
    • Select the user that you want to unlock and Click Run.

    The action should execute successfully and your locked account is unlocked now.

    Other attributes

    • Never expire account: user.setAttribute("accountExpires", NewDate);
    • Never expire password: user.setAttribute("userAccountControl", 66048);
    • Set email address: user.setAttribute("mail", strEmail);

    How to publish your PowerShell script to PSGallery online

    You ever created a PS script and wanted it to share. What best place would it be other than the PowerShell Gallery itself!

    Let’s go through this article to know how I listed my own PS script on PSGallery.

    • Click Create and provide Key Name = script_name and glob pattern = *
    • Now, I have my script ready with me. However, before uploading the script, I must add some metadata to my script like Author, Description, Version etc.
    • For that, I have used New-ScriptFileInfo method. I put the values as per my need and run them inside Powershell console.
    $Parms = @{
      Path = "C:\Users\mayank.goyal1\Downloads\scriptInfo.ps1"
      Version = "2.1.0"
      Author = "Mayank Goyal"
      Description = "This script converts vRO Package directly into JSDoc website by connecting to vRO. Requires nodejs and jsdoc module installed. Create new files *.html & *.js directly from vRO with JSDoc annotation. It consumes a package name which should exist in vRO with all the Actions you want to document and creates .js files with JSDoc Annotations."
      ProjectUri = "https://github.com/imtrinity94/vRODoc"
    New-ScriptFileInfo @Parms
    Get-Content -Path C:\Users\mayank.goyal1\Downloads\scriptInfo.ps1
    • This creates a new file C:\Users\mayank.goyal1\Downloads\scriptInfo.ps1. Opening this file show me this content.
    .VERSION 2.1.0
    .GUID 7202df73-3b8c-4616-adeb-b6d9b97a63c7
    .AUTHOR Mayank Goyal
    .PROJECTURI https://github.com/imtrinity94/vRODoc
     This script converts vRO Package directly into JSDoc website by connecting to vRO. Requires nodejs and jsdoc module installed. Create new files *.html & *.js directly from vRO with JSDoc annotation. It consumes a package name which should exist in vRO with all the Actions you want to document and creates .js files with JSDoc Annotations. 
    • Copy this file’s content and put it on top of your original script.
    • Now, it’s time to publish our file. Use Publish-Script command along with file path and API key that I have handy already.
    Publish-Script -Path "C:\Users\mayank.goyal1\Downloads\vRODoc.ps1" -NuGetApiKey oy2hxalemh46n4ghty*****************************64 -Repository PSGallery
    • This should have worked, but it didn’t. Got this error no matter from where I run it.

    Failed to publish script ‘vRODoc’: ‘The underlying connection was closed: An unexpected error occurred on a send.

    • The first problem might be that my system runs TLS11 instead of TLS12, to fix this run the following command:
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12

    The second problem might be that my PowerShellGet Module is not up to date. To fix this I run the following command:

    Install-Module PowerShellGet -Force -AllowClobber -Scope CurrentUser
    • Restart PS terminal and run this command again. This time it worked.

    BINGO! It’s published for anyone to use.