SCRIPTING REFERENCE

JavaScript ES5 vs PowerShell

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.

Author
Chris Foottit
Date
2026-05-25
Version
1.0
Tags
javascript es5 powershell vro
Table of Contents
  1. Overview
  2. Variables & Scope
  3. Functions
  4. Objects & Hashtables
  5. Arrays & Collections
  6. Control Flow
  7. Error Handling
  8. String Operations
  9. Type System & Coercion
  10. JSON
01

Overview

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.

VRO Note VRO uses ES5 only. There is no 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.

Key Differences at a Glance

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
02

Variables & Scope

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:).

Declaration & Assignment

ConceptJavaScript ES5PowerShell
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

Hoisting (ES5 Only)

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

Scope Rules

// 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
Common Trap In ES5, forgetting 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.
03

Functions

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.

Declaration Styles

// 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

Default Parameters

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

Closures

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
04

Objects & Hashtables

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.

Creating Objects

// 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

Inspecting & Iterating Properties

OperationJavaScript ES5PowerShell
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"

Constructor Functions (ES5 Prototype Pattern)

// 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
05

Arrays & Collections

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.

Creation & Basic Operations

OperationJavaScript ES5PowerShell
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)

Functional Methods

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
Performance Note 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.
06

Control Flow

Conditionals

// 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

Comparison Operators

MeaningJavaScript ES5PowerShell
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 matchn/a (use regex)-like "web-*"
Regex match/pattern/.test(str)$str -match "pattern"

Loops

// 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

Switch

// 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
07

Error Handling

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 / Catch / Finally

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

Error Object Properties

PropertyJavaScript 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
PowerShell Non-Terminating Errors PowerShell distinguishes terminating errors (caught by 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
08

String Operations

Common Operations

OperationJavaScript ES5PowerShell
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*"

Regular Expressions

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
Case Sensitivity PowerShell string operators (-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.
09

Type System & Coercion

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.

Primitive Types

ES5 TypeES5 ExamplePowerShell 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]

Checking Types

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

The == vs === Trap

Always Use === in ES5 The == 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)
10

JSON

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.

Serialize & Deserialize

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

Gotchas

ScenarioJavaScript ES5PowerShell
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]
VRO Best Practice When passing complex objects between VRO actions and workflows, serialise to JSON with 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.