akm-cli 0.9.17-alpha.3 → 0.9.17-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +731 -0
- package/dist/akm +94 -196
- package/dist/cli/shared.js +6 -2
- package/dist/cli.js +22 -9
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +2 -2
- package/dist/commands/health/checks.js +30 -75
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +0 -38
- package/dist/commands/health/improve-metrics.js +123 -562
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +11 -106
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -73
- package/dist/commands/health.js +122 -143
- package/dist/commands/improve/consolidate/chunking.js +25 -100
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +538 -1075
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1075
- package/dist/commands/improve/eligibility.js +126 -400
- package/dist/commands/improve/execution.js +3 -5
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +29 -166
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +12 -7
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +443 -1063
- package/dist/commands/improve/ledger.js +114 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +459 -1172
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +14 -118
- package/dist/commands/improve/memory/memory-improve.js +4 -3
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -10
- package/dist/commands/improve/preparation.js +851 -2339
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +621 -1367
- package/dist/commands/improve/salience.js +46 -232
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +323 -0
- package/dist/commands/proposal/drain.js +251 -644
- package/dist/commands/proposal/proposal-cli.js +3 -18
- package/dist/commands/proposal/proposal-types.js +20 -41
- package/dist/commands/proposal/proposal.js +1 -2
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +502 -1487
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +13 -89
- package/dist/commands/read/curate.js +63 -413
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +2 -13
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +7 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +2 -11
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/stash-cli.js +1 -1
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +9 -8
- package/dist/commands/tasks/tasks.js +326 -930
- package/dist/commands/tasks/validate.js +42 -21
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +0 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +11 -12
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +291 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +1 -33
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +163 -68
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +21 -95
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-lock.js +76 -173
- package/dist/core/logs-db.js +2 -2
- package/dist/core/paths.js +0 -27
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +108 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +293 -1012
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/graph-db.js +106 -46
- package/dist/indexer/ensure-index.js +44 -85
- package/dist/indexer/graph/graph-extraction.js +340 -562
- package/dist/indexer/graph/graph-related.js +130 -0
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +139 -151
- package/dist/indexer/indexer.js +524 -846
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +81 -90
- package/dist/indexer/passes/metadata.js +132 -200
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +1 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +181 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +2 -7
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +10 -15
- package/dist/llm/graph-extract.js +3 -12
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/metadata-enhance.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +2 -34
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +60 -23
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +1 -2
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +43937 -93313
- package/dist/scripts/akm-migrate.js +43697 -93071
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +0 -17
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +168 -0
- package/dist/storage/repositories/index-connection.js +34 -70
- package/dist/storage/repositories/index-entries-repository.js +69 -111
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +83 -269
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +192 -220
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +35 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +34 -27
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +129 -277
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +125 -745
- package/dist/tasks/backends/schtasks.js +101 -620
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/scheduler-binding.js +18 -238
- package/dist/tasks/scheduler-invocation.js +52 -52
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +361 -751
- package/dist/tasks/source/parse-task-source.js +160 -10
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +2 -2
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +41 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
- package/docs/reference/cli.md +182 -125
- package/docs/reference/configuration.md +49 -56
- package/docs/reference/data-and-telemetry.md +19 -20
- package/docs/reference/tasks.md +86 -38
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +87 -406
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -146
- package/dist/commands/improve/distill/promote-memory.js +0 -329
- package/dist/commands/improve/distill/quality-gate.js +0 -500
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -21
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/config/retired-experimental-keys-shim.js +0 -62
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -441
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -78
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -6,25 +6,24 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Each task is registered under the `\akm\` Task Scheduler folder so the
|
|
8
8
|
* backend never touches user-managed tasks. The full task definition is
|
|
9
|
-
* sent through `schtasks /Create /TN \akm\<id> /XML <path
|
|
9
|
+
* sent through `schtasks /Create /TN \akm\<id> /XML <path> /F` so we can
|
|
10
10
|
* express triggers/principals/actions without quoting hell.
|
|
11
11
|
*
|
|
12
12
|
* Platform notes:
|
|
13
13
|
* • `LogonType=InteractiveToken` means the task runs in the context of
|
|
14
14
|
* the registering user only when they are logged in — there is no
|
|
15
15
|
* stored password and the task will not fire at the lock screen.
|
|
16
|
-
* • `<Principal>` records the current user SID
|
|
17
|
-
*
|
|
16
|
+
* • `<Principal>` records the current user SID; it is part of the
|
|
17
|
+
* fingerprint in `<Source>`, so a sync run by another user re-registers
|
|
18
|
+
* the task under that user.
|
|
18
19
|
* • `<DisallowStartIfOnBatteries>false</…>` and `<StopIfGoingOnBatteries>
|
|
19
|
-
* false</…>` allow the task to run on battery
|
|
20
|
-
*
|
|
21
|
-
* • `MultipleInstancesPolicy=IgnoreNew` makes overlapping triggers safe:
|
|
22
|
-
* while a task is still running, a new fire is dropped rather than
|
|
23
|
-
* queued or run in parallel.
|
|
20
|
+
* false</…>` allow the task to run on battery.
|
|
21
|
+
* • `MultipleInstancesPolicy=IgnoreNew` makes overlapping triggers safe.
|
|
24
22
|
* • `/Query /FO CSV /NH` (without `/V`) outputs three columns:
|
|
25
23
|
* `TaskName,Next Run Time,Status` — so the regex anchors on the task
|
|
26
|
-
* name as the leading quoted field.
|
|
27
|
-
*
|
|
24
|
+
* name as the leading quoted field.
|
|
25
|
+
* • Task Scheduler runs a task with the account's own environment, so
|
|
26
|
+
* PATH is not carried anywhere; the descriptor holds directories only.
|
|
28
27
|
*
|
|
29
28
|
* Tests inject a fake exec + filesystem.
|
|
30
29
|
*/
|
|
@@ -37,143 +36,56 @@ import { ConfigError } from "../../core/errors.js";
|
|
|
37
36
|
import { getTaskLogDir } from "../../core/paths.js";
|
|
38
37
|
import { resolveAkmInvocation } from "../resolve-akm-bin.js";
|
|
39
38
|
import { parseSchedule, translateToSchtasks } from "../schedule.js";
|
|
40
|
-
import {
|
|
39
|
+
import { schedulerBindingNativeId, schedulerLogicalBindingId } from "../scheduler-binding.js";
|
|
41
40
|
import { buildScheduledBindingInvocation, parseScheduledBindingArgv, resolveScheduledTaskContext, schedulerContextDescriptor, schedulerContextPath, } from "../scheduler-invocation.js";
|
|
42
41
|
import { escapeXml, nodeExec, nodeFs, normalizeXmlForUtf16File, runOrThrow, } from "./exec-utils.js";
|
|
43
42
|
export const DEFAULT_FOLDER_PREFIX = "\\akm\\";
|
|
44
43
|
const SIGNATURE_PREFIX = "akm:v1:";
|
|
45
|
-
const SCHTASKS_SNAPSHOT = Symbol("akm-schtasks-binding-snapshot");
|
|
46
44
|
export function SCHTASKS_BACKEND(options = {}) {
|
|
47
|
-
const exec = options.exec ??
|
|
45
|
+
const exec = options.exec ?? nodeExec();
|
|
48
46
|
const fsLike = options.fs ?? defaultSchtasksFs();
|
|
49
47
|
const akmArgv = options.akmArgv ?? resolveAkmInvocation().argv;
|
|
50
48
|
const logDir = options.logDir ?? getTaskLogDir();
|
|
51
49
|
const folder = options.folderPrefix ?? DEFAULT_FOLDER_PREFIX;
|
|
52
50
|
const scheduledContext = options.scheduledContext ?? resolveScheduledTaskContext();
|
|
53
|
-
const defaultContextPath = schedulerContextPath(schedulerContextDescriptor(scheduledContext
|
|
51
|
+
const defaultContextPath = schedulerContextPath(schedulerContextDescriptor(scheduledContext));
|
|
54
52
|
const userSid = options.userSid ?? resolveCurrentUserSid(exec);
|
|
55
53
|
const taskName = (nativeId) => `${folder}${nativeId}`;
|
|
54
|
+
const xmlFor = (task, opts) => buildSchtasksXml(task, akmArgv, logDir, {
|
|
55
|
+
folderPrefix: folder,
|
|
56
|
+
contextPath: opts?.contextPath ?? defaultContextPath,
|
|
57
|
+
userSid,
|
|
58
|
+
binding: [...(opts?.binding ?? akmArgv)],
|
|
59
|
+
});
|
|
60
|
+
const queryXml = (nativeId) => runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(nativeId), "/XML"], {
|
|
61
|
+
message: (result) => `schtasks /Query /XML for "${taskName(nativeId)}" failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
62
|
+
}).stdout;
|
|
56
63
|
return {
|
|
57
64
|
name: "schtasks",
|
|
58
|
-
install(task, opts
|
|
59
|
-
|
|
60
|
-
assertSchedulerExpectationIdentity(expected, task);
|
|
65
|
+
install(task, opts) {
|
|
66
|
+
const xml = normalizeXmlForUtf16File(xmlFor(task, opts));
|
|
61
67
|
const nativeId = schedulerBindingNativeId(task);
|
|
62
|
-
const xml = normalizeXmlForUtf16File(buildSchtasksXml(task, akmArgv, logDir, {
|
|
63
|
-
folderPrefix: folder,
|
|
64
|
-
contextPath: opts?.contextPath ?? defaultContextPath,
|
|
65
|
-
userSid,
|
|
66
|
-
binding: [...(opts?.binding ?? akmArgv)],
|
|
67
|
-
...(opts?.target !== undefined ? { target: opts.target } : {}),
|
|
68
|
-
}));
|
|
69
|
-
const initialArtifact = expected
|
|
70
|
-
? assertSchedulerNativeArtifactCardinality(inspectSchtasksState(exec, folder, taskName).artifacts, nativeId, expected.state === "absent" ? 0 : 1)
|
|
71
|
-
: undefined;
|
|
72
|
-
const existingNativeId = expected ? initialArtifact?.nativeId : nativeId;
|
|
73
|
-
const query = existingNativeId
|
|
74
|
-
? runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(existingNativeId), "/XML"], {
|
|
75
|
-
isOk: (r) => r.status === 0 || isMissingTaskResult(r),
|
|
76
|
-
message: (r) => `schtasks /Query failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
|
|
77
|
-
})
|
|
78
|
-
: { status: 1, stdout: "", stderr: "missing" };
|
|
79
|
-
if (expected) {
|
|
80
|
-
assertSchedulerMutationArtifact(query.status === 0 && existingNativeId ? schtasksArtifact(existingNativeId, query.stdout) : undefined, expected);
|
|
81
|
-
}
|
|
82
|
-
let previous;
|
|
83
|
-
if (query.status === 0) {
|
|
84
|
-
if (!expected) {
|
|
85
|
-
assertSchedulerNativeArtifactOwner(nativeId, task, extractSchtasksInvocation(query.stdout)?.invocation);
|
|
86
|
-
}
|
|
87
|
-
const enabled = taskXmlEnabled(query.stdout);
|
|
88
|
-
if (enabled === undefined) {
|
|
89
|
-
throw new ConfigError(`schtasks /Query returned an unreadable definition for "${taskName(nativeId)}"; refusing to replace it.`, "INVALID_CONFIG_FILE");
|
|
90
|
-
}
|
|
91
|
-
previous = { xml: normalizeXmlForUtf16File(query.stdout), enabled };
|
|
92
|
-
}
|
|
93
68
|
fsLike.ensureDir(logDir);
|
|
94
69
|
const tmpFile = path.join(fsLike.tmpdir(), `akm-task-${nativeId}-${Date.now()}.xml`);
|
|
95
70
|
fsLike.writeFile(tmpFile, xml);
|
|
96
71
|
try {
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
})
|
|
105
|
-
: undefined;
|
|
106
|
-
assertSchedulerMutationArtifact(current?.status === 0 && finalNativeId ? schtasksArtifact(finalNativeId, current.stdout) : undefined, expected);
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
assertSchtasksArtifactUnchanged(exec, taskName(nativeId), nativeId, task);
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
// /F forces overwrite if a task with the same name exists.
|
|
113
|
-
runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(nativeId), "/XML", tmpFile, "/F"], {
|
|
114
|
-
message: (r) => `schtasks /Create failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
|
|
72
|
+
// /F forces overwrite if a task with the same name exists.
|
|
73
|
+
runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(nativeId), "/XML", tmpFile, "/F"], {
|
|
74
|
+
message: (r) => `schtasks /Create failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
|
|
75
|
+
});
|
|
76
|
+
if (!task.enabled) {
|
|
77
|
+
runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), "/DISABLE"], {
|
|
78
|
+
message: (r) => `schtasks /Change /DISABLE failed: ${r.stderr || r.stdout || "no output"}.`,
|
|
115
79
|
});
|
|
116
|
-
if (!task.enabled) {
|
|
117
|
-
runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), "/DISABLE"], {
|
|
118
|
-
message: (r) => `schtasks /Change /DISABLE failed: ${r.stderr || r.stdout || "no output"}.`,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
const rollbackErrors = [];
|
|
124
|
-
if (previous === undefined) {
|
|
125
|
-
try {
|
|
126
|
-
const remove = exec.run(["schtasks", "/Delete", "/TN", taskName(nativeId), "/F"]);
|
|
127
|
-
if (remove.status !== 0 && !isMissingTaskResult(remove)) {
|
|
128
|
-
rollbackErrors.push(new ConfigError(`schtasks /Delete during rollback failed: ${remove.stderr || remove.stdout || "no output"}.`, "INVALID_CONFIG_FILE"));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
catch (rollbackError) {
|
|
132
|
-
rollbackErrors.push(rollbackError);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
try {
|
|
137
|
-
fsLike.writeFile(tmpFile, previous.xml);
|
|
138
|
-
runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(nativeId), "/XML", tmpFile, "/F"], {
|
|
139
|
-
message: (r) => `schtasks /Create during rollback failed: ${r.stderr || r.stdout || "no output"}.`,
|
|
140
|
-
});
|
|
141
|
-
const stateFlag = previous.enabled ? "/ENABLE" : "/DISABLE";
|
|
142
|
-
runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), stateFlag], {
|
|
143
|
-
message: (r) => `schtasks /Change ${stateFlag} during rollback failed: ${r.stderr || r.stdout || "no output"}.`,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
catch (rollbackError) {
|
|
147
|
-
rollbackErrors.push(rollbackError);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (rollbackErrors.length > 0) {
|
|
151
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
152
|
-
throw new AggregateError([err, ...rollbackErrors], `${message}; rollback for Task Scheduler task "${task.id}" was incomplete.`);
|
|
153
|
-
}
|
|
154
|
-
throw err;
|
|
155
80
|
}
|
|
156
81
|
}
|
|
157
82
|
finally {
|
|
158
83
|
fsLike.removeFile(tmpFile);
|
|
159
84
|
}
|
|
160
85
|
},
|
|
161
|
-
uninstall(nativeId
|
|
162
|
-
if (expected) {
|
|
163
|
-
assertSchedulerExpectationIdentity({ ...expected, state: "present" });
|
|
164
|
-
const artifact = assertSchedulerNativeArtifactCardinality(inspectSchtasksState(exec, folder, taskName).artifacts, nativeId, 1);
|
|
165
|
-
const currentNativeId = artifact?.nativeId ?? nativeId;
|
|
166
|
-
const current = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(currentNativeId), "/XML"], {
|
|
167
|
-
isOk: (result) => result.status === 0 || isMissingTaskResult(result),
|
|
168
|
-
message: (result) => `schtasks /Query failed during final removal ownership check (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
169
|
-
});
|
|
170
|
-
if (current.status !== 0) {
|
|
171
|
-
throw new ConfigError(`Task Scheduler artifact ${JSON.stringify(nativeId)} disappeared after coherent inspection.`, "INVALID_CONFIG_FILE");
|
|
172
|
-
}
|
|
173
|
-
assertSchedulerRemovalArtifact(currentNativeId, expected, extractSchtasksInvocation(current.stdout)?.invocation, installedSignature(current.stdout));
|
|
174
|
-
}
|
|
86
|
+
uninstall(nativeId) {
|
|
175
87
|
runOrThrow(exec, ["schtasks", "/Delete", "/TN", taskName(nativeId), "/F"], {
|
|
176
|
-
isOk: (r) => r.status === 0 ||
|
|
88
|
+
isOk: (r) => r.status === 0 || isMissingTaskResult(r),
|
|
177
89
|
message: (r) => `schtasks /Delete failed: ${r.stderr || r.stdout || "no output"}.`,
|
|
178
90
|
});
|
|
179
91
|
},
|
|
@@ -184,219 +96,38 @@ export function SCHTASKS_BACKEND(options = {}) {
|
|
|
184
96
|
});
|
|
185
97
|
},
|
|
186
98
|
list() {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
99
|
+
const listing = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
|
|
100
|
+
message: (result) => `schtasks /Query failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
101
|
+
});
|
|
102
|
+
const rows = [];
|
|
103
|
+
for (const line of (listing.stdout ?? "").split(/\r?\n/)) {
|
|
104
|
+
const name = line.match(/^"([^"]+)",/)?.[1];
|
|
105
|
+
if (!name?.startsWith(folder))
|
|
106
|
+
continue;
|
|
107
|
+
const nativeId = name.slice(folder.length);
|
|
108
|
+
const xml = queryXml(nativeId);
|
|
109
|
+
const parsed = extractSchtasksInvocation(xml);
|
|
110
|
+
if (!parsed)
|
|
111
|
+
continue;
|
|
112
|
+
rows.push({
|
|
113
|
+
id: schedulerLogicalBindingId(nativeId, parsed.invocation),
|
|
114
|
+
nativeId,
|
|
115
|
+
enabled: taskXmlEnabled(xml),
|
|
116
|
+
signature: taskXmlSignature(xml),
|
|
117
|
+
...(parsed.target !== undefined ? { target: parsed.target } : {}),
|
|
118
|
+
binding: parsed.binding,
|
|
119
|
+
contextPath: parsed.contextPath,
|
|
120
|
+
invocation: parsed.invocation,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return rows;
|
|
203
124
|
},
|
|
204
125
|
expectedSignature(task, opts) {
|
|
205
|
-
|
|
206
|
-
folderPrefix: folder,
|
|
207
|
-
contextPath: opts?.contextPath ?? defaultContextPath,
|
|
208
|
-
userSid,
|
|
209
|
-
binding: [...(opts?.binding ?? akmArgv)],
|
|
210
|
-
...(opts?.target !== undefined ? { target: opts.target } : {}),
|
|
211
|
-
}));
|
|
212
|
-
if (signature === undefined)
|
|
213
|
-
throw new Error("Failed to fingerprint generated Task Scheduler XML.");
|
|
214
|
-
return signature;
|
|
126
|
+
return taskXmlSignature(xmlFor(task, opts));
|
|
215
127
|
},
|
|
216
128
|
};
|
|
217
129
|
}
|
|
218
|
-
|
|
219
|
-
const result = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
|
|
220
|
-
message: (query) => `schtasks /Query failed (exit ${query.status}): ${query.stderr || query.stdout || "no output"}.`,
|
|
221
|
-
});
|
|
222
|
-
const refs = [];
|
|
223
|
-
for (const line of (result.stdout ?? "").split(/\r?\n/)) {
|
|
224
|
-
const name = line.match(/^"([^"]+)",/)?.[1];
|
|
225
|
-
if (!name?.startsWith(folder))
|
|
226
|
-
continue;
|
|
227
|
-
const id = name.slice(folder.length);
|
|
228
|
-
const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(id), "/XML"], {
|
|
229
|
-
message: (current) => `schtasks /Query /XML for "${taskName(id)}" failed (exit ${current.status}): ${current.stderr || current.stdout || "no output"}.`,
|
|
230
|
-
});
|
|
231
|
-
const signature = installedSignature(query.stdout);
|
|
232
|
-
const installed = extractSchtasksInvocation(query.stdout);
|
|
233
|
-
const ref = {
|
|
234
|
-
id: installed ? schedulerLogicalBindingId(id, installed.invocation) : id,
|
|
235
|
-
...(signature !== undefined ? { signature } : {}),
|
|
236
|
-
...(installed?.target !== undefined ? { target: installed.target } : {}),
|
|
237
|
-
};
|
|
238
|
-
Object.defineProperty(ref, "nativeId", { value: id });
|
|
239
|
-
if (installed)
|
|
240
|
-
Object.defineProperty(ref, "invocation", { value: Object.freeze([...installed.invocation]) });
|
|
241
|
-
refs.push(ref);
|
|
242
|
-
}
|
|
243
|
-
return refs;
|
|
244
|
-
}
|
|
245
|
-
function inspectSchtasksState(exec, folder, taskName) {
|
|
246
|
-
const listing = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
|
|
247
|
-
message: (result) => `schtasks /Query failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
248
|
-
});
|
|
249
|
-
const installed = [];
|
|
250
|
-
const artifacts = [];
|
|
251
|
-
for (const nativeId of schtasksNativeIds(listing.stdout, folder)) {
|
|
252
|
-
const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(nativeId), "/XML"], {
|
|
253
|
-
message: (result) => `schtasks /Query /XML for "${taskName(nativeId)}" failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
254
|
-
});
|
|
255
|
-
const artifact = schtasksArtifact(nativeId, query.stdout);
|
|
256
|
-
artifacts.push(artifact);
|
|
257
|
-
const parsed = extractSchtasksInvocation(query.stdout);
|
|
258
|
-
if (!parsed)
|
|
259
|
-
continue;
|
|
260
|
-
const enabled = taskXmlEnabled(query.stdout);
|
|
261
|
-
const ref = {
|
|
262
|
-
id: schedulerLogicalBindingId(nativeId, parsed.invocation),
|
|
263
|
-
...(enabled !== undefined ? { enabled } : {}),
|
|
264
|
-
...(artifact.fingerprint !== undefined ? { signature: artifact.fingerprint } : {}),
|
|
265
|
-
...(parsed.target !== undefined ? { target: parsed.target } : {}),
|
|
266
|
-
binding: parsed.binding,
|
|
267
|
-
contextPath: parsed.contextPath,
|
|
268
|
-
};
|
|
269
|
-
Object.defineProperty(ref, "nativeId", { value: nativeId });
|
|
270
|
-
Object.defineProperty(ref, "invocation", { value: Object.freeze([...parsed.invocation]) });
|
|
271
|
-
installed.push(ref);
|
|
272
|
-
}
|
|
273
|
-
return Object.freeze({ installed: Object.freeze(installed), artifacts: Object.freeze(artifacts) });
|
|
274
|
-
}
|
|
275
|
-
function schtasksNativeIds(stdout, folder) {
|
|
276
|
-
const ids = [];
|
|
277
|
-
for (const line of (stdout ?? "").split(/\r?\n/)) {
|
|
278
|
-
const match = line.match(/^"([^"]+)",/);
|
|
279
|
-
const name = match?.[1];
|
|
280
|
-
if (name?.startsWith(folder))
|
|
281
|
-
ids.push(name.slice(folder.length));
|
|
282
|
-
}
|
|
283
|
-
return ids;
|
|
284
|
-
}
|
|
285
|
-
function schtasksArtifact(nativeId, xml) {
|
|
286
|
-
const parsed = extractSchtasksInvocation(xml);
|
|
287
|
-
const owner = parsed ? schedulerLogicalBindingOwner(nativeId, parsed.invocation) : undefined;
|
|
288
|
-
const artifact = parsed
|
|
289
|
-
? {
|
|
290
|
-
nativeId,
|
|
291
|
-
...(owner !== undefined ? { bindingId: owner } : {}),
|
|
292
|
-
invocation: Object.freeze([...parsed.invocation]),
|
|
293
|
-
}
|
|
294
|
-
: { nativeId };
|
|
295
|
-
const fingerprint = installedSignature(xml);
|
|
296
|
-
if (fingerprint !== undefined)
|
|
297
|
-
Object.defineProperty(artifact, "fingerprint", { value: fingerprint });
|
|
298
|
-
return artifact;
|
|
299
|
-
}
|
|
300
|
-
function snapshotSchtasksBindings(ids, exec, folder, taskName) {
|
|
301
|
-
const listing = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
|
|
302
|
-
message: (result) => `schtasks /Query failed while snapshotting bindings (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
303
|
-
});
|
|
304
|
-
const requestedKeys = new Set(ids.map(schedulerNativeArtifactKey));
|
|
305
|
-
const enumeratedIds = new Set(ids);
|
|
306
|
-
for (const nativeId of schtasksNativeIds(listing.stdout, folder)) {
|
|
307
|
-
if (requestedKeys.has(schedulerNativeArtifactKey(nativeId)))
|
|
308
|
-
enumeratedIds.add(nativeId);
|
|
309
|
-
}
|
|
310
|
-
const entries = [...enumeratedIds].map((id) => {
|
|
311
|
-
const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(id), "/XML"], {
|
|
312
|
-
isOk: (result) => result.status === 0 || isMissingTaskResult(result),
|
|
313
|
-
message: (result) => `schtasks /Query failed while snapshotting "${taskName(id)}": ${result.stderr || result.stdout || "no output"}.`,
|
|
314
|
-
});
|
|
315
|
-
if (query.status !== 0)
|
|
316
|
-
return Object.freeze({ id });
|
|
317
|
-
const enabled = taskXmlEnabled(query.stdout);
|
|
318
|
-
if (enabled === undefined) {
|
|
319
|
-
throw new ConfigError(`schtasks /Query returned an unreadable definition while snapshotting "${taskName(id)}".`, "INVALID_CONFIG_FILE");
|
|
320
|
-
}
|
|
321
|
-
return Object.freeze({ id, xml: normalizeXmlForUtf16File(query.stdout), enabled });
|
|
322
|
-
});
|
|
323
|
-
const artifacts = entries.flatMap((entry) => entry.xml === undefined ? [] : [schtasksArtifact(entry.id, entry.xml)]);
|
|
324
|
-
return Object.freeze({
|
|
325
|
-
kind: SCHTASKS_SNAPSHOT,
|
|
326
|
-
nativeIds: Object.freeze([...ids]),
|
|
327
|
-
artifacts: Object.freeze(artifacts),
|
|
328
|
-
entries: Object.freeze(entries),
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
function restoreSchtasksBindings(snapshot, context, expectedCurrent) {
|
|
332
|
-
if (!isSchtasksBindingSnapshot(snapshot)) {
|
|
333
|
-
throw new ConfigError("Invalid Task Scheduler snapshot.", "INVALID_CONFIG_FILE");
|
|
334
|
-
}
|
|
335
|
-
const errors = [];
|
|
336
|
-
let rollbackInventory;
|
|
337
|
-
if (expectedCurrent) {
|
|
338
|
-
try {
|
|
339
|
-
rollbackInventory = inspectSchtasksState(context.exec, context.folder, context.taskName);
|
|
340
|
-
}
|
|
341
|
-
catch (error) {
|
|
342
|
-
errors.push(error);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
for (const entry of snapshot.entries) {
|
|
346
|
-
try {
|
|
347
|
-
if (expectedCurrent) {
|
|
348
|
-
if (!rollbackInventory)
|
|
349
|
-
continue;
|
|
350
|
-
const expected = expectedCurrent.find((candidate) => candidate.nativeId === entry.id);
|
|
351
|
-
if (!expected) {
|
|
352
|
-
throw new ConfigError(`Missing Task Scheduler rollback expectation for ${JSON.stringify(entry.id)}.`, "INVALID_CONFIG_FILE");
|
|
353
|
-
}
|
|
354
|
-
assertSchedulerRollbackArtifactCardinality(rollbackInventory.artifacts, expected);
|
|
355
|
-
}
|
|
356
|
-
restoreSchtasksEntry(entry, context);
|
|
357
|
-
}
|
|
358
|
-
catch (error) {
|
|
359
|
-
errors.push(error);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
if (errors.length > 0) {
|
|
363
|
-
throw new AggregateError(errors, `Failed to completely restore ${errors.length} Task Scheduler operation(s).`);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
function restoreSchtasksEntry(entry, context) {
|
|
367
|
-
if (entry.xml === undefined) {
|
|
368
|
-
runOrThrow(context.exec, ["schtasks", "/Delete", "/TN", context.taskName(entry.id), "/F"], {
|
|
369
|
-
isOk: (result) => result.status === 0 || isMissingTaskResult(result),
|
|
370
|
-
message: (result) => `schtasks /Delete failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
|
|
371
|
-
});
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
const tmpFile = path.join(context.fsLike.tmpdir(), `akm-task-restore-${schedulerNativeBindingId(entry.id)}-${Date.now()}.xml`);
|
|
375
|
-
context.fsLike.writeFile(tmpFile, entry.xml);
|
|
376
|
-
try {
|
|
377
|
-
runOrThrow(context.exec, ["schtasks", "/Create", "/TN", context.taskName(entry.id), "/XML", tmpFile, "/F"], {
|
|
378
|
-
message: (result) => `schtasks /Create failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
|
|
379
|
-
});
|
|
380
|
-
const stateFlag = entry.enabled === false ? "/DISABLE" : "/ENABLE";
|
|
381
|
-
runOrThrow(context.exec, ["schtasks", "/Change", "/TN", context.taskName(entry.id), stateFlag], {
|
|
382
|
-
message: (result) => `schtasks /Change ${stateFlag} failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
finally {
|
|
386
|
-
context.fsLike.removeFile(tmpFile);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
function isSchtasksBindingSnapshot(value) {
|
|
390
|
-
return (typeof value === "object" &&
|
|
391
|
-
value !== null &&
|
|
392
|
-
value.kind === SCHTASKS_SNAPSHOT &&
|
|
393
|
-
Array.isArray(value.entries));
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Recover the bundle name embedded as a `--bundle <bundle>` pair in the
|
|
397
|
-
* PowerShell `<Arguments>` of an installed Task Scheduler definition. Returns
|
|
398
|
-
* undefined for the primary/default form.
|
|
399
|
-
*/
|
|
130
|
+
/** The `--bundle <bundle>` token of an installed Task Scheduler definition; undefined for the primary form. */
|
|
400
131
|
export function extractSchtasksTarget(xml) {
|
|
401
132
|
return extractSchtasksInvocation(xml)?.target;
|
|
402
133
|
}
|
|
@@ -415,12 +146,10 @@ function findPowerShellInvocationOperator(script) {
|
|
|
415
146
|
for (let index = 0; index < script.length; index += 1) {
|
|
416
147
|
const char = script[index];
|
|
417
148
|
if (char === "'") {
|
|
418
|
-
if (inSingleQuote && script[index + 1] === "'")
|
|
149
|
+
if (inSingleQuote && script[index + 1] === "'")
|
|
419
150
|
index += 1;
|
|
420
|
-
|
|
421
|
-
else {
|
|
151
|
+
else
|
|
422
152
|
inSingleQuote = !inSingleQuote;
|
|
423
|
-
}
|
|
424
153
|
continue;
|
|
425
154
|
}
|
|
426
155
|
if (!inSingleQuote && char === "&" && /\s/.test(script[index + 1] ?? ""))
|
|
@@ -463,70 +192,32 @@ function parsePowerShellSingleQuotedArgs(script, start) {
|
|
|
463
192
|
}
|
|
464
193
|
return argv;
|
|
465
194
|
}
|
|
466
|
-
/**
|
|
467
|
-
* Re-verify ownership of the native artifact immediately before `/Create /F`
|
|
468
|
-
* overwrites it (the read/prepare-to-/Create ownership race at the native
|
|
469
|
-
* boundary).
|
|
470
|
-
*
|
|
471
|
-
* This used to also compare the fresh query against the first read taken
|
|
472
|
-
* earlier in `install` and refuse the whole operation if anything about the
|
|
473
|
-
* XML had changed in between -- a freshness re-check on top of the identity
|
|
474
|
-
* re-check right below it. That extra comparison never caught anything the
|
|
475
|
-
* owner check didn't already cover (a foreign/changed owner still fails
|
|
476
|
-
* `assertSchedulerNativeArtifactOwner`), and `task sync` is idempotent, so a
|
|
477
|
-
* spurious refusal here only cost the user a rerun of a command they'd
|
|
478
|
-
* already asked for. Dropped; the owner re-check (the actual corruption/
|
|
479
|
-
* clobber guard -- no fallback exists if a foreign task got silently
|
|
480
|
-
* overwritten) stays.
|
|
481
|
-
*/
|
|
482
|
-
function assertSchtasksArtifactUnchanged(exec, nativeTaskName, nativeId, task) {
|
|
483
|
-
// This runs outside install's rollback region: no scheduler mutation has
|
|
484
|
-
// occurred, so a raced owner must remain untouched rather than be restored
|
|
485
|
-
// from the stale first read.
|
|
486
|
-
const current = runOrThrow(exec, ["schtasks", "/Query", "/TN", nativeTaskName, "/XML"], {
|
|
487
|
-
isOk: (result) => result.status === 0 || isMissingTaskResult(result),
|
|
488
|
-
message: (result) => `schtasks /Query failed during final ownership check (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
489
|
-
});
|
|
490
|
-
if (current.status === 0) {
|
|
491
|
-
assertSchedulerNativeArtifactOwner(nativeId, task, extractSchtasksInvocation(current.stdout)?.invocation);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
195
|
export function buildSchtasksXml(task, akmArgv, logDir, options) {
|
|
495
196
|
const folder = options.folderPrefix ?? DEFAULT_FOLDER_PREFIX;
|
|
496
197
|
const now = options.now ? options.now() : new Date();
|
|
497
|
-
const
|
|
498
|
-
const
|
|
499
|
-
const
|
|
500
|
-
return schtasksTemplate
|
|
501
|
-
.replaceAll("{{TASK_ID}}", escapeXml(nativeId))
|
|
502
|
-
.replaceAll("{{FOLDER}}", escapeXml(folder))
|
|
503
|
-
.replace("{{SIGNATURE}}", definition.signature)
|
|
504
|
-
.replace("{{TRIGGER_XML}}", triggerXml)
|
|
505
|
-
.replace('<Principal id="Author">', `<Principal id="Author">\n <UserId>${escapeXml(options.userSid)}</UserId>`)
|
|
506
|
-
.replace("{{ENABLED}}", task.enabled ? "true" : "false")
|
|
507
|
-
.replace("{{COMMAND}}", escapeXml(definition.command))
|
|
508
|
-
.replace("{{ARGS}}", escapeXml(definition.args))
|
|
509
|
-
.replace("{{LOG_PATH}}", escapeXml(definition.logPath));
|
|
510
|
-
}
|
|
511
|
-
function buildSchtasksDefinition(task, akmArgv, logDir, folder, contextPath, userSid, _target) {
|
|
512
|
-
const spec = parseSchedule(task.cron, "schtasks");
|
|
513
|
-
const trigger = translateToSchtasks(spec);
|
|
514
|
-
const invocation = buildScheduledBindingInvocation(akmArgv, contextPath, task.invocation);
|
|
515
|
-
const invoke = `& ${invocation.argv.map((arg) => quotePowerShell(arg)).join(" ")}`;
|
|
516
|
-
const executionEvidence = task.executionEvidenceDigest === undefined
|
|
517
|
-
? ""
|
|
518
|
-
: `$null=${quotePowerShell(`akm-workflow-evidence:${assertSchedulerExecutionEvidenceDigest(task.executionEvidenceDigest)}`)}; `;
|
|
519
|
-
const script = `${executionEvidence}${invoke}; exit $LASTEXITCODE`;
|
|
198
|
+
const trigger = translateToSchtasks(parseSchedule(task.cron, "schtasks"));
|
|
199
|
+
const invocation = buildScheduledBindingInvocation(options.binding ?? akmArgv, options.contextPath, task.invocation);
|
|
200
|
+
const script = `& ${invocation.argv.map((arg) => quotePowerShell(arg)).join(" ")}; exit $LASTEXITCODE`;
|
|
520
201
|
const command = "powershell.exe";
|
|
521
202
|
const args = ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script].map(quoteArg).join(" ");
|
|
522
203
|
const nativeId = schedulerBindingNativeId(task);
|
|
523
204
|
const logPath = path.join(logDir, `${nativeId}.log`);
|
|
524
|
-
// The boundary changes on reinstall, and enabled state can change via
|
|
525
|
-
// Keep both outside the stored definition fingerprint so no-op
|
|
205
|
+
// The boundary changes on reinstall, and enabled state can change via
|
|
206
|
+
// /Change. Keep both outside the stored definition fingerprint so no-op
|
|
207
|
+
// sync stays stable.
|
|
526
208
|
const fingerprint = createHash("sha256")
|
|
527
|
-
.update(JSON.stringify({ folder, id: nativeId, trigger, command, args, logPath, userSid }))
|
|
209
|
+
.update(JSON.stringify({ folder, id: nativeId, trigger, command, args, logPath, userSid: options.userSid }))
|
|
528
210
|
.digest("hex");
|
|
529
|
-
return
|
|
211
|
+
return schtasksTemplate
|
|
212
|
+
.replaceAll("{{TASK_ID}}", escapeXml(nativeId))
|
|
213
|
+
.replaceAll("{{FOLDER}}", escapeXml(folder))
|
|
214
|
+
.replace("{{SIGNATURE}}", `${SIGNATURE_PREFIX}${fingerprint}`)
|
|
215
|
+
.replace("{{TRIGGER_XML}}", renderSchtasksTrigger(trigger, now))
|
|
216
|
+
.replace('<Principal id="Author">', `<Principal id="Author">\n <UserId>${escapeXml(options.userSid)}</UserId>`)
|
|
217
|
+
.replace("{{ENABLED}}", task.enabled ? "true" : "false")
|
|
218
|
+
.replace("{{COMMAND}}", escapeXml(command))
|
|
219
|
+
.replace("{{ARGS}}", escapeXml(args))
|
|
220
|
+
.replace("{{LOG_PATH}}", escapeXml(logPath));
|
|
530
221
|
}
|
|
531
222
|
function renderSchtasksTrigger(trigger, now) {
|
|
532
223
|
return expandNativeTriggers(trigger)
|
|
@@ -540,14 +231,7 @@ function expandNativeTriggers(trigger) {
|
|
|
540
231
|
case "minuteValues":
|
|
541
232
|
return trigger.minutes.map((atMinute) => ({ kind: "daily", atHour: 0, atMinute, repeatEveryMinutes: 60 }));
|
|
542
233
|
case "hour":
|
|
543
|
-
return [
|
|
544
|
-
{
|
|
545
|
-
kind: "daily",
|
|
546
|
-
atHour: 0,
|
|
547
|
-
atMinute: trigger.atMinute,
|
|
548
|
-
repeatEveryMinutes: trigger.everyHours * 60,
|
|
549
|
-
},
|
|
550
|
-
];
|
|
234
|
+
return [{ kind: "daily", atHour: 0, atMinute: trigger.atMinute, repeatEveryMinutes: trigger.everyHours * 60 }];
|
|
551
235
|
case "hourValues":
|
|
552
236
|
return trigger.hours.map((atHour) => ({ kind: "daily", atHour, atMinute: trigger.atMinute }));
|
|
553
237
|
case "daily":
|
|
@@ -563,8 +247,8 @@ function renderNativeTrigger(trigger, startBoundary) {
|
|
|
563
247
|
const repetition = trigger.repeatEveryMinutes === undefined
|
|
564
248
|
? ""
|
|
565
249
|
: ` <Repetition>
|
|
566
|
-
<Interval>${
|
|
567
|
-
<Duration>${
|
|
250
|
+
<Interval>${formatMinuteDuration(trigger.repeatEveryMinutes)}</Interval>
|
|
251
|
+
<Duration>${formatMinuteDuration(24 * 60 - trigger.repeatEveryMinutes)}</Duration>
|
|
568
252
|
<StopAtDurationEnd>false</StopAtDurationEnd>
|
|
569
253
|
</Repetition>
|
|
570
254
|
`;
|
|
@@ -617,18 +301,10 @@ ${months}
|
|
|
617
301
|
</ScheduleByMonth>
|
|
618
302
|
</CalendarTrigger>`;
|
|
619
303
|
}
|
|
620
|
-
function formatRepetitionInterval(minutes) {
|
|
621
|
-
return formatMinuteDuration(minutes);
|
|
622
|
-
}
|
|
623
|
-
function formatRepetitionDuration(intervalMinutes) {
|
|
624
|
-
return formatMinuteDuration(24 * 60 - intervalMinutes);
|
|
625
|
-
}
|
|
626
304
|
function formatMinuteDuration(minutes) {
|
|
627
305
|
const hours = Math.floor(minutes / 60);
|
|
628
306
|
const remainingMinutes = minutes % 60;
|
|
629
|
-
|
|
630
|
-
const minutePart = remainingMinutes > 0 ? `${remainingMinutes}M` : "";
|
|
631
|
-
return `PT${hourPart}${minutePart}`;
|
|
307
|
+
return `PT${hours > 0 ? `${hours}H` : ""}${remainingMinutes > 0 ? `${remainingMinutes}M` : ""}`;
|
|
632
308
|
}
|
|
633
309
|
function nextStartBoundary(trigger, now) {
|
|
634
310
|
const boundary = new Date(now.getTime());
|
|
@@ -669,16 +345,11 @@ function nextStartBoundary(trigger, now) {
|
|
|
669
345
|
}
|
|
670
346
|
}
|
|
671
347
|
function formatStartBoundary(d) {
|
|
672
|
-
// Local-time ISO-8601 (no zone suffix) — Task Scheduler interprets a
|
|
673
|
-
//
|
|
674
|
-
//
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
const dd = String(d.getDate()).padStart(2, "0");
|
|
678
|
-
const hh = String(d.getHours()).padStart(2, "0");
|
|
679
|
-
const mi = String(d.getMinutes()).padStart(2, "0");
|
|
680
|
-
const ss = String(d.getSeconds()).padStart(2, "0");
|
|
681
|
-
return `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}`;
|
|
348
|
+
// Local-time ISO-8601 (no zone suffix) — Task Scheduler interprets a bare
|
|
349
|
+
// boundary in the registering user's timezone, which matches what a user
|
|
350
|
+
// typing "0 9 * * *" means ("9am local").
|
|
351
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
352
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
682
353
|
}
|
|
683
354
|
function quoteArg(s) {
|
|
684
355
|
if (/^[A-Za-z0-9_\-./@:%=+,\\]+$/.test(s))
|
|
@@ -690,110 +361,24 @@ function quoteArg(s) {
|
|
|
690
361
|
function quotePowerShell(value) {
|
|
691
362
|
return `'${value.replaceAll("'", "''")}'`;
|
|
692
363
|
}
|
|
693
|
-
function installedSignature(xml) {
|
|
694
|
-
return taskXmlSignature(xml);
|
|
695
|
-
}
|
|
696
364
|
function isMissingTaskResult(result) {
|
|
697
365
|
return /cannot find|not found/i.test(`${result.stderr ?? ""}\n${result.stdout ?? ""}`);
|
|
698
366
|
}
|
|
367
|
+
// ── Installed-definition signature ──────────────────────────────────────────
|
|
368
|
+
//
|
|
369
|
+
// Task Scheduler rewrites a registered definition (schema defaults filled in,
|
|
370
|
+
// StartBoundary moved on), so a row is compared by the fingerprint akm stamps
|
|
371
|
+
// into `<Source>` — everything it renders except the enabled state — plus the
|
|
372
|
+
// enabled state `/Change` toggles. A task edited in Task Scheduler that keeps
|
|
373
|
+
// that fingerprint is left as it is.
|
|
699
374
|
function taskXmlSignature(xml) {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const task = findChild(document, "Task");
|
|
703
|
-
const triggers = findChild(task, "Triggers");
|
|
704
|
-
const principals = findChild(task, "Principals");
|
|
705
|
-
const settings = findChild(task, "Settings");
|
|
706
|
-
const actions = findChild(task, "Actions");
|
|
707
|
-
if (!triggers || !principals || !settings || !actions)
|
|
708
|
-
return undefined;
|
|
709
|
-
normalizeTriggerBoundaries(triggers);
|
|
710
|
-
normalizeNativeDefaults(triggers, principals, settings);
|
|
711
|
-
const enabledElement = findChild(settings, "Enabled");
|
|
712
|
-
const enabledValue = enabledElement ? elementText(enabledElement).toLowerCase() : undefined;
|
|
713
|
-
const enabled = enabledValue === undefined || enabledValue === "true" || enabledValue === "1";
|
|
714
|
-
// Enabled is represented explicitly in the signature suffix. Removing it
|
|
715
|
-
// here also treats an omitted Enabled element as its schema default, true.
|
|
716
|
-
settings.children = settings.children.filter((child) => typeof child === "string" || child.name.toLowerCase() !== "enabled");
|
|
717
|
-
const canonical = [triggers, principals, settings, actions].map(canonicalXmlElement).join("\n");
|
|
718
|
-
const fingerprint = createHash("sha256").update(canonical).digest("hex");
|
|
719
|
-
return signatureWithEnabled(`${SIGNATURE_PREFIX}${fingerprint}`, enabled);
|
|
720
|
-
}
|
|
721
|
-
catch {
|
|
722
|
-
return undefined;
|
|
723
|
-
}
|
|
375
|
+
const source = xml.match(/<(?:[\w.-]+:)?Source>([^<]*)<\//i)?.[1]?.trim() ?? "";
|
|
376
|
+
return `${decodeXml(source)}|enabled=${taskXmlEnabled(xml)}`;
|
|
724
377
|
}
|
|
725
378
|
function taskXmlEnabled(xml) {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (!settings)
|
|
730
|
-
return undefined;
|
|
731
|
-
const enabledElement = findChild(settings, "Enabled");
|
|
732
|
-
if (!enabledElement)
|
|
733
|
-
return true;
|
|
734
|
-
const enabled = elementText(enabledElement).toLowerCase();
|
|
735
|
-
if (enabled === "true" || enabled === "1")
|
|
736
|
-
return true;
|
|
737
|
-
if (enabled === "false" || enabled === "0")
|
|
738
|
-
return false;
|
|
739
|
-
return undefined;
|
|
740
|
-
}
|
|
741
|
-
catch {
|
|
742
|
-
return undefined;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
function parseXml(xml) {
|
|
746
|
-
const document = { name: "#document", attributes: {}, children: [] };
|
|
747
|
-
const stack = [document];
|
|
748
|
-
const tokens = xml.match(/<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<[^>]+>|[^<]+/g) ?? [];
|
|
749
|
-
for (const token of tokens) {
|
|
750
|
-
if (token.startsWith("<!--") || token.startsWith("<?"))
|
|
751
|
-
continue;
|
|
752
|
-
if (token.startsWith("</")) {
|
|
753
|
-
if (stack.length === 1)
|
|
754
|
-
throw new Error("Unexpected XML closing tag.");
|
|
755
|
-
const closingName = localXmlName(token.slice(2, -1).trim());
|
|
756
|
-
const current = stack.pop();
|
|
757
|
-
if (current?.name !== closingName)
|
|
758
|
-
throw new Error("Mismatched XML closing tag.");
|
|
759
|
-
continue;
|
|
760
|
-
}
|
|
761
|
-
if (token.startsWith("<")) {
|
|
762
|
-
const selfClosing = /\/\s*>$/.test(token);
|
|
763
|
-
const match = token.match(/^<\s*([^\s/>]+)([\s\S]*?)\/?\s*>$/);
|
|
764
|
-
if (!match)
|
|
765
|
-
throw new Error("Invalid XML opening tag.");
|
|
766
|
-
const element = {
|
|
767
|
-
name: localXmlName(match[1]),
|
|
768
|
-
attributes: parseXmlAttributes(match[2]),
|
|
769
|
-
children: [],
|
|
770
|
-
};
|
|
771
|
-
stack[stack.length - 1].children.push(element);
|
|
772
|
-
if (!selfClosing)
|
|
773
|
-
stack.push(element);
|
|
774
|
-
continue;
|
|
775
|
-
}
|
|
776
|
-
const text = decodeXml(token.trim());
|
|
777
|
-
if (text.length > 0)
|
|
778
|
-
stack[stack.length - 1].children.push(text);
|
|
779
|
-
}
|
|
780
|
-
if (stack.length !== 1)
|
|
781
|
-
throw new Error("Unclosed XML tag.");
|
|
782
|
-
return document;
|
|
783
|
-
}
|
|
784
|
-
function parseXmlAttributes(raw) {
|
|
785
|
-
const attributes = {};
|
|
786
|
-
const pattern = /([^\s=]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
787
|
-
for (const match of raw.matchAll(pattern)) {
|
|
788
|
-
const name = localXmlName(match[1]);
|
|
789
|
-
if (match[1] === "xmlns" || match[1].startsWith("xmlns:"))
|
|
790
|
-
continue;
|
|
791
|
-
attributes[name] = decodeXml(match[2] ?? match[3] ?? "");
|
|
792
|
-
}
|
|
793
|
-
return attributes;
|
|
794
|
-
}
|
|
795
|
-
function localXmlName(name) {
|
|
796
|
-
return name.slice(name.lastIndexOf(":") + 1);
|
|
379
|
+
const settings = xml.match(/<(?:[\w.-]+:)?Settings>([\s\S]*?)<\/(?:[\w.-]+:)?Settings>/i)?.[1] ?? "";
|
|
380
|
+
const value = settings.match(/<(?:[\w.-]+:)?Enabled>\s*([^<]*?)\s*<\//i)?.[1]?.toLowerCase();
|
|
381
|
+
return value !== "false" && value !== "0";
|
|
797
382
|
}
|
|
798
383
|
function decodeXml(value) {
|
|
799
384
|
return value.replace(/&(?:#(\d+)|#x([\da-f]+)|amp|lt|gt|quot|apos);/gi, (entity, decimal, hex) => {
|
|
@@ -817,124 +402,20 @@ function decodeXml(value) {
|
|
|
817
402
|
}
|
|
818
403
|
});
|
|
819
404
|
}
|
|
820
|
-
function findChild(parent, name) {
|
|
821
|
-
return parent?.children.find((child) => typeof child !== "string" && child.name.toLowerCase() === name.toLowerCase());
|
|
822
|
-
}
|
|
823
|
-
function elementText(element) {
|
|
824
|
-
return element.children.filter((child) => typeof child === "string").join("");
|
|
825
|
-
}
|
|
826
|
-
function normalizeTriggerBoundaries(triggers) {
|
|
827
|
-
for (const trigger of triggers.children) {
|
|
828
|
-
if (typeof trigger === "string")
|
|
829
|
-
continue;
|
|
830
|
-
const boundary = findChild(trigger, "StartBoundary");
|
|
831
|
-
if (!boundary)
|
|
832
|
-
continue;
|
|
833
|
-
const time = elementText(boundary).match(/T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?/);
|
|
834
|
-
if (!time)
|
|
835
|
-
continue;
|
|
836
|
-
const secondsSinceMidnight = Number(time[1]) * 3600 + Number(time[2]) * 60 + Number(time[3]);
|
|
837
|
-
const interval = findChild(findChild(trigger, "Repetition"), "Interval");
|
|
838
|
-
const intervalSeconds = interval ? parseIsoDurationSeconds(elementText(interval)) : undefined;
|
|
839
|
-
boundary.children = [
|
|
840
|
-
intervalSeconds === undefined
|
|
841
|
-
? `dynamic-date;time=${time[1]}:${time[2]}:${time[3]}`
|
|
842
|
-
: `dynamic-cycle;phase-seconds=${secondsSinceMidnight % intervalSeconds}`,
|
|
843
|
-
];
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
const MATERIALIZED_SETTING_DEFAULTS = {
|
|
847
|
-
allowstartondemand: "true",
|
|
848
|
-
allowhardterminate: "true",
|
|
849
|
-
startwhenavailable: "false",
|
|
850
|
-
runonlyifnetworkavailable: "false",
|
|
851
|
-
hidden: "false",
|
|
852
|
-
runonlyifidle: "false",
|
|
853
|
-
waketorun: "false",
|
|
854
|
-
executiontimelimit: "PT72H",
|
|
855
|
-
priority: "7",
|
|
856
|
-
compatibility: "Vista",
|
|
857
|
-
useunifiedschedulingengine: "true",
|
|
858
|
-
disallowstartonremoteappsession: "false",
|
|
859
|
-
volatile: "false",
|
|
860
|
-
};
|
|
861
|
-
const MATERIALIZED_IDLE_DEFAULTS = {
|
|
862
|
-
duration: "PT10M",
|
|
863
|
-
waittimeout: "PT1H",
|
|
864
|
-
stoponidleend: "true",
|
|
865
|
-
restartonidle: "false",
|
|
866
|
-
};
|
|
867
|
-
function normalizeNativeDefaults(triggers, principals, settings) {
|
|
868
|
-
for (const principal of elementChildren(principals)) {
|
|
869
|
-
principal.children = principal.children.filter((child) => {
|
|
870
|
-
if (typeof child === "string")
|
|
871
|
-
return true;
|
|
872
|
-
return child.name.toLowerCase() !== "runlevel" || elementText(child).toLowerCase() !== "leastprivilege";
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
settings.children = settings.children.filter((child) => {
|
|
876
|
-
if (typeof child === "string")
|
|
877
|
-
return true;
|
|
878
|
-
const name = child.name.toLowerCase();
|
|
879
|
-
if (name === "idlesettings")
|
|
880
|
-
return !containsOnlyDefaults(child, MATERIALIZED_IDLE_DEFAULTS);
|
|
881
|
-
const defaultValue = MATERIALIZED_SETTING_DEFAULTS[name];
|
|
882
|
-
return defaultValue === undefined || elementText(child) !== defaultValue;
|
|
883
|
-
});
|
|
884
|
-
for (const trigger of elementChildren(triggers)) {
|
|
885
|
-
trigger.children = trigger.children.filter((child) => {
|
|
886
|
-
if (typeof child === "string")
|
|
887
|
-
return true;
|
|
888
|
-
const name = child.name.toLowerCase();
|
|
889
|
-
if (name === "enabled")
|
|
890
|
-
return elementText(child).toLowerCase() !== "true";
|
|
891
|
-
return name !== "executiontimelimit" || elementText(child) !== "PT72H";
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
405
|
function resolveCurrentUserSid(exec) {
|
|
896
406
|
const result = runOrThrow(exec, ["whoami", "/user", "/fo", "csv", "/nh"], {
|
|
897
407
|
message: (r) => `whoami /user failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
|
|
898
408
|
});
|
|
899
409
|
const match = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.match(/\bS-\d+(?:-\d+){2,}\b/i);
|
|
900
|
-
if (!match)
|
|
410
|
+
if (!match)
|
|
901
411
|
throw new ConfigError("whoami /user returned no Windows user SID.", "INVALID_CONFIG_FILE");
|
|
902
|
-
}
|
|
903
412
|
return `S${match[0].slice(1)}`;
|
|
904
413
|
}
|
|
905
|
-
function elementChildren(element) {
|
|
906
|
-
return element.children.filter((child) => typeof child !== "string");
|
|
907
|
-
}
|
|
908
|
-
function containsOnlyDefaults(element, defaults) {
|
|
909
|
-
const children = elementChildren(element);
|
|
910
|
-
return (children.length > 0 &&
|
|
911
|
-
children.length === element.children.length &&
|
|
912
|
-
children.every((child) => defaults[child.name.toLowerCase()] === elementText(child)));
|
|
913
|
-
}
|
|
914
|
-
function parseIsoDurationSeconds(value) {
|
|
915
|
-
const match = value.match(/^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/i);
|
|
916
|
-
if (!match)
|
|
917
|
-
return undefined;
|
|
918
|
-
const seconds = Number(match[1] ?? 0) * 3600 + Number(match[2] ?? 0) * 60 + Number(match[3] ?? 0);
|
|
919
|
-
return seconds > 0 ? seconds : undefined;
|
|
920
|
-
}
|
|
921
|
-
function canonicalXmlElement(element) {
|
|
922
|
-
const attributes = Object.entries(element.attributes).sort(([a], [b]) => a.localeCompare(b));
|
|
923
|
-
const children = element.children.map((child) => typeof child === "string" ? ["text", child] : ["element", canonicalXmlElement(child)]);
|
|
924
|
-
children.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
925
|
-
return JSON.stringify([element.name, attributes, children]);
|
|
926
|
-
}
|
|
927
|
-
function signatureWithEnabled(signature, enabled) {
|
|
928
|
-
return `${signature}|enabled=${enabled ? "true" : "false"}`;
|
|
929
|
-
}
|
|
930
|
-
function defaultSchtasksExec() {
|
|
931
|
-
return nodeExec();
|
|
932
|
-
}
|
|
933
414
|
function defaultSchtasksFs() {
|
|
934
415
|
return {
|
|
935
416
|
...nodeFs(),
|
|
936
417
|
writeFile(file, content) {
|
|
937
|
-
fs.writeFileSync(file,
|
|
418
|
+
fs.writeFileSync(file, `${content}`, { encoding: "utf16le" });
|
|
938
419
|
},
|
|
939
420
|
removeFile(file) {
|
|
940
421
|
try {
|