A run_script command was judged solely on PowerShell's exit code, and PowerShell treats most errors as non-terminating: it writes the error, runs the next line, and still exits zero. The account-provisioning script reported succeeded having provisioned nothing, and because no output was captured there was no evidence to contradict the verdict. Scripts now carry an errormode. Strict prepends $ErrorActionPreference = 'Stop' so the first error ends the run and the exit code means what it says. It is opt-in per script rather than global because bodies authored against the permissive semantics rely on them - tolerating a failing cleanup, probing with a cmdlet that errors when a thing is absent, looping past a bad item - and would start aborting midway, leaving endpoints half-configured. The default is unchanged, so every existing script keeps running exactly as before. The prologue is applied after digest verification, so the SHA-256 still validates the bytes the server delivered. stdout/stderr is captured on both paths: redirected streams for SYSTEM, a transcript file under the ShopFloor profile for user-session runs, since CreateProcessAsUser cannot inherit the service's pipes across the session boundary. Output is always logged locally and returned to the server only on failure, so routine output stays on the machine that produced it instead of filling command history. Also adds the optional per-script working directory, which the same surfaces needed, and a collapsible explainer on the scripts page - the dropdown is not self-explanatory, and an operator who does not know about non-terminating errors has no reason to ever leave the default. Agent 1.14.0. An older agent ignoring errormode keeps today's behaviour.
47 lines
1.7 KiB
Python
47 lines
1.7 KiB
Python
"""Add scripts errormode
|
|
|
|
Backs the per-script "how should a non-terminating error be treated" field.
|
|
|
|
PowerShell invoked with -File exits 0 whenever it reaches the end of the body,
|
|
even if every meaningful line inside wrote an error. The agent derives the
|
|
command verdict from that exit code, so a script could report SUCCEEDED while
|
|
doing none of its work - which is exactly how an account-provisioning script
|
|
came back green with the account only partly built.
|
|
|
|
NOT NULL with server_default 'default', so every script row that existed before
|
|
this migration keeps today's permissive behaviour exactly. Strict is opt-in per
|
|
script and never retroactive: turning it on globally would change the meaning of
|
|
bodies that were authored under the permissive rule and deliberately tolerate a
|
|
failing Remove-Item, probe with Get-LocalUser, or loop past a bad item. Those
|
|
would begin aborting partway through, which is worse than a false green because
|
|
it leaves the endpoint half-configured.
|
|
|
|
The value is validated in shopdb/core/api/scripts.py where a rejection can
|
|
explain itself, not in the schema where it would only produce an IntegrityError.
|
|
|
|
Revision ID: 7d49_script_errormode
|
|
Revises: 7d48_script_workingdir
|
|
Create Date: 2026-09-09
|
|
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
revision = '7d49_script_errormode'
|
|
down_revision = '7d48_script_workingdir'
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade():
|
|
op.add_column('scripts', sa.Column(
|
|
'errormode', sa.String(length=16), nullable=False,
|
|
server_default='default',
|
|
comment="'default' keeps PowerShell's permissive behaviour; "
|
|
"'strict' stops on the first error so the exit code is honest"))
|
|
|
|
|
|
def downgrade():
|
|
op.drop_column('scripts', 'errormode')
|