Script Handler Extension (or, in short, Script Handler) handler can be used to prepare and execute a script on the IoT Device.
For some update scenarios, you may want to run additional set of commands before, during, or after, installing a software update on your device. This can be achieved using an Inline Step with microsoft/script:1
handler type.
By default, Device Update Agent workflow invokes Script Handler to process microsoft/script:/1
steps.
For more information about Inline Step, see Update Manifest V4 Schema
Script Handler key concepts include:
- execute a custom script that's delivered to a device as an update payload.
- pass option(s) and argument(s) specified in an Update Manifest to the script at runtime.
- pass Device Update Agent workflow information to the script at runtime. Component Data to the script at runtime.
- gather result (ADUC_Result data) of the script execution then report back to the Device Update Agent workflow.
- communicate the Script's desired action (e.g., restart the Agent process, reboot the device) back to the Device Update Agent workflow.
- gather error and informational logs generated from the script execution then report back to the Device Update Agent workflow.
NOTE | For demonstration purposes, we will use following Update Manifest data as an example throughout this document.
The following update contains 3 in-line steps (in instructions.steps
array) :
- Pre-install step
- Firmware installation step
- Post-install step
All 3 steps are handled by Script Handler, as indicated by "handler":"microsoft/script:1"
in each step data.
NOTE | Device Update agent will dynamically load a registered Update Handler for a Handler Type (also known as Update Type) specified in
handler
property. The Script Handler provided in the project will be registered to handlemicrosoft/script:1
update type by default as part of deviceupdate-agent APT package installation.
{
"updateId": {
"provider": "contoso",
"name": "Virtual-Vacuum-virtual-motors",
"version": "3.0"
},
"isDeployable": false,
"compatibility": [
{
"group": "motors"
}
],
"instructions": {
"steps": [
{
"description": "Motors Update pre-install step",
"handler": "microsoft/script:1",
"files": [
"contoso-motor-installscript.sh"
],
"handlerProperties": {
"scriptFileName": "contoso-motor-installscript.sh",
"arguments": "--pre-install-sim-success --component-name --component-name-val --component-group --component-group-val --component-prop path --component-prop-val path",
"installedCriteria": "1.2"
}
},
{
"description": "Motors Update - firmware installation",
"handler": "microsoft/script:1",
"files": [
"contoso-motor-installscript.sh",
"motor-firmware-1.2.json"
],
"handlerProperties": {
"scriptFileName": "contoso-motor-installscript.sh",
"arguments": "--firmware-file motor-firmware-1.2.json --component-name --component-name-val --component-group --component-group-val --component-prop path --component-prop-val path",
"installedCriteria": "1.2"
}
},
{
"description": "Motors Update post-install step",
"handler": "microsoft/script:1",
"files": [
"contoso-motor-installscript.sh"
],
"handlerProperties": {
"scriptFileName": "contoso-motor-installscript.sh",
"arguments": "--post-install-sim-success --component-name --component-name-val --component-group --component-group-val --component-prop path --component-prop-val path",
"installedCriteria": "1.2"
}
}
]
},
"referencedBy": [
{
"provider": "Contoso",
"name": "Virtual-Vacuum",
"version": "5.0"
}
],
"manifestVersion": "4.0",
"scanResult": "Success",
"importedDateTime": ...,
"bundledBy": [...],
"createdDateTime": ...,
"etag": ...
}
The current implementation of the Script Handler requires following values in the handlerProperties
map:
Key | Value | Description |
---|---|---|
scriptFileName | Name of the script file | This file must be imported as part of the update. The file will be downloaded into a working folder on the device, when Device Update agent processing the update. See Specify A Script Filename for more details. |
arguments | Space-delimited list of options and arguments to pass to the script file when executing the step | There are a reserved options and arguments that Script Handler appends to the list, at runtime. For script that support Multi-Component Update, the reserved component-related options can be specified in the handlerProperties.arguments . These options will be processed by the Script Handler, and replaced with the runtime values for a component being updated. |
installedCriteria | A string interpreted by the handler to determine whether the Step need to be processed. | Script Handler pass the responsibility to determine whether the Step has been completed (aka., installed) by passing this installedCriteria string to the script. |
From an example steps data above, notice the handlerProperties.scriptFileName
property values, which are the same as file names in files
array.
At runtime, the Script Handler will download every files listed in files
array, into a workfolder
(e.g., /var/lib/adu/downloads/workflow_id_1234567890/).
Note | Learn more about how the Script Handler passes
workfolder
path to the script in Default Workflow Options and Arguments section below.
Later on, the Script Handler will invoke the specified script file, in a forked-process, to perform various tasks, such as download
(additional file), install
an update, apply
the installed update, cancel
the update flow if needed, or perform is-installed
evaluation.
IMPORTANT | Whatever file is referenced in the update manifest will be run as
root
! If you do not wish to enable this behavior, then you can modifyadu-shell
to run the file as other user.
You can specify the script argument in `handlerProperties.arguments' property of the In-line Step (see Update Manifest for more information about Multi-Step Ordered Execution and different between In-line vs. Reference steps)
For example (see arguments value below):
{
"description": "Motors Update post-install step",
"handler": "microsoft/script:1",
"files": [
"contoso-motor-installscript.sh"
],
"handlerProperties": {
"scriptFileName": "contoso-motor-installscript.sh",
"arguments": "--post-install-sim-success --component-name --component-name-val --component-group --component-group-val --component-prop path --component-prop-val path",
"installedCriteria": "1.2"
}
}
The arguments
value contains a space-delimited options
and arguments
that will be processed by the Script Handler. These options
may be interpreted by the Script Handler, then, replaced by, or populated with runtime variables.
Additionally, the Script Handler will append some default runtime workflow related values, such as, workfolder
, result-file
, installed-criteria
, etc. to the options and arguments list.
You can specify any options and arguments that relevant to the script in this 'arguments' string, as long as they don't conflict with reserved options or default workflow runtime options and arguments listed below.
You can specify any argument in the handlerProperties.arguments
string, except the default workflow options and arguments and options for component runtime variables listed in tables below:
IMPORTANT: Specify these options in the
handlerProperties.arguments
string may cause an unexpected behavior.
The following options and arguments are automatically pass to the script.
IMPORTANT: Specify these options in the
handlerProperties.arguments
string may cause an unexpected behavior:
Options | Argument(s) | Description |
---|---|---|
--action-apply | none | Script Handler will append this option to the arguments list to indicate that the script is invoked as part of the apply action.The Script should perform any additional tasks that finalize the installation of the update. For example, for SWUpdate flow, the install action may involve files copy and bootloader configurations changes. The apply action may involve device restart, using the boot partition which contains the new version of the OS. |
--action-cancel | none | Script Handler will append this option to the arguments list to indicate that the update (step) is being canceled.The Script should consider canceling any in-progress task, and restore the device or component back to the original state. |
--action-download | none | Script Handler will append this option to the arguments list to indicate that the script is invoked as part of the download action.The Script should perform any additional content download tasks to ensure that all content required by the installation is accessible during the installation. |
--action-install | none | Script Handler will append this option to the arguments list to indicate that the script is invoked as part of the install action.The Script should perform all tasks related to the software installation. Such as, install a software package(s) on host device, transfer firmware file to connected peripheral, remove existing file(s) that no longer needed, etc. |
--action-is-installed | none | Script Handler will append this option to the arguments list to indicate that the script is invoked as part of the isInstalled inquiry.As part of the Agent-Orchestrated workflow, sometimes, Device Update Agent will invoke the Script Handler's IsInstalled function to determine wither the current Step has been installed. For Step that does not install any software, is installed is equivalent to applied. E.g., "Is the step has been applied on the device or components?" |
--work-folder | <"work_folder_path"> | A fully qualified path to workfolder (a.k.a. sandbox folder ). This is a folder used for storing downloaded file, workflow result file, and any other temp files.Script Handler will append this option to the arguments list when invoking the script. |
--result-file | <"result_file_path"> | A fully qualified path to the workflow task result. The result file contains a ADUC_Result data in a JSON. The Script Handler will read ADUC_Result data in this file and continue or abort the update workflow based on the resultCode . If the result file does not exist, or can not be opened, the update workflow will be aborted.Script Handler will append this option to the arguments list when invoking the script. |
--installed-criteria | <"installed_criteria_string"> | Pass-through value to the script. |
The following options are reserved, and will be replaced by component-related value(s) at runtime. They are useful when implementing script that is support multi-component updates, which often perform various component(s)-specific tasks.
For example:
- Copy files to a specific device path
- Install firmware for a specific device name
- Install firmware for a group of device
NOTE | The following arguments will be replaced by "n/a" if
Component Enumerator Extension
is not registered.
Options | Arguments | Runtime Value Type | Description |
---|---|---|---|
--component-id-val | none | string | The current components' id |
--component-name-val | none | string | The current component's name |
--component-manufacturer-val | none | string | The component's manufacturer |
--component-model-val | none | string | The component's model |
--component-version-val | none | string | The component's version |
--component-group-val | none | string | The component's group |
--component-prop-val | property name | string | The components' property value. NOTE: The argument must follow by a component's property name. |
The following instruction step contains various reserved options that would be replaced by component-related runtime variables, after processed by the Script Handler.
{
"description": "Motors Update - firmware installation",
"handler": "microsoft/script:1",
"files": [
"contoso-motor-installscript.sh",
"motor-firmware-1.2.json"
],
"handlerProperties": {
"scriptFileName": "contoso-motor-installscript.sh",
"arguments": "--firmware-file motor-firmware-1.2.json --component-name --component-name-val --component-group --component-group-val --component-prop path --component-prop-val path",
"installedCriteria": "1.2"
}
},
Following are the mapping between the step.handlersProperties.arguments
value and the actual arguments that StepHandler pass to the script at runtime.
Note: for this example, let's assumed that the Script Handler is processing an update workflow (
install
) for following component information provided by a registeredComponent Enumerator
extension
{
"components": [
...,
{
"id": "contoso-motor-serial-00000",
"name": "left-motor",
"group": "motors",
"manufacturer": "contoso",
"model": "virtual-motor",
"properties": {
"path": "\/usr\/local\/contoso-devices\/vacuum-1\/motors\/contoso-motor-serial-00000",
"firmwareDataFile": "firmware.json",
"status": "ok"
},
"version": "1.2",
"description": "This component is generated for testing purposes."
},
...
]
}
handleProperties.arguments (option) |
handleProperties.arguments (arguments) |
Value pass to the script at runtime (after processed by Script Handler) |
Note |
---|---|---|---|
--firmware-file motor-firmware-1.2.json | --firmware-file motor-firmware-1.2.json | Not a reserved option. | |
--component-name | --component-name | Not a reserved option. | |
--component-name-val | "left-motor" | This is the value of the component's name property above. | |
--component-group | --component-group | Not a reserved option. | |
--component-group-val | "motors" | This is the value of the component's group property above. | |
--component-prop | path | --component-prop path | Not a reserved option. Note that both option and argument are unchanged. |
--component-prop-val | path | "/usr/local/contoso-devices/vacuum-1/motors/contoso-motor-serial-00000" | This is the value of the component's properties.path value above. |
--action-install | Automatically appended as part of the *install* workflow. This indicates that the script should perform 'install' task. |
||
--work-folder "/var/lib/adu/downloads/workflow_id_1234567890/" | Automatically appended as part of the *install* workflow. Note that is is for demonstration purposes. The actual work folder path will be different in real update scenario. |
||
--result-file "/var/lib/adu/downloads/workflow_id_1234567890/aduc_result.json" | Automatically appended as part of the *install* workflow. Note that is is for demonstration purposes. The actual file path will be different in real update scenario. |
||
--installed-criteria "2.0" | Automatically appended as part of the *install* workflow. The value "2.0" is the value of handlerProperties.installedCriteria , if specified. |
From the post-processed values in the table above, this is what the script invoking command looks like:
contoso-motor-installscript.sh
--firmware-file motor-firmware-1.2.json
--component-name "left-motor" --component-group "motors"
--component-prop path "/usr/local/contoso-devices/vacuum-1/motors/contoso-motor-serial-00000"
--action-install
--work-folder "/var/lib/adu/downloads/workflow_id_1234567890/"
--result-file "/var/lib/adu/downloads/workflow_id_1234567890/aduc_result.json"
--installed-criteria": "1.2"
To get started, you can take a look at the example-installscript.sh to see how the script can be implemented.
IMPORTANT: Please note that this is for demonstration purposes only. The example script is provided AS-IS. You should not use it for your production project.
The script, must write an ADUC_Result data into a designated result file (specified by --result-file option)
IMPORTANT: Make sure to modify the default behavior of writing result file. If agent detects ResultCode and ExtendedResultCode of 0, then for discoverability, it will replace the ExtendedResultCode with ADUC_ERC_SCRIPT_HANDLER_INSTALL_FAILURE_SCRIPT_RESULT_EXTENDEDRESULTCODE_ZERO that has numeric code of 81054976 (decimal) and 0x30500206 (hexadecimal).
Example contents of result file for Success:
{
"resultCode": 1,
"extendedResultCode": 0,
"resultDetails": ""
}
Example contents of result file for Failure:
{
"resultCode": 0,
"extendedResultCode": <some non-zero decimal number>,
"resultDetails": "some optional description"
}
The following is excerpt from an example script:
...
#
# Write result json string to result file.
#
result(){
# NOTE: don't insert timestamp in result file.
if [ -z $result_file ]; then
echo "$@" >&1
else
echo "$@" > "$result_file"
fi
}
...
#
# Perform install-related tasks.
#
InstallUpdate() {
resultCode=0
extendedResultCode=0
resultDetails=""
ret_val=0
#
# PLACEHOLDER : Evaluate 'installedCriteria' to determine if there is anything to do.
# If not, return ADUC_Result_Install_Success (600). Otherwise, continue.
#
#
# PLACEHOLDER : Perform installation tasks here.
#
# Based on overall result..
#
# # Set result code and details
# resultCode=<Result Code>
# extendedResultCode=<Extended Result Code, in case of error>
# resultDetails="<Additional result details>
# $ret_value=<Script exit code>
# Prepare ADUC_Result json.
aduc_result_json="{\"resultCode\":$resultCode, \"extendedResultCode\":$extendedResultCode,\"resultDetails\":\"$resultDetails\"}"
# Write ADUC_Result to result file.
result "$aduc_result_json"
$ret $ret_val
}
...