A side-by-side reference for engineers working across both environments. Covers the ES5 subset used by vRealize Orchestrator alongside equivalent PowerShell patterns — variables, functions, objects, arrays, control flow, error handling, strings, types, and JSON.
ECMAScript 5 (ES5) is the fifth edition of the JavaScript language
standard, published in December 2009. It is the scripting engine embedded in
vRealize Orchestrator (VRO), which uses Mozilla Rhino to execute
JavaScript. ES5 predates let, const, arrow functions,
classes, and modules — all features introduced in ES6 (2015) and later.
Understanding ES5's constraints is essential for writing correct VRO scripts.
PowerShell is Microsoft's task-automation shell and scripting language, built on the .NET runtime. It is the dominant automation language for Windows infrastructure and is also cross-platform since PowerShell 7. Its object pipeline, cmdlet ecosystem, and .NET type system make it fundamentally different from ES5 in philosophy, even when solving the same problems.
let,
const, => arrow functions, class,
Promise, or Symbol. If you are accustomed to modern
JavaScript, treat VRO as a constrained environment where every variable is
declared with var.
| Dimension | JavaScript ES5 | PowerShell |
|---|---|---|
| Runtime | Browser / Node / VRO (Rhino) | .NET CLR / .NET Core |
| Typing | Dynamic, weakly typed | Dynamic, optionally strongly typed |
| Object model | Prototype-based inheritance | .NET class hierarchy + PSObject wrapper |
| Variable prefix | None — var x |
$ sigil — $x |
| Statement terminator | Semicolon (optional via ASI) | Newline or semicolon |
| Pipeline | No native pipeline | Object pipeline is central |
| Null keyword | null / undefined |
$null |
| Boolean literals | true / false |
$true / $false |
ES5 has only one variable declaration keyword — var — and its scope
is function-scoped, not block-scoped. Variables declared inside an
if block or for loop are visible throughout the entire
containing function. PowerShell uses a $-prefixed name and has
explicit scope modifiers ($script:, $global:,
$local:).
| Concept | JavaScript ES5 | PowerShell |
|---|---|---|
| Declare + assign | var x = 10; |
$x = 10 |
| Declare only | var x; → undefined |
$x = $null (convention) |
| Multiple assignment | var a = 1, b = 2; |
$a = 1; $b = 2 |
| Constant (ES5) | No const — use naming convention |
Set-Variable -Name X -Option Constant -Value 10 |
ES5 hoists var declarations to the top of their containing
function. The declaration is moved but the assignment is not, so the variable
exists but holds undefined before its assignment line is reached.
PowerShell has no hoisting — reading an unset variable returns $null.
// JavaScript ES5 — hoisting console.log(x); // undefined (not an error) var x = "hello"; console.log(x); // "hello" JS (ES5)
# PowerShell — no hoisting Write-Host $x # returns empty string ($null), no error by default $x = "hello" Write-Host $x # "hello" PowerShell
// JavaScript ES5 — var leaks out of blocks into the function function demo() { if (true) { var msg = "inside if"; } console.log(msg); // "inside if" — var is function-scoped } JS (ES5)
# PowerShell — variables in child scopes are not visible in parent function Demo { $msg = "inside function" } Demo Write-Host $msg # empty — $msg is local to Demo # Use $script: or $global: to promote scope function DemoGlobal { $global:msg = "promoted" } DemoGlobal Write-Host $msg # "promoted" PowerShell
var inside a function creates an implicit global.
In VRO this can silently pollute the workflow execution context across action calls.
Always declare with var.
ES5 functions are first-class objects — they can be assigned to variables, passed
as arguments, and returned from other functions. PowerShell has both traditional
function blocks and scriptblock objects ({ }),
which serve a similar first-class role.
// 1. Function declaration — hoisted, callable before definition function greet(name) { return "Hello, " + name; } // 2. Function expression — NOT hoisted var greet = function(name) { return "Hello, " + name; }; // 3. Immediately Invoked Function Expression (IIFE) — private scope (function() { var secret = "not global"; })(); JS (ES5)
# 1. Named function function Greet($name) { return "Hello, $name" } # 2. Scriptblock assigned to variable (like a function expression) $greet = { param($name) "Hello, $name" } &$greet "World" # invoke with & operator # 3. No direct IIFE equivalent — use a scriptblock and invoke immediately &{ $secret = "not global" } PowerShell
ES5 has no native default parameter syntax. The idiomatic workaround uses the
|| operator.
// ES5 default parameter pattern function connect(host, port) { host = host || "localhost"; port = port || 443; return host + ":" + port; } JS (ES5)
# PowerShell — native default parameter values function Connect($host = "localhost", $port = 443) { "${host}:${port}" } PowerShell
ES5 closures capture variables by reference, not by value. This is a frequent
source of bugs inside loops. PowerShell scriptblocks also close over variables,
but GetNewClosure() can force a value capture.
// Classic ES5 closure-in-loop bug var fns = []; for (var i = 0; i < 3; i++) { fns.push(function() { return i; }); } console.log(fns[0]()); // 3 — not 0! // Fix: wrap in an IIFE to capture value at each iteration var fns = []; for (var i = 0; i < 3; i++) { (function(n) { fns.push(function() { return n; }); })(i); } console.log(fns[0]()); // 0 ✓ JS (ES5)
# PowerShell — GetNewClosure() captures current variable values $fns = @() for ($i = 0; $i -lt 3; $i++) { $fns += { $i }.GetNewClosure() } &$fns[0] # 0 ✓ PowerShell
In ES5, objects are created with literal syntax or constructor functions and are
linked via a prototype chain. In PowerShell, the nearest equivalent is the
hashtable (@{}) for key-value data, or
[PSCustomObject] when you need dot-notation property access and
pipeline compatibility.
// Object literal var server = { name: "web-01", ip: "10.0.0.1", port: 443, active: true }; console.log(server.name); // dot notation console.log(server["ip"]); // bracket notation JS (ES5)
# Hashtable $server = @{ Name = "web-01" IP = "10.0.0.1" Port = 443 Active = $true } $server.Name # dot notation (hashtable quirk — works but not standard) $server["IP"] # bracket notation # PSCustomObject — better for pipeline / property enumeration $server = [PSCustomObject]@{ Name = "web-01" IP = "10.0.0.1" Port = 443 Active = $true } $server.Name # "web-01" PowerShell
| Operation | JavaScript ES5 | PowerShell |
|---|---|---|
| Get all keys | Object.keys(obj) |
$ht.Keys or $obj.PSObject.Properties.Name |
| Check key exists | obj.hasOwnProperty("key") |
$ht.ContainsKey("key") |
| Delete property | delete obj.key |
$ht.Remove("key") |
| Iterate key/value | for (var k in obj) { obj[k] } |
foreach ($k in $ht.Keys) { $ht[$k] } |
| Add property at runtime | obj.newKey = "val" |
$obj | Add-Member -NotePropertyName "NewKey" -NotePropertyValue "val" |
// ES5 constructor + prototype function Server(name, ip) { this.name = name; this.ip = ip; } Server.prototype.toString = function() { return this.name + " (" + this.ip + ")"; }; var s = new Server("web-01", "10.0.0.1"); console.log(s.toString()); // "web-01 (10.0.0.1)" JS (ES5)
# PowerShell — Add-Member to attach methods to a PSCustomObject $s = [PSCustomObject]@{ Name = "web-01"; IP = "10.0.0.1" } $s | Add-Member -MemberType ScriptMethod -Name "Describe" -Value { "$($this.Name) ($($this.IP))" } -Force $s.Describe() # "web-01 (10.0.0.1)" PowerShell
ES5 arrays are zero-indexed, dynamically sized objects. ES5 introduced powerful
functional array methods: forEach, map,
filter, and reduce. PowerShell arrays (@())
are .NET arrays under the hood; the pipeline provides functional equivalents
via cmdlets.
| Operation | JavaScript ES5 | PowerShell |
|---|---|---|
| Literal | var a = [1, 2, 3]; |
$a = @(1, 2, 3) or $a = 1, 2, 3 |
| Length | a.length |
$a.Count or $a.Length |
| First / Last | a[0] / a[a.length-1] |
$a[0] / $a[-1] |
| Append | a.push(4) |
$a += 4 |
| Remove last | a.pop() |
No direct equivalent — rebuild: $a = $a[0..($a.Count-2)] |
| Slice (copy) | a.slice(1, 3) |
$a[1..2] |
| Join to string | a.join(", ") |
$a -join ", " |
| Reverse | a.reverse() (in-place) |
[array]::Reverse($a) |
| Sort | a.sort() |
$a | Sort-Object |
| Find index | a.indexOf(val) |
[array]::IndexOf($a, $val) |
var nums = [1, 2, 3, 4, 5]; // forEach — iterate, no return value nums.forEach(function(n) { console.log(n); }); // map — transform each element, returns new array var doubled = nums.map(function(n) { return n * 2; }); // [2, 4, 6, 8, 10] // filter — returns new array of matching elements var evens = nums.filter(function(n) { return n % 2 === 0; }); // [2, 4] // reduce — accumulate to a single value var sum = nums.reduce(function(acc, n) { return acc + n; }, 0); // 15 // some / every — boolean tests nums.some(function(n) { return n > 4; }); // true nums.every(function(n) { return n > 0; }); // true JS (ES5)
$nums = 1, 2, 3, 4, 5 # ForEach-Object — iterate (pipeline) $nums | ForEach-Object { Write-Host $_ } # ForEach-Object as map $doubled = $nums | ForEach-Object { $_ * 2 } # Where-Object — filter $evens = $nums | Where-Object { $_ % 2 -eq 0 } # Measure-Object — sum / count / min / max $sum = ($nums | Measure-Object -Sum).Sum # some equivalent $any = ($nums | Where-Object { $_ -gt 4 }).Count -gt 0 # every equivalent $all = ($nums | Where-Object { $_ -le 0 }).Count -eq 0 PowerShell
+= on arrays creates a new array on every call because
.NET arrays are fixed-size. For large collections inside a loop, use
[System.Collections.Generic.List[object]] and its
.Add() method instead.
// JavaScript ES5 if (status === "ok") { console.log("all good"); } else if (status === "warn") { console.log("warning"); } else { console.log("error"); } JS (ES5)
# PowerShell if ($status -eq "ok") { Write-Host "all good" } elseif ($status -eq "warn") { Write-Host "warning" } else { Write-Host "error" } PowerShell
| Meaning | JavaScript ES5 | PowerShell |
|---|---|---|
| Equal (loose / value) | == | -eq |
| Equal (strict type+value) | === | Use -eq with typed variables |
| Not equal | != / !== | -ne |
| Greater than | > | -gt |
| Less than | < | -lt |
| Greater or equal | >= | -ge |
| Less or equal | <= | -le |
| Logical and | && | -and |
| Logical or | || | -or |
| Logical not | ! | -not or ! |
| Wildcard match | n/a (use regex) | -like "web-*" |
| Regex match | /pattern/.test(str) | $str -match "pattern" |
// for loop for (var i = 0; i < 5; i++) { console.log(i); } // for...in — iterates object keys (not recommended for arrays) var obj = { a: 1, b: 2 }; for (var key in obj) { console.log(key, obj[key]); } // while / do...while var n = 0; while (n < 3) { n++; } do { n--; } while (n > 0); JS (ES5)
# for loop for ($i = 0; $i -lt 5; $i++) { Write-Host $i } # foreach — iterates values directly $ht = @{ a = 1; b = 2 } foreach ($key in $ht.Keys) { Write-Host $key $ht[$key] } # while / do...while $n = 0 while ($n -lt 3) { $n++ } do { $n-- } while ($n -gt 0) PowerShell
// JavaScript ES5 — fall-through requires explicit break switch (color) { case "red": console.log("stop"); break; case "green": console.log("go"); break; default: console.log("unknown"); } JS (ES5)
# PowerShell — no fall-through by default; supports regex and wildcards switch ($color) { "red" { Write-Host "stop" } "green" { Write-Host "go" } default { Write-Host "unknown" } } # Regex-based switch switch -Regex ($input) { '^GET' { Write-Host "read" } '^POST' { Write-Host "write" } } PowerShell
Both languages use try / catch / finally syntax. The key differences
are in what gets thrown, how errors propagate, and how the runtime behaves when an
error occurs outside a try block.
try { var result = riskyCall(); if (!result) { throw new Error("riskyCall returned falsy"); } } catch (e) { console.log("Caught: " + e.message); } finally { console.log("always runs"); } JS (ES5)
try { $result = Invoke-RiskyCall if (-not $result) { throw "Invoke-RiskyCall returned falsy" } } catch { Write-Host "Caught: $($_.Exception.Message)" } finally { Write-Host "always runs" } PowerShell
| Property | JavaScript ES5 (e) | PowerShell ($_ in catch) |
|---|---|---|
| Message | e.message |
$_.Exception.Message |
| Stack trace | e.stack |
$_.ScriptStackTrace |
| Error type check | e instanceof TypeError |
$_.Exception -is [System.IO.IOException] |
| Re-throw | throw e; |
throw $_ or throw $_.Exception |
catch) from non-terminating errors (written to the error
stream, execution continues). Many cmdlets produce non-terminating errors by
default. Use -ErrorAction Stop or set
$ErrorActionPreference = "Stop" to promote them so they are
catchable.
# Non-terminating — execution continues, catch is never entered try { Get-Item "C:\DoesNotExist" # writes to error stream, continues Write-Host "still running" } catch { Write-Host "never reached" } # Terminating — promote with -ErrorAction Stop try { Get-Item "C:\DoesNotExist" -ErrorAction Stop } catch { Write-Host "caught: $($_.Exception.Message)" } PowerShell
| Operation | JavaScript ES5 | PowerShell |
|---|---|---|
| Concatenate | "Hello " + name |
"Hello " + $name or "Hello $name" |
| Length | str.length |
$str.Length |
| Uppercase | str.toUpperCase() |
$str.ToUpper() |
| Lowercase | str.toLowerCase() |
$str.ToLower() |
| Trim whitespace | str.trim() |
$str.Trim() |
| Split | str.split(",") |
$str -split "," |
| Replace | str.replace("a", "b") |
$str -replace "a","b" |
| Index of | str.indexOf("x") |
$str.IndexOf("x") |
| Substring | str.substring(2, 5) (start, end) |
$str.Substring(2, 3) (start, length) |
| Starts with | str.indexOf("x") === 0 |
$str.StartsWith("x") or $str -like "x*" |
| Contains | str.indexOf("x") !== -1 |
$str.Contains("x") or $str -like "*x*" |
var str = "server: web-01"; // Test for match var matched = /web-\d+/.test(str); // true // Extract capture group var m = str.match(/web-(\d+)/); if (m) { console.log(m[1]); } // "01" // Replace with regex var clean = str.replace(/web-\d+/g, "db-02"); // Case-insensitive flag var hit = /WEB/i.test(str); // true JS (ES5)
$str = "server: web-01" # Test for match (-match also populates $Matches) $matched = $str -match "web-\d+" # $true # Extract capture group — $Matches is populated by -match if ($str -match "web-(\d+)") { Write-Host $Matches[1] # "01" } # Replace with regex $clean = $str -replace "web-\d+", "db-02" # Case-sensitive match $hit = $str -cmatch "WEB" # $false (case-sensitive) PowerShell
-eq, -replace,
-match, -like) are case-insensitive by
default. Prefix with c for case-sensitive:
-ceq, -creplace, -cmatch.
JavaScript regex is case-sensitive by default; append the i flag
for case-insensitive: /pattern/i.
ES5's type system is loose and performs implicit coercion when comparing or operating on values of different types — a well-known source of bugs. PowerShell is also dynamically typed but is more conservative with coercion, and allows explicit type constraints on variables and parameters.
| ES5 Type | ES5 Example | PowerShell Equivalent |
|---|---|---|
| Number | 42, 3.14, NaN, Infinity |
[int], [double], [decimal] |
| String | "hello" |
[string] |
| Boolean | true / false |
$true / $false — [bool] |
| Null | null |
$null |
| Undefined | undefined (unset variable) |
No equivalent — unset variables return $null |
| Object | {} |
[hashtable] / [PSCustomObject] |
var x = 42; typeof x; // "number" typeof "hello"; // "string" typeof true; // "boolean" typeof null; // "object" — famous JavaScript bug typeof []; // "object" typeof undefined; // "undefined" // instanceof for reference types [] instanceof Array; // true // Reliable array check in ES5 Array.isArray([]); // true JS (ES5)
$x = 42 $x.GetType().Name # "Int32" $x -is [int] # $true $x -is [string] # $false # Safe cast — returns $null on failure instead of throwing $x -as [string] # "42" "abc" -as [int] # $null # Hard cast [string]$x # "42" # Constrain variable type — subsequent assignments are auto-cast [int]$port = 8080 $port = "9090" # becomes int 9090 $port = "abc" # throws — cannot convert to int PowerShell
== operator coerces types before comparing, producing results
that are often surprising. Use === (strict equality) in all
comparisons. PowerShell's -eq does not have this issue — it coerces
the right operand to the type of the left operand rather than applying arbitrary
implicit conversion.
// == coercion surprises 0 == "" // true ← "" coerced to 0 0 == false // true ← false coerced to 0 "" == false // true null == undefined // true "1" == 1 // true // === strict equality — no coercion 0 === "" // false ✓ 0 === false // false ✓ "1" === 1 // false ✓ JS (ES5)
ES5 introduced the native JSON object, which is used heavily in VRO
for passing structured data between workflow inputs and action outputs. PowerShell
uses the ConvertTo-Json and ConvertFrom-Json cmdlets.
var payload = { host: "web-01", port: 443, tags: ["prod", "web"] }; // Object → JSON string var json = JSON.stringify(payload); // '{"host":"web-01","port":443,"tags":["prod","web"]}' // Pretty-print with indentation var pretty = JSON.stringify(payload, null, 2); // JSON string → object var obj = JSON.parse(json); console.log(obj.host); // "web-01" JS (ES5)
$payload = [PSCustomObject]@{ Host = "web-01" Port = 443 Tags = @("prod", "web") } # Object → compact JSON string $json = $payload | ConvertTo-Json -Compress # '{"Host":"web-01","Port":443,"Tags":["prod","web"]}' # Nested objects require -Depth (default is 2 — silently truncates deeper) $json = $payload | ConvertTo-Json -Depth 10 # JSON string → PSCustomObject $obj = $json | ConvertFrom-Json $obj.Host # "web-01" PowerShell
| Scenario | JavaScript ES5 | PowerShell |
|---|---|---|
| Invalid JSON input | JSON.parse() throws SyntaxError |
ConvertFrom-Json throws a terminating error |
| Deep nesting | No depth limit | Default -Depth 2 silently truncates — always pass -Depth 10 for complex payloads |
undefined values |
Keys with undefined values are omitted from output |
$null values are serialised as JSON null |
| Dates | Date objects serialised as ISO string via .toISOString() |
[datetime] serialised as "\/Date(ms)\/" — convert manually: $d.ToString("o") |
| Numbers | All numbers are IEEE 754 double-precision floats | Integer types preserved; large integers may need [long] or [decimal] |
JSON.stringify() at the boundary and deserialise with
JSON.parse() on the receiving end. This avoids issues with VRO's
internal type marshalling for nested objects and ensures data survives the
action-output boundary cleanly.