Script API reference#
This is the reference for what a Reportworq .csx script can reach. For how to attach one to a job, see The Script Runner.
Script file format#
Scripts are plain C# statements. No class, namespace or Main method is required, top-level code runs directly, and await is fully supported.
// Minimal example. No using statements required for the pre-imported namespaces.
var sheet = Workbook.Worksheets["Summary"];
sheet.Cells["A1"].PutValue("Generated by Reportworq");
sheet.Cells["A2"].PutValue(DateTime.Now.ToString("yyyy-MM-dd HH:mm"));
sheet.AutoFitColumns();
Logger.LogInformation("Summary stamped for {OutputName}.", OutputName);
Compilation environment#
These namespaces are pre-imported, so you do not need using statements for them:
SystemSystem.LinqSystem.Collections.GenericMicrosoft.Extensions.LoggingAspose.CellsAspose.Slides
Anything else needs an explicit using at the top of the script, for example System.IO or System.Net.Http.
These assemblies are explicitly referenced: Aspose.Cells, Aspose.Slides, Microsoft.Extensions.Logging, the .NET base library, and System.Linq. Every other assembly already loaded by the Reportworq process is also reachable with a using.
NuGet packages cannot be added at run time.
Globals available in every script#
| Name | Type | Notes |
|---|---|---|
JobName |
string |
Name of the distribution job |
OutputName |
string |
Tokenised output file name for the current runtime step |
Parameters |
IReadOnlyDictionary<string, string> |
Resolved job parameter values |
HookName |
string |
The hook currently firing, for example "AfterCalculation" |
EntryName |
string |
This script's label, for example "#1 validate_totals.csx" |
Logger |
ILogger |
Routes into the job history log |
Hook-dependent objects#
| Name | Type | Non-null in |
|---|---|---|
Workbook |
Aspose.Cells.Workbook |
Before Calculation, After Calculation, After Packet Generation |
Presentation |
Aspose.Slides.Presentation |
After PowerPoint |
JobResult |
ScriptJobResult |
After Send |
All three are null in every other hook. A script attached to more than one hook must check before use:
if (Workbook == null)
{
AddWarning("No workbook at this hook, skipping.");
return;
}
To branch on the current hook, read HookName:
if (HookName == "AfterCalculation")
{
// workbook work
}
else if (HookName == "AfterSend")
{
// check JobResult
}
Logging and control flow#
| Member | Effect |
|---|---|
Logger.LogInformation / LogWarning / LogError |
Writes to the job history log with the [Script/Hook/#N file.csx] prefix |
AddWarning(string message) |
Logs a warning. Execution continues. |
AddError(string message) |
Logs an error and immediately halts the job. No further scripts and no further pipeline steps run. |
AddError is the mechanism for script-controlled validation that must stop the pipeline. It throws, so nothing after it in your script runs either.
Calling AddError in After Send is valid but cannot undo a send. The files have already gone out. It records the failure in the job log; it does not recall anything.
Shared state across hooks#
One JobState dictionary is allocated per job and threaded into every script at every hook. A value written in Before Calculation on report 2 is readable in After Send.
| Method | Signature | Behavior |
|---|---|---|
SetValue |
void SetValue(string key, object value) |
Stores a value. A null or empty key is silently ignored. A null value is stored but reads as absent. |
GetValue<T> |
T GetValue<T>(string key, T defaultValue) |
Returns the value if present and assignable to T, otherwise the default. Strict type match, no coercion: store an int, ask for a long, get the default. Never throws. |
HasValue |
bool HasValue(string key) |
True only for a stored non-null value. |
RemoveValue |
void RemoveValue(string key) |
Silent no-op when the key is missing. |
// In a Before Calculation script:
SetValue("report_start", DateTime.UtcNow);
// In an After Send script, a different row in the same job:
var started = GetValue<DateTime>("report_start", DateTime.MinValue);
Logger.LogInformation("Job took {Elapsed}.", DateTime.UtcNow - started);
Thread-safe file append#
AppendLineWithRetry writes one line to a file, tolerating transient IOException. Use it instead of File.AppendAllLines whenever concurrent burst job steps might write to the same file.
void AppendLineWithRetry(
string path,
string line,
int maxAttempts = 8,
int initialDelayMs = 25)
It retries with jittered exponential backoff, per-attempt delays start at 25 ms and double, capped at one second each. It opens the file with read sharing so a tailing reader does not block it. The final attempt lets the exception escape, so a genuinely stuck file still surfaces as an error.
// After Send, safe for concurrent burst steps writing to a shared CSV
var line = $"{DateTime.UtcNow:O},{JobName},{OutputName},{JobResult.Success}";
AppendLineWithRetry(@"\\server\logs\reportworq_audit.csv", line);
The After Send job result#
JobResult is populated only in After Send. All properties are read-only.
| Property | Type | Meaning |
|---|---|---|
Success |
bool |
Every distribution step completed without error |
HasCalcWarnings |
bool |
Any report sheet had calculation warnings |
OutputFiles |
IReadOnlyList<string> |
Names of all output files prepared for distribution |
Errors |
IReadOnlyList<string> |
Errors collected during the job, empty on success |
StartTime |
DateTime? |
UTC job start |
EndTime |
DateTime? |
UTC job end |
Elapsed |
TimeSpan |
Total elapsed time |
Status |
string |
For example Complete, Failed, Cancelled |
StepName |
string |
Runtime step name. Includes the contact name on a burst job. |
if (!JobResult.Success)
{
var errors = string.Join("; ", JobResult.Errors);
Logger.LogWarning("Job failed in {Elapsed:g}: {Errors}", JobResult.Elapsed, errors);
}
else
{
Logger.LogInformation("Job succeeded in {Elapsed:g}. Files: {Files}",
JobResult.Elapsed, string.Join(", ", JobResult.OutputFiles));
}
Error handling#
| Scenario | Behavior |
|---|---|
AddError called in any hook |
Job halts immediately, remaining scripts and pipeline steps skipped, error recorded in job history |
AddError called in After Send |
Same, but the sends already happened |
| Unhandled runtime exception | Wrapped and propagated, halting the job |
| Compilation error | Thrown before execution begins, job halts |
| Job canceled | Cancellation propagates, job marked canceled, scripts stop at their next await |
| Any other exception in After Send | Caught and logged, job result recording continues |
Script C# is not quite file C##
A .csx is compiled as a script, not as a source file, and the grammar differs in one place that bites immediately.
You cannot use a top-level using declaration. At the start of a statement, the script parser reads using as a using directive, so the C# 8 form fails to compile:
using var http = new HttpClient(); // does NOT compile in a .csx
The parser binds using var as a directive importing a namespace called var, then expects ; and finds the variable name instead, reporting CS1002 at the identifier. Adding a semicolon does not help, because the semicolon is not what is missing.
Write it as a using statement, or drop the using entirely:
using (var http = new HttpClient()) // compiles
{
// ...
}
var http = new HttpClient(); // also compiles
Using directives at the top of the file are fine, and are how you reach namespaces that are not pre-imported. It is only the inline using var x = ... declaration form that the script grammar rejects.
Cell is ambiguous#
Both Aspose.Cells and Aspose.Slides are pre-imported, and each defines a type called Cell. Using the bare name fails to compile:
foreach (Cell cell in sheet.Cells) // does NOT compile: CS0104, ambiguous
foreach (Aspose.Cells.Cell cell in sheet.Cells) // compiles
Qualify it. The same applies to any other type name the two libraries share.
Cells is a non-generic collection#
Worksheet.Cells implements the non-generic IEnumerable, so var in a foreach over it binds as object and members like .IsFormula and .Value will not resolve. State the element type explicitly, qualified as above.
HTTP calls#
System.Net.Http.HttpClient is available. The hook blocks until the call completes, so always set a timeout.
using System.Net.Http;
using (var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) })
{
var response = await http.PostAsync(url, content);
}
Note the using (...) statement form rather than using var, for the reason above.
Log format#
Each execution is bracketed by the runner:
[Script/AfterCalculation/#1 validate.csx] Executing script for job 'Monthly Sales', output 'Monthly Sales - East'.
[Script/AfterCalculation/#1 validate.csx] Script completed in 42 ms.
AddWarning and AddError use the same prefix:
[Script/AfterCalculation/#1 validate.csx] Summary!B2 is zero or blank, job halted.
Entries appear in the job history log for that job step.
Related pages#
- The Script Runner for configuration, licensing and the security posture.
- Example scripts for working code against every hook.
Feedback on this page
Comments, questions, requests, or something missing or unclear? Email us - the page you are on is filled in for you.
Email feedback on this pageOr write to support@reportworq.com directly.