Woodford CLI is a command-line tool that allows you to manipulate app projects on remote Resco Cloud or Power Platform servers. It provides a flexible set of commands that help developers speed up the deployment of Woodford projects:
-
log in (device code or client credentials) and log out
-
list projects and roles
-
create, update and delete projects
-
import and export projects (since 19.2 also as distribution packages)
-
validate and publish projects
Find more details in the npmjs package readme document.
Prerequisites
Minimum version:
-
15.0 (first supported release)
-
19.1 for client ID and client secret authentication flow, update command, parent project property
-
19.2 for distribution packages (
-g,-wand-mexport options)
Supported backends:
Software:
-
Node.js (current LTS) with npm
Installation
To install CLI globally, run the following command:
npm install -g @resconet/woodford-cli
To uninstall CLI, run the following command.
npm uninstall -g @resconet/woodford-cli
Available commands
Options:
--help Show help [boolean]
--version Show version number [boolean]
-u, --url Organization url [string]
-l, --login Login [string]
-p, --password Password [string]
-c, --command Command: [login | logout | list | export | import |
create | update | delete | publish | roles] project
[string] [required]
-n, --name Project name [string]
-f, --file Path to file [string]
-v, --validate Validate imported project [boolean] [default: true]
-r, --properties Command properties
- Create:
'priority;role1,role2,..;appid;version;parent'
- Update: 'priority;role1,role2,..;appid;version'
[string]
-d, --dynamicscrm Dynamics CRM backend [boolean]
-a, --clientid Client ID [string]
-s, --clientsecret Client Secret [string]
-g, --distributionpackage Export as distribution package [boolean]
-w, --allowderivation Allow derivation [boolean]
-m, --customversionid Custom version ID [string]
Notes on options
-
-u– organization URL. Resco Cloud:https://<server>/<organization>; Power Platform:https://<name>.crm.dynamics.com. -
-d– selects the Power Platform backend. The CLI selects it automatically when the URL ends with.dynamics.com; add-dfor any other Power Platform URL. Without it, Resco Cloud is assumed. -
-aand-s– client ID and client secret login (19.1 and later). Without them,-lstarts the device code flow. -
-v– applies toimportandpublish. Validation is on by default; use--no-validateto skip it. -
-r– semicolon-separated parts, in this order:
-
priority– project priority (number, default 0) -
role1,role2,...– comma-separated role names (defaultSystem Administrator). Userolesto list the available names. -
appid– app ID -
version– project version -
parent– only forcreate(19.1 and later): name of the parent project; several parents can be separated by commas. Optional.
With update, parts you leave out fall back to their defaults (priority 0, role System Administrator), so pass every part you want to keep.
-g, -w and -m – see Distribution packages below.
Usage
Login (device code flow):
wf-cli -c login -u https://<server>.crm.dynamics.com -l user@example.com
Login (client credentials):
wf-cli -c login -u https://<server>.crm.dynamics.com -a <client_id> -s <client_secret>
Logout:
wf-cli -c logout
List projects:
wf-cli -c list
List roles:
wf-cli -c roles
Create project:
wf-cli -c create -n <project_name> -r "priority;role1,role2,...;appid;version;parent"
wf-cli -c create -n "Best Project" -r "1;System Administrator,Sales Person;My First App;v1.0"
wf-cli -c create -n "Derived Project" -r "1;System Administrator;My First App;v1.0;Best Project"
Update project:
wf-cli -c update -n <project_name> -r "priority;role1,role2,...;appid;version"
wf-cli -c update -n "Best Project" -r "2;System Administrator,Sales Person;My First App;v1.1"
Delete project (no confirmation is asked):
wf-cli -c delete -n <project_name>
Import project:
wf-cli -c import -n <project_name> -f project.woodford
wf-cli -c import -n <project_name> -f project.woodford --no-validate
Export project:
wf-cli -c export -n <project_name> -f project.woodford
Publish project:
wf-cli -c publish -n <project_name>
wf-cli -c publish -n <project_name> --no-validate
Distribution packages
Since 19.2, an app project can be exported as a read-only distribution package. When such a package is imported, it is created as a standard project that cannot be edited: Edit, Export, Clone and Git-related actions are disabled. See the v19.2 release notes and App projects for how this looks in App Configurator.
Export options:
-
-g(--distributionpackage) – export the project as a distribution package. -
-w(--allowderivation) – allow derivation. If the package allows it, the Derive action stays available for the imported project. -
-m(--customversionid) – custom version ID that identifies the package. It can be adjusted later in the project Properties dialog.
-w and -m only work together with -g.
Examples:
# distribution package
wf-cli -c export -n <project_name> -f project.woodford -g
# allow derivation
wf-cli -c export -n <project_name> -f project.woodford -g -w
# custom version ID
wf-cli -c export -n <project_name> -f project.woodford -g -m "v1.0"
# allow derivation and custom version ID
wf-cli -c export -n <project_name> -f project.woodford -g -w -m "v1.0"
If you use -w or -m without -g, the CLI prints Warning: -w (allowderivation) and -m (customversionid) options are only available when using -g (distributionpackage) and exports a standard project.
To import a distribution package, use the regular import command. The package properties are read from the file automatically.
Example: Migrate and publish a project with role assignment
The following PowerShell script demonstrates a full deployment workflow: authenticating with client credentials, selecting roles across all business units, conditionally creating a destination project, migrating configuration from a source project, and publishing.
$url = "https://<name>.crm4.dynamics.com"
$clientId = "<client id>"
$clientSecret = "<client secret>"
$srcProjectName = "<source project name>"
$dstProjectName = "<destination project name>"
$appId = "<app id>"
$priority = 100
$version = "<custom version>"
$roles = "Salesperson", "Field Service - Dispatcher", "Customer Service Representative"
# Login using client credentials
wf-cli -c login -u $url -a $clientId -s $clientSecret
# Select roles across all business units
# wf-cli returns roles in the format "RoleName(BusinessUnit)" — filter by known role names
$listRoles = wf-cli -c roles
$selectedRoles = $listRoles | Where-Object {
foreach ($r in $roles) {
if ($_ -match "^$r\(.*$") { return $true }
}
return $false
}
$projectRoles = $selectedRoles -join ","
# Export source project
wf-cli -c export -n $srcProjectName -f "$srcProjectName.woodford"
# Create destination project if it doesn't exist yet
$list = wf-cli -c list
if (-not ($list | Where-Object { $_ -match "^$dstProjectName$" })) {
Write-Host "Project $dstProjectName not found. Creating..." -ForegroundColor Red
wf-cli -c create -n $dstProjectName -r "$priority;$projectRoles;$appId;$version"
}
# Import configuration into destination project
wf-cli -c import -n $dstProjectName -f "$srcProjectName.woodford"
# Clean up exported file
Remove-Item "$srcProjectName.woodford"
# Publish destination project
wf-cli -c publish -n $dstProjectName
# Logout
wf-cli -c logout
Key points
-
The
rolescommand returns entries inRoleName(BusinessUnit)format — the script matches by role name prefix to capture all business units automatically. -
The
createcommand is skipped if the destination project already exists, making the script safely re-runnable. -
The
-r(properties) parameter forcreatetakes the formatpriority;role1,role2,...;appid;version;parent. The optionalparentpart is not used in this script.
Running in CI pipelines
Do not store the client secret in the script or in the repository. Keep it as a secret variable of your CI system and pass it to -s from the environment. The login state is saved in a .wf-cli file in the home directory of the user running the CLI; logout deletes it, so call it at the end of the job.
Bash example (WF_URL, WF_CLIENT_ID and WF_CLIENT_SECRET are secret variables; the destination project already exists):
#!/usr/bin/env bash
trap 'wf-cli -c logout' EXIT
wf-cli -c login -u "$WF_URL" -a "$WF_CLIENT_ID" -s "$WF_CLIENT_SECRET"
wf-cli -c export -n "$SRC_PROJECT" -f "$SRC_PROJECT.woodford"
wf-cli -c import -n "$DST_PROJECT" -f "$SRC_PROJECT.woodford"
wf-cli -c publish -n "$DST_PROJECT"
rm -f "$SRC_PROJECT.woodford"