CustomScript Design Best Practices
CustomScripts allow Activate services to integrate with external systems during provisioning. When services are processed immediately, scripts often run within the context of the original request. However, when services are processed on a future effective date, the execution context changes.
This guide explains how CustomScripts are executed in Activate, why effective-dated services behave differently, and how to design scripts that work reliably in all scenarios.
Purpose
This document provides guidance on writing CustomScripts that are safe to use with:
- Effective-dated services
- System-initiated provisioning
- Reprocessing and retries
It highlights common design mistakes and provides clear guidance to prevent them.
How CustomScripts Are Executed
When a service is provisioned, Activate invokes any CustomScript defined on the service parameters. The script is called by Activate Orchestrator using the current ServiceInstance as the execution context.
Example invocation:
object r = ProvisioningSystem.Evaluator.ScriptEngine.Call(
this,
Service.GetParameters("Script"),
"Create",
new object[] { this }
);In this call:
- this represents the current ServiceInstance
- The Create, Update, or Delete method receives the ServiceInstance as an argument
The ServiceInstance represents the service being provisioned and contains the data required to perform provisioning actions.
Effective-Dated Services and Job Context
When a service has a future effective date, it is not processed immediately. Instead, Activate Orchestrator processes it automatically on the effective date using a system-generated job.
This is expected behaviour.
Key implications:
- The job executing the service is not the original user-initiated job
- User request payloads are not available on the Job
- Job arguments may be empty or system-generated
Effective-dated services always run under a system job when processed by Activate Orchestrator.
Common Design Pitfalls
Relying on Job Context for Service Data
A common mistake is writing CustomScripts that extract provisioning data from the Job context.
Example of incorrect usage:
string userPrincipalName =
Job.Arguments.GetString("=//Job/services/instance/0/user[userprincipalname]");This approach may appear to work when:
- The service is processed immediately
- The script runs in the same job that captured the request
However, it fails when:
- The service is processed on its effective date
- The service is reprocessed
- The service runs under a system job
In these cases, the expected data is no longer present on the Job.
Referencing ServiceInstances by Index
Another error-prone pattern is referencing ServiceInstances by position within a job, for example:
This is unsafe because:
- A job may contain multiple ServiceInstances
- The order of services in the job is not guaranteed
- The service being processed may not be at the expected index
This can result in scripts reading data from the wrong service instance.
ServiceInstances should never be identified by index within a Job.
Correct Data Access Pattern
CustomScripts must retrieve all required data from the ServiceInstance or related objects, not from the Job.
Recommended examples:
string userPrincipalName = srv.User.UserPrincipalName;
string role = Evaluator.GetString(srv, "=/Data/role");Why this works:
- The ServiceInstance reflects the current state of the service
- Service data is populated consistently regardless of job type
- The script behaves the same for immediate and effective-dated processing
The ServiceInstance is the authoritative source of provisioning data for CustomScripts.
Using srv.Job (With Caution)
The ServiceInstance provides a reference to the most recent job that processed or otherwise interacted with it via srv.Job.
This can be useful for:
- Diagnostic logging
- Correlation identifiers
- Advanced scenarios where job metadata is explicitly required
However, this reference must be used sparingly.
A service may be:
- Reprocessed
- Updated
- Evaluated
- Included in background or system jobs
As a result, srv.Job may not represent the original request or the job you expect.
If data logically belongs to the service, retrieve it from the ServiceInstance rather than from srv.Job.
Why Scripts May Appear Not to Run
When a CustomScript relies on Job data, failures can be misleading.
From an operator’s perspective, it may appear that:
- The script did not run
- The CustomScript was skipped
In reality:
- The script was executed
- Required data was missing
- Internal logic did not complete as expected
Early return statements can further obscure this behaviour by preventing later logic and logging from executing.
Best Practices for CustomScript Design
Follow these guidelines when authoring CustomScripts:
- Always use the ServiceInstance (srv) for service-related data
- Avoid referencing Job arguments for provisioning logic
- Never identify ServiceInstances by index within a Job
- Use srv.Job only when job metadata is explicitly required
- Assume scripts may run under system jobs
- Design scripts to be idempotent where possible
- Avoid early return statements that bypass core logic
- Log clearly when required service data is missing
CustomScript Rules of Thumb
Use these rules as a quick check when writing or reviewing CustomScripts.
Data Ownership
- If the data belongs to the service, read it from the ServiceInstance
- If the data belongs to the user, read it from srv.User
- If the data belongs to the job, question whether you really need it
Most CustomScripts should not need to read from the Job at all.
Job Usage
- Do not rely on Job arguments for provisioning logic
- Expect services to run under system jobs, not user-initiated jobs
- Use srv.Job only for logging, diagnostics, or correlation metadata
- Never assume srv.Job represents the original request
Service Identification
- Never reference ServiceInstances by index within a Job
- Do not assume a service is at position 0 or any fixed index
- Always work with the ServiceInstance passed into the script
Execution Assumptions
- Assume scripts may run:
- On an effective date
- As part of reprocessing
- Under background or system jobs
- Write scripts that behave consistently regardless of execution context
Control Flow and Logging
- Avoid early return statements that bypass core logic
- Log clearly when required service data is missing
- Prefer explicit validation over silent failure
Mental Model
- The ServiceInstance is the unit of provisioning
- Jobs are an execution mechanism, not a data source
- Scripts should describe what to do for this service, not how the request was submitted
Summary
Effective-dated services are processed by Activate Orchestrator using system jobs. This is expected and by design.
To ensure CustomScripts run reliably:
- Treat the ServiceInstance as the authoritative source of service data
- Avoid Job-based assumptions in script logic
- Design scripts to function independently of how or when they are executed
Following these principles ensures consistent provisioning behaviour across immediate, scheduled, and system-initiated service processing.