diff --git a/docs/agent-reference/how-to-implement-custom-update-handler.md b/docs/agent-reference/how-to-implement-custom-update-handler.md index a297adac9..2c352a469 100644 --- a/docs/agent-reference/how-to-implement-custom-update-handler.md +++ b/docs/agent-reference/how-to-implement-custom-update-handler.md @@ -32,7 +32,7 @@ See **ContentHandler** class definition in [content_handler.hpp](../../src/exte | Install | Handles an 'install' task invoked by DU Agent workflow.

The install task usually includes a process where the handler invokes external tool or command to install the downloaded update payload file(s) to the desired target.
For example, for 'microsoft/apt' update, the handler could invoke following command:

`apt-get install ` | For success cases, the **ResultCode** field of the **ADUC_Result** struct can be one of the following values:

ADUC_Result_Install_Success
ADUC_Result_Install_InProgress
ADUC_Result_Install_Skipped_UpdateAlreadyInstalled
ADUC_Result_Install_Skipped_NoMatchingComponents
ADUC_Result_Install_RequiredImmediateReboot
ADUC_Result_Install_RequiredReboot
ADUC_Result_Install_RequiredImmediateAgentRestart
ADUC_Result_Install_RequiredAgentRestart

See [adu_core.h](../../src/adu_types/inc/aduc/types/adu_core.h) for more details | | Apply | Handles an 'apply' task invoked by DU Agent workflow.

The apply task usually includes one or more additional steps after an 'install' task has completed. Such as, validating installed items, restart system service, update configuration files, persist some meta data related to the update process.

The DU Agent workflow will consider the update complete successfully only when an apply task returns ADUC_Result_Apply_Success | For success cases, the **ResultCode** field of the **ADUC_Result** struct can be one of the following values:

ADUC_Result_Apply_Success
ADUC_Result_Apply_InProgress
ADUC_Result_Apply_RequiredImmediateReboot
ADUC_Result_Apply_RequiredReboot
ADUC_Result_Apply_RequiredImmediateAgentRestart
ADUC_Result_Apply_RequiredAgentRestart

See [adu_core.h](../../src/adu_types/inc/aduc/types/adu_core.h) for more details | | Cancel | Handles a 'cancel' task invoked by DU Agent workflow.

The cancel task usually initiated by the Device Update Service. When an Agent received a cancel request, the Agent workflow will relay this request to an active Step Handler that's currently processing the deployment. The handler should try to gracefully cancelling current task, and return appropriate result. | For success cases, the **ResultCode** field of the **ADUC_Result** struct can be one of the following values:

ADUC_Result_Cancel_Success
ADUC_Result_Cancel_UnableToCancel
See [adu_core.h](../../src/adu_types/inc/aduc/types/adu_core.h) for more details | -| IsInstalled | In some situation, an Agent workflow can invoke 'IsInstalled' function, to determine whether the current update (as specified in [**ADUC_WorkflowData**](../../src/adu_types/inc/aduc/types/workflow.h) object) is installed on the target device, or component. The Step Handler is responsible for evaluate the target state and return result accordingly. | The **ResultCode** field of the **ADUC_Result** struct can be one of the following values:

ADUC_Result_IsInstalled_Installed
ADUC_Result_IsInstalled_NotInstalled
See [adu_core.h](../../src/adu_types/inc/aduc/types/adu_core.h) for more details | +| IsInstalled | Probe that lets the orchestrator (and the Steps Handler, when this handler is used as a step) decide whether the update described by [**ADUC_WorkflowData**](../../src/adu_types/inc/aduc/types/workflow.h) is already present on the target device or component, so it can skip work that has already been done. **Called multiple times per deployment** (see [IsInstalled call sites and authoring guidance](#isinstalled-call-sites-and-authoring-guidance) below) — must be cheap, side-effect-free, and idempotent. The handler compares the workflow's `installedCriteria` against handler-scoped state (e.g., installed package version for `microsoft/apt:1`, file hash on disk for `microsoft/swupdate:2`) and returns the result; it must **not** consult system-wide state outside the scope of this handler's content. | The **ResultCode** field of the **ADUC_Result** struct can be one of the following values:

ADUC_Result_IsInstalled_Installed
ADUC_Result_IsInstalled_NotInstalled

A `ResultCode` of `0` is `ADUC_Result_Failure`; the Steps Handler converts a failure to `NotInstalled` so the workflow proceeds. See [adu_core.h](../../src/adu_types/inc/aduc/types/adu_core.h) for more details. | ## Consuming ADUC_WorkflowData @@ -107,6 +107,29 @@ When a step targets selected components, the Steps Handler invokes your handler If `selectedComponentsCount == 0` for an inline step at the parent level, the step is treated as **optional** and skipped (not failed). +### IsInstalled call sites and authoring guidance + +`IsInstalled` is **not** invoked once per deployment. The agent core and the Steps Handler call it several times to decide whether the update — or any individual step — can be skipped. Authors must therefore treat it as a **cheap, side-effect-free, idempotent probe**. The known call sites are: + +| Call site | Why | +|---|---| +| Agent core, before starting a deployment workflow | Avoids re-running an update that is already installed across an agent restart or a service-driven retry. | +| Agent core, after a successful Apply | Confirms the post-apply state matches `installedCriteria` before reporting success to the service. | +| Steps Handler, before each step's `Download` | If the step's content is already installed, downloading is skipped. | +| Steps Handler, before each step's `Install`/`Apply` | If the step is already installed, `Backup` / `Install` / `Apply` are skipped for that step and the workflow continues with the next step (the rest of the workflow is **not** short-circuited). | +| Steps Handler, after the per-(component × step) loop | Aggregate `IsInstalled` for the parent workflow: returns `Installed` only when **every** (component × step) pair returns `Installed`; any single `NotInstalled` makes the whole update `NotInstalled`. | + +Authoring rules: + +1. **Be cheap.** It runs many times. Avoid network calls, package-manager refreshes, or anything that mutates state. Cache results inside the handler if necessary. +2. **Be scoped.** A step's `IsInstalled` must reflect the installed state of *this step's content*, not system-wide state. A `microsoft/apt:1` step asks "is this package at this version installed?", not "is the device fully up to date?". +3. **Be deterministic.** Given the same `ADUC_WorkflowData`, return the same result regardless of how many times you are called or in what order. +4. **Use `installedCriteria` honestly.** Read it via `workflow_get_installed_criteria` and compare it against the handler-scoped state. Don't hard-code "always installed" or "always not installed" — both will break MSOE skip semantics. +5. **Don't return `0` to mean "yes".** `0` is `ADUC_Result_Failure`. Use `ADUC_Result_IsInstalled_Installed` (`900`) or `ADUC_Result_IsInstalled_NotInstalled` (`901`). The Steps Handler converts a failure return to `NotInstalled` so the workflow proceeds, but you should not rely on that. +6. **No reboots, no restarts.** `IsInstalled` is a query. It must never schedule a reboot, restart the agent, or modify the device. + +When this handler is used as a step inside a multi-step manifest, the Steps Handler's aggregate `IsInstalled` is the **conjunction** of each step's per-component `IsInstalled` results. See [steps-handler.md → IsInstalled](steps-handler.md#isinstalled) for the exact algorithm and `selectedComponentsCount == 0` (no matching components) edge case. + In some case, a Device Builder may want to install an update content on one or more component(s) that connected to the Host Device instead.