vRO Artifact Authoring Notes
These notes capture the practical details learned while adding workflow artifact import/export and creating the List VMs by Project Name workflow. Use this as the fast path for future workflow/action work.
Workflow Artifact Format
- A
.workflowfile is a ZIP archive. - The archive contains:
workflow-info— a Java properties file (not XML). vRO writes the fixed keystype=workflow,version=2.0,charset=UTF-16,unicode=true,creator=www.dunes.ch, andowner=. The workflow's own id/name/version live inworkflow-content, not here.workflow-content— the workflow XML.input_form_— only present when the workflow exposes inputs (UI-startable). Inputless workflows omit it and rely on the in-workflow<presentation>.
workflow-contentis XML encoded as UTF-16BE with a big-endian BOM (0xFE 0xFF). A little-endian BOM (0xFF 0xFE) is rejected by live import.- The
workflow-contentXML declaration must be<?xml version='1.0' encoding='UTF-8'?>(single quotes,UTF-8) — exactly what vRO writes, even though the bytes are UTF-16BE (the BOM drives decoding). The VCF 9.x Orchestrate editor returns a 500 when opening a workflow whose declaration saysencoding="UTF-16", even though import and execution succeed. input_form_is JSON encoded as UTF-16BE with a BOM. It holds both thelayout(form) and theschema; the schema mirrors the workflow inputs, so keep them in sync (the builder generates the schema from the inputs).- The XML root looks like:
<workflow xmlns="http://vmware.com/vco/workflow" ... root-name="..." object-name="workflow:name=generic" id="..." version="..." api-version="6.0.0" editor-version="2.0" ...>- Use
editor-version="2.0"and do not emitallowed-operationson authored workflows. The valueallowed-operations="vf"is vRO's read-only marker for locked Library workflows; emitting it makes the Orchestrate editor treat the workflow as locked, so it shows Details instead of Open and returns a 500 when opened. Setobject-nameto lowercaseworkflow:name=generic; vRO normalizes it on export.
- User-facing inputs/outputs are bare
<param name="..." type="..."/>elements (no<description>child — descriptions belong in<presentation>/input_form_; a<param>description combined with the workflow<description>breaks the editor). Inputs live under<input>, outputs under<output>. - Scriptable tasks are
<workflow-item type="task">nodes with a non-empty<description>and a<script encoded="false"><![CDATA[...]]></script>body. - Every item — the start node, each task, and the end item — needs a distinct
<position>. If items overlap (all at0,0, which happens when positions are omitted) the editor's schema renderer fails. Lay them out left-to-right with increasingx. - Flow is chained via
out-name: each task points to the next item, and the final task points to an explicit terminal item<workflow-item name="..." type="end" end-mode="0">that carries an empty<in-binding/>. Do not putend-mode="1"on a task — live import rejects scaffolds that lack an explicittype="end"item. - Prefer native vRO action workflow items when a workflow step only executes one existing action. Avoid wrapping a single action in a scriptable task that only calls
System.getModule(...). - Use scriptable tasks when the item performs multiple action calls or additional orchestration logic such as validation, branching, input shaping, or result aggregation.
- Prefer horizontal workflow layouts: arrange sequential items left-to-right with increasing
xpositions and stableypositions unless a branch needs vertical separation. - Task input/output bindings must connect workflow parameters to script variables:
<in-binding><bind name="projectName" type="string" export-name="projectName"/></in-binding><out-binding><bind name="vms" type="Array/Properties" export-name="vms"/></out-binding>
Workflow Input Forms
Workflows intended to start from the vRO UI need a valid input_form_ entry. The MCP scaffold builder generates this automatically from workflow inputs.
Valid package/exported input form shape:
{
"layout": {
"pages": [
{
"id": "page_general",
"sections": [
{
"id": "section_inputs",
"fields": [
{
"id": "message",
"display": "textField",
"signpostPosition": "right-middle",
"state": { "visible": true, "read-only": false }
}
]
}
],
"title": "General"
}
]
},
"schema": {
"message": {
"id": "message",
"type": { "dataType": "string" },
"label": "Message",
"constraints": { "required": true }
}
},
"options": { "externalValidations": [] },
"itemId": ""
}Important compatibility notes:
- Section objects must contain only
idandfields; put the visible title on the page, not the section. - Field objects should use
id,display,signpostPosition, andstate. Avoid unverified properties such assize. - Field IDs must match keys in
schema. - Use
textFieldfor string,passwordFieldforSecureString,checkboxfor boolean,decimalFieldfor number, andvaluePickerTreefor vRO reference types such asVC:VirtualMachine. preflight-workflow-fileandpreflight-packagevalidateinput_form_entries and fail on the section/field shapes known to break the vRO start page.
Import And Export Endpoints
Use the official vRO content endpoints:
- Export workflow artifact:
GET /vco/api/content/workflows/{workflowId}Accept: application/zip
- Import workflow artifact:
POST /vco/api/workflows?categoryId={categoryId}&overwrite={true|false}- Body is multipart
FormDatawith afilepart containing the.workflowartifact. - Do not set
Content-Typemanually for multipart uploads; letfetch/FormDataset the boundary.
- Export action artifact:
GET /vco/api/actions/{actionId}Accept: application/zip- Save as
.action.
- Import action artifact:
POST /vco/api/actions- Body is multipart
FormDatawithfileandcategoryName.
- Export configuration artifact:
GET /vco/api/configurations/{id}- Save as
.vsoconf.
- Import configuration artifact:
POST /vco/api/configurations- Body is multipart
FormDatawithfileandcategoryId.
The MCP tools implemented for this are:
export-workflow-fileimport-workflow-filepreflight-workflow-fileexport-action-fileimport-action-filepreflight-action-fileexport-configuration-fileimport-configuration-filepreflight-configuration-filepreflight-package
They read/write files only under their configured artifact directories:
VCFA_ARTIFACT_DIR/workflowsVCFA_ARTIFACT_DIR/actionsVCFA_ARTIFACT_DIR/configurations
Advanced users can override individual directories with VCFA_WORKFLOW_DIR, VCFA_ACTION_DIR, or VCFA_CONFIGURATION_DIR.
File Safety Pattern
Match the package/resource import-export safety model:
- Require a plain relative file name, not a path.
- Reject absolute paths.
- Reject path separators and traversal.
- Require the artifact extension for the target type:
.workflow,.action,.vsoconf,.package, or.zipas applicable. - Reject symlink import sources.
- Reject symlink export targets.
- Reject existing export targets unless
overwrite: true. - Verify real paths remain under the configured artifact root for that artifact type.
The shared helper is resolveFileInDirectory(rootDir, fileName, label, envName) in src/client/files.ts.
Workflow Runtime Patterns
Useful vRO script idioms:
var hosts = Server.findAllForType("VRA:Host");
var project = VraEntitiesFinder.getProjects(host);
var machines = VraEntitiesFinder.getMachines(host);
var machinesViaAction = System.getModule("com.vmware.library.vra.infrastructure.machine").getAllMachines(host);
var props = new Properties();
props.put("name", "value");Use System.getModule(...).actionName(...) inside a scriptable task only when the script needs to coordinate multiple action calls or perform additional logic. For a single action call, author a native vRO action workflow item and bind its inputs/output directly.
Historical built-in action observed during one live validation session:
com.vmware.library.vra.infrastructure.machine/getAllMachines- Script:
return VraEntitiesFinder.getMachines(host) - Input:
host (VRA:Host) - Return:
Array/VRA:Machine
Do not reuse live IDs from historical notes. Action details must be rediscovered in the target environment with MCP:
list-actionswith a filterget-actionby ID
Robust vRO Script Helpers
When writing portable workflow scripts, include small helper functions:
toText(value)for null-safe string conversion.normalize(value)for trimmed, case-insensitive matching.getField(object, fieldName)that tries both direct property access and Java-style getter methods.asArray(value)that accepts arrays and common paged response shapes likecontent,items, ordocuments.
This matters because vRO objects can expose properties differently depending on whether they are native scripting objects, plugin objects, or API wrapper objects.
List VMs By Project Name Workflow
Implemented workflow:
- Name:
List VMs by Project Name - ID:
fd370e68-24bc-4bb3-96cc-1e105fc9a516 - Category:
VCFA - Category ID:
7080802d9c4478cd019c447a56c302e8 - Input:
projectName (string)
- Outputs:
vms (Array/Properties)vmCount (number)
Runtime behavior:
- Trim and validate
projectName. - Auto-select first
VRA:Host, sorted by name/id. - Resolve project by exact case-insensitive name.
- Fail clearly when no project matches.
- Fail clearly when multiple normalized names match.
- Use
VraEntitiesFinder.getMachines(host)through the built-in library action/finder. - Filter machines by
projectIdfirst, withprojectNamefallback. - Return one
Propertiesobject per matched machine with:idnamestatuspowerStateprojectIdprojectNamedeploymentIddeploymentNameowneraddress
Historical live validation on 2026-04-30:
projectName = "MainPrj"completed successfully.- It returned
vmCount = 4from the VRA machine inventory. list-deploymentsshowed 5 deployments in the same project, but the extra deployment was not returned byVraEntitiesFinder.getMachines(host).- Deployment-aware fallback paths were attempted in the artifact, but this vRO runtime did not expose deployment listing through the tested scripting APIs.
- Blank
projectNamefailed withprojectName is required. - Unknown project failed with
Project not found: __does_not_exist__.
Treat these validation results as an example of the checks to run, not as a portable environment contract. Different VCFA/vRO environments can expose different projects, host names, plugin object shapes, and inventory coverage.
Validation Commands
Fast local checks:
npm testBefore uploading local artifacts, run the matching preflight tool:
preflight-workflow-filechecks.workflowZIP structure, theworkflow-infoproperties file, UTF-16BEworkflow-content, an explicittype="end"terminal item, UTF-16BEinput_form_JSON when present, parameters, bindings, task flow, vRO type syntax, action references, and local import path safety. It also emits warnings (non-blocking) for shapes that import and run but make the VCF 9.x editor fail to open the workflow: anencoding="UTF-16"XML declaration,<param><description>children, missing/overlapping item<position>s, an end item without<in-binding/>, and tasks without a<description>.preflight-action-filechecks.actionZIP/path safety and parses recognizable XML metadata conservatively.preflight-configuration-filechecks.vsoconfZIP/path safety and parses recognizable XML metadata conservatively.preflight-packagechecks.package/.zipimport safety, inspects nested.workflow,.action, and.vsoconfartifacts when they are present, and validates package elementinput_form_entries.
The import tools run the same preflight checks and fail locally before authentication or multipart upload when blocking errors are found.
Useful live MCP sequence:
preflight-workflow-filewithfileName.import-workflow-filewithcategoryId,fileName,overwrite: true,confirm: true.list-workflowsfiltered by workflow name.get-workflowto verify inputs/outputs.run-workflowwith inputs andconfirm: true.get-workflow-executionto poll status and inspect outputs/errors.
Workflow files are read from the workflows subdirectory of VCFA_ARTIFACT_DIR (defaults to artifacts/workflows/ in the MCP server process working directory, typically the open project) unless VCFA_WORKFLOW_DIR overrides it.
Common Pitfalls
create-workflowcreates only an empty workflow shell; use authored artifacts and the project package publish flow for real reusable workflow content.- Do not use a plain scriptable task solely to invoke one action; use a native action workflow item for that case. Scaffold it directly with
scaffold-workflow-fileby passing a task ofkind: "action"(module,actionName, orderedinputs, andresultBinding). In exported XML this is still atype="task"item, but it hasscript-module="<module>/<actionName>", a generatedactionResult = System.getModule("<module>").<actionName>(...)script, and anout-bindingfromactionResultto the workflow output. OmitresultBindingfor an action with no return value (the scaffold then emits the bare call with noactionResult). - Avoid vertical-only layouts for simple linear workflows; horizontal layouts are easier to scan and match project conventions.
- Do not omit
input_form_for workflows with user inputs that should be started from the vRO UI. - Do not add
titleto input form sections or unverified properties likesizeto fields; vRO can reject the start page with schema validation errors. .workflowworkflow-contentmust be UTF-16BE with a big-endian BOM,workflow-infomust be the Java properties file (not XML), and the workflow must terminate in an explicit<workflow-item type="end">; live import rejects any of these defects with400 "Not a valid workflow file".- Multipart imports break if
Content-Typeis set manually without the boundary. VRA:Machineinventory and VCFA deployments are not always a one-to-one match.- Workflow execution starts asynchronously; always poll
get-workflow-execution. - vRO script errors surface with item line numbers, so keep generated script readable.
- Action imports use category/module name (
categoryName), while workflow and configuration imports use category IDs.