Skip to content

Commit f3e0d59

Browse files
At0mFlowAt0mFlow
authored andcommitted
Add recurring drift and run evidence
1 parent 676dbff commit f3e0d59

16 files changed

Lines changed: 993 additions & 34 deletions

‎AGENTS.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ Audit collector, its tests, synthetic examples, documentation and brand assets.
1515
backend, authentication, billing or customer-data logic.
1616
- Never add collected client scripts, task exports, server inventories or audit
1717
bundles to this repository.
18-
- Never initialise, commit, sync or push a client's collected scripts. The tool
19-
may only write the requested local audit bundle.
18+
- Never initialise a client's repository, create a remote, store Git
19+
credentials or stage paths outside the requested bundle. Explicit Git sync
20+
may operate only in an existing working tree and must commit only the
21+
bundle's `scripts/`, `manifests/` and `README.txt` paths.
2022
- Never add telemetry or upload audit results to At0mFlow or another service.
23+
- Never execute discovered scripts to infer success or failure. Execution
24+
evidence must come from the previous hour of the local Windows Task Scheduler
25+
Operational event log and missing evidence must remain unknown.
2126
- Never stage files from outside this repository.
2227
- Keep `origin` pointed only to
2328
`https://github.com/At0mFlow/At0mFlow-ScriptAudit.git` or its equivalent

‎PUBLIC_BOUNDARY.md‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,16 @@ The tool writes a local audit bundle only after somebody runs it. That bundle
2020
can contain source code, server names, file owners, task principals, paths and
2121
scheduling details. It must be treated as confidential client material.
2222

23-
The tool does not initialise Git, create a remote, push a repository, upload to
24-
At0mFlow or send telemetry. Generated bundle names are ignored by this public
25-
repository's `.gitignore`.
23+
The default tool does not run Git. Explicit `-GitSync` works only inside an
24+
existing working tree, commits only the selected bundle's `scripts/`,
25+
`manifests/` and `README.txt`, and uses the operator's existing non-interactive
26+
Git configuration. It does not initialise Git, create a remote, store
27+
credentials, upload to At0mFlow or send telemetry. Generated bundle names are
28+
ignored by this public repository's `.gitignore`.
29+
30+
Execution evidence is limited to the previous hour of the local Windows Task
31+
Scheduler Operational event log. The collector does not execute a discovered
32+
script and does not convert absent evidence into an invented outcome.
2633

2734
## Automated controls
2835

‎README.md‎

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ one server or many, preserving their folder tree, and recording the scheduled
1616
tasks that run them.
1717

1818
It creates one local audit bundle containing copied scripts, CSV inventories,
19-
task actions, triggers, conditions, hashes and collection errors. It is designed
20-
for server estate discovery, SCCM deployment and the first stage of a controlled
21-
PowerShell audit.
19+
task actions, triggers, conditions, hashes, collection errors and one hour of
20+
local Task Scheduler outcome evidence. It is designed for server estate
21+
discovery, SCCM deployment and the first stage of a controlled PowerShell audit.
2222

2323
The collector is public. The bundle it creates is not. Generated bundles can
24-
contain source code and infrastructure metadata, so the tool never initialises
25-
Git, pushes a repository, uploads to At0mFlow or sends telemetry.
24+
contain source code and infrastructure metadata. By default the tool performs
25+
no Git operation. An explicit `-GitSync` option can update a preconfigured
26+
private Git working tree, but it never initialises Git, creates a remote, stores
27+
credentials, uploads to At0mFlow or sends telemetry.
2628

2729
## Quick start
2830

@@ -75,6 +77,8 @@ Computers: 2
7577
Scripts found: 47
7678
Scripts copied: 47
7779
PowerShell task actions: 18
80+
Execution evidence: 12
81+
Execution failures: 1
7882
Collection errors: 1
7983
Bundle: D:\Audit\At0mFlow-ScriptAudit-20260827
8084
```
@@ -127,6 +131,7 @@ At0mFlow-ScriptAudit-20260827/
127131
manifests/
128132
script-inventory.csv
129133
scheduled-tasks.csv
134+
script-run-evidence.csv
130135
collection-errors.csv
131136
summary.json
132137
TREE.txt
@@ -142,6 +147,12 @@ records include the principal, run level, logon type, action, arguments,
142147
working directory, script references, trigger summary, conditions, run times
143148
and last result.
144149

150+
`script-run-evidence.csv` looks back exactly one hour in the local Windows Task
151+
Scheduler Operational log. A script is marked successful or failed only when a
152+
matching event provides that evidence. Missing, disabled or inaccessible logs
153+
remain explicitly unknown. The collector never runs a discovered script to
154+
test it.
155+
145156
See the complete [output schema](docs/output-schema.md) and the checked-in
146157
[synthetic examples](examples/).
147158

@@ -173,9 +184,30 @@ This writes manifests and task context without copying source files.
173184

174185
## Private Git hand-off
175186

176-
The collector does not run Git. If version control is approved after review,
177-
use a private repository and scan the collected scripts for credentials first.
178-
See [safe Git hand-off](docs/safe-git-handoff.md).
187+
The default collector does not run Git. If version control is approved after
188+
review, use a private repository and scan the collected scripts for credentials
189+
first. See [safe Git hand-off](docs/safe-git-handoff.md).
190+
191+
## Recurring private repository refresh
192+
193+
For an approved, existing private Git checkout, the collector can refresh its
194+
stable folder tree and push only collector-owned paths:
195+
196+
```powershell
197+
./src/Invoke-At0mFlowScriptAudit.ps1 `
198+
-SearchPath C:\Scripts, D:\Automation `
199+
-OutputPath D:\PrivateGit\PowerShell-Estate `
200+
-Force `
201+
-GitSync `
202+
-Quiet `
203+
-FailOnCollectionError
204+
```
205+
206+
Configure the Git remote, upstream branch, author and non-interactive
207+
authentication before scheduling the command. The tool does not create or
208+
store those settings. It commits only `scripts/`, `manifests/` and the bundle
209+
`README.txt`, so unrelated repository content and staged work stay outside the
210+
collector commit. See [recurring private repository refresh](docs/recurring-private-repository.md).
179211

180212
## Take the estate further with At0mFlow
181213

@@ -210,6 +242,7 @@ At0mFlow product code.
210242
- [SCCM deployment](docs/sccm.md)
211243
- [Output schema](docs/output-schema.md)
212244
- [Safe Git hand-off](docs/safe-git-handoff.md)
245+
- [Recurring private repository refresh](docs/recurring-private-repository.md)
213246

214247
## Contributing
215248

‎SECURITY.md‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,12 @@ The collector does not request, store or transmit passwords. A supplied
1717
`PSCredential` is passed only to PowerShell remoting for the current run. It is
1818
not written to the bundle.
1919

20-
The project has no telemetry, hosted service, At0mFlow API client or automatic
21-
Git operations.
20+
The project has no telemetry, hosted service or At0mFlow API client. Git is
21+
disabled by default. `-GitSync` works only inside an existing working tree,
22+
uses the operator's existing non-interactive Git configuration and scopes its
23+
commit to the bundle's `scripts/`, `manifests/` and `README.txt` paths. It never
24+
initialises a repository, creates a remote or stores credentials.
25+
26+
Task outcome evidence comes only from the previous hour of the local Windows
27+
Task Scheduler Operational event log. The collector does not run discovered
28+
scripts and does not interpret missing logs as success or failure.

‎docs/output-schema.md‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ One row represents one source path on one computer.
99
| `ComputerName` | Windows computer that owned the source path. |
1010
| `OriginalPath` | Original script path. |
1111
| `CentralRelativePath` | Collision-safe path inside the bundle. |
12+
| `SourcePhysicalPath` | Explicit physical path of the source script. |
13+
| `CollectedCopyPath` | Absolute local path of the copied script. |
14+
| `RepositoryRelativePath` | Stable Git-relative path when the bundle is inside a Git working tree. |
1215
| `DiscoverySources` | `SearchPath`, `ScheduledTask`, or both. |
1316
| `ReferencedByTasks` | Scheduled task paths and names linked to the script. |
1417
| `DeclaredAuthor` | Optional `# Author:` or `# Created by:` header value. |
@@ -17,6 +20,9 @@ One row represents one source path on one computer.
1720
| `CreatedUtc` | Source creation time in UTC. |
1821
| `LastWriteUtc` | Source modification time in UTC. |
1922
| `SHA256` | Source content hash. |
23+
| `PreviousCollectedSHA256` | Hash of the prior collected copy when one existed. |
24+
| `CollectedSHA256` | Verified hash of the current collected copy. |
25+
| `CollectionChange` | `Added`, `Modified`, `Unchanged`, `NotCopied`, `Unavailable`, `CopyFailed` or `HashMismatch`. |
2026
| `CopyStatus` | `Copied`, `InventoryOnly`, `Missing`, `TooLarge`, `CopyFailed` or `HashMismatch`. |
2127

2228
`DeclaredAuthor` and `FileOwner` are evidence fields, not proof of authorship.
@@ -30,6 +36,22 @@ It records task identity, state, description, principal, run level, logon type,
3036
action command, arguments, working directory, discovered script references,
3137
excluded references, triggers, conditions, last run, next run and last result.
3238

39+
## `script-run-evidence.csv`
40+
41+
One or more rows represent the available execution evidence for each discovered
42+
script. The time window is always the previous hour from the local
43+
`Microsoft-Windows-TaskScheduler/Operational` event log.
44+
45+
`EvidenceStatus` distinguishes observed events from no scheduled-task
46+
reference, no matching event and an unavailable event log. `Outcome` is
47+
`Succeeded`, `Failed`, `SchedulerStatus` or `Unknown`. `Attribution` states
48+
whether the event matched an exact task action, only the task, or an ambiguous
49+
multi-action task.
50+
51+
Observed rows include the event timestamp, ID, record ID, task, action, exact
52+
result code and a stable `EvidenceKey`. Consumers should deduplicate using that
53+
key and must not create failure events from `Unknown` rows.
54+
3355
## `collection-errors.csv`
3456

3557
Every row includes a computer, collection stage, target and readable message.
@@ -41,8 +63,9 @@ suppressed count so a noisy access problem cannot create an unbounded CSV.
4163

4264
## `summary.json`
4365

44-
Contains tool version, timestamps, output location, collection mode, counts,
45-
error status and the search roots used on each computer.
66+
Contains tool version, timestamps, output and optional Git working-tree
67+
locations, collection mode, counts, error status, one-hour evidence status and
68+
the search roots used on each computer.
4669

4770
## `TREE.txt`
4871

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Recurring private repository refresh
2+
3+
Use this mode only after the collected material has been approved for a private
4+
Git repository.
5+
6+
The collector can refresh the same stable folder tree on a schedule. Existing
7+
scripts are overwritten only when their SHA-256 content changes. The inventory
8+
records the physical source path, absolute collected-copy path, portable
9+
repository-relative path, previous hash, current hash and whether the copy was
10+
added, modified or unchanged.
11+
12+
## Prepare the working tree
13+
14+
Before using `-GitSync`, configure and test these items under the Windows
15+
identity that will run the scheduled task:
16+
17+
1. An existing private Git working tree.
18+
2. An existing remote and upstream branch.
19+
3. Repository-local Git author name and email.
20+
4. Non-interactive authentication suitable for that private remote.
21+
5. Write permission to the collector output path.
22+
6. Read permission to the source paths and local Task Scheduler Operational
23+
event log.
24+
25+
The collector does not create, store or repair any of these settings.
26+
27+
Task Scheduler history must already be enabled on each collected server. An
28+
administrator can enable it through Task Scheduler's **Enable All Tasks
29+
History** action or, after reviewing the change, with:
30+
31+
```powershell
32+
wevtutil.exe sl Microsoft-Windows-TaskScheduler/Operational /e:true
33+
```
34+
35+
Grant the scheduled identity only the local event-log access it needs, commonly
36+
through the built-in **Event Log Readers** group. The collector does not change
37+
event-log settings or local group membership.
38+
39+
## Run once interactively
40+
41+
```powershell
42+
./src/Invoke-At0mFlowScriptAudit.ps1 `
43+
-SearchPath C:\Scripts, D:\Automation `
44+
-OutputPath D:\PrivateGit\PowerShell-Estate `
45+
-Force `
46+
-GitSync
47+
```
48+
49+
Review the resulting commit and confirm that the expected private remote
50+
received it before scheduling the command.
51+
52+
## Schedule hourly
53+
54+
Create a Windows Scheduled Task that runs the same command every hour. Use an
55+
approved service identity or managed execution identity that already has the
56+
required file, event-log and Git permissions. Add `-Quiet` for non-interactive
57+
output and `-FailOnCollectionError` when collection gaps should fail the task.
58+
59+
Task Scheduler Operational evidence is always limited to the previous hour.
60+
This makes an hourly schedule a natural fit without turning the public tool into
61+
a resident agent.
62+
63+
## Safety boundaries
64+
65+
- The source scripts are read only. The collector never modifies or executes
66+
them.
67+
- Git must already exist and be configured by the operator.
68+
- Credentials are never accepted or stored by the collector.
69+
- Git prompts are disabled so unattended runs fail clearly instead of hanging.
70+
- Only the selected output's `scripts/`, `manifests/` and `README.txt` are
71+
committed.
72+
- Other files and staged changes are not included in the collector commit.
73+
- The one-hour event evidence comes only from Windows Event Viewer on each
74+
collected computer. No At0mFlow service is contacted.
75+
76+
For a reusable, repository-agnostic scheduled commit tool, see the public
77+
At0mFlow RepoSync project when it becomes available.

‎docs/safe-git-handoff.md‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Safe Git hand-off
22

3-
The collector deliberately performs no Git operations.
3+
The collector performs no Git operation unless `-GitSync` is explicitly used.
44

55
Before placing a bundle under version control:
66

@@ -25,6 +25,17 @@ git status
2525
Inspect the dry run and status before staging. This guide intentionally stops
2626
before adding a remote or pushing data.
2727

28+
## Optional recurring sync
29+
30+
After the repository, private remote, upstream branch, author and
31+
non-interactive authentication have been configured and tested by the operator,
32+
`-GitSync` may be used for a managed recurring refresh. It never initialises a
33+
repository or creates those settings.
34+
35+
The commit is restricted to the selected bundle's `scripts/`, `manifests/` and
36+
`README.txt`. Other staged changes remain staged and are not included. See
37+
[recurring private repository refresh](recurring-private-repository.md).
38+
2839
For mass repository organisation, automated SOP creation, cleanup planning and
2940
ongoing ownership tracking, use [At0mFlow](https://at0mflow.com/) after the
3041
bundle has been reviewed and approved.

‎docs/sccm.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ gaps are present.
2020
or another approved execution identity.
2121
- Writing locally and letting SCCM collect the bundle is often simpler than
2222
writing directly to a network share.
23+
- Reading one-hour execution evidence requires local access to the Task
24+
Scheduler Operational event log. Grant only the event-log permission needed
25+
by the approved SCCM execution identity.
2326

2427
## Detection and collection
2528

@@ -29,6 +32,10 @@ Use `manifests/summary.json` as the completion artefact. Check its timestamp,
2932
Collect the whole output folder, not only the copied scripts. The CSVs provide
3033
the task and provenance context needed during estate review.
3134

35+
`script-run-evidence.csv` contains only the previous hour of local Task
36+
Scheduler evidence. Disabled or inaccessible logging is recorded as unavailable
37+
evidence rather than success or failure.
38+
3239
## Reruns
3340

3441
Use a new timestamped path for immutable audit evidence. `-Force` is available

‎docs/usage.md‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,33 @@ Task folders under `\Microsoft\` are excluded by default:
4848

4949
Use that option only when built-in task context is genuinely needed.
5050

51+
## One-hour execution evidence
52+
53+
When scheduled-task discovery is enabled, the collector reads only the previous
54+
hour from `Microsoft-Windows-TaskScheduler/Operational`. It records matching
55+
action completion and launch failure evidence in
56+
`manifests/script-run-evidence.csv`.
57+
58+
The account running the collector must be allowed to read that local Windows
59+
event log. When several computers are collected over WinRM, the same check runs
60+
inside each remote session against that computer's local event log. Nothing is
61+
sent to At0mFlow or another service.
62+
63+
Task Scheduler history must be enabled by an administrator before collection.
64+
The collector reports disabled or inaccessible logging but does not modify the
65+
event log or local permissions.
66+
67+
The outcome rules are deliberately conservative:
68+
69+
- result code `0` from event 201 is `Succeeded`;
70+
- a non-zero action result, task launch failure, logon failure or action start
71+
failure is `Failed`;
72+
- Task Scheduler status constants are `SchedulerStatus`, not failures;
73+
- no matching event is `Unknown`, never a presumed success;
74+
- ambiguous multi-action task attribution is labelled in the CSV.
75+
76+
The collector never executes a discovered script to determine its status.
77+
5178
## Default locations
5279

5380
`-IncludeDefaultLocations` removes the standard Windows and Program Files path
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
"ComputerName","OriginalPath","CentralRelativePath","DiscoverySources","ReferencedByTasks","DeclaredAuthor","FileOwner","LengthBytes","CreatedUtc","LastWriteUtc","SHA256","CopyStatus"
2-
"SERVER-EXAMPLE-01","C:\Scripts\Synthetic-NightlyReport.ps1","scripts/SERVER-EXAMPLE-01/C/Scripts/Synthetic-NightlyReport.ps1","SearchPath; ScheduledTask","\Operations\Synthetic nightly report","Example Automation Team","EXAMPLE\svc-script","241","2026-08-27T01:00:00.0000000Z","2026-08-27T02:00:00.0000000Z","8B7D44C0E3E2C188C785ABF311CD9A6D61F68D4E3911AD9731CF06F4DA6CD2CE","Copied"
1+
"ComputerName","OriginalPath","CentralRelativePath","SourcePhysicalPath","CollectedCopyPath","RepositoryRelativePath","DiscoverySources","ReferencedByTasks","DeclaredAuthor","FileOwner","LengthBytes","CreatedUtc","LastWriteUtc","SHA256","PreviousCollectedSHA256","CollectedSHA256","CollectionChange","CopyStatus"
2+
"SERVER-EXAMPLE-01","C:\Scripts\Synthetic-NightlyReport.ps1","scripts/SERVER-EXAMPLE-01/C/Scripts/Synthetic-NightlyReport.ps1","C:\Scripts\Synthetic-NightlyReport.ps1","D:\PrivateGit\PowerShell-Estate\scripts\SERVER-EXAMPLE-01\C\Scripts\Synthetic-NightlyReport.ps1","scripts/SERVER-EXAMPLE-01/C/Scripts/Synthetic-NightlyReport.ps1","SearchPath; ScheduledTask","\Operations\Synthetic nightly report","Example Automation Team","EXAMPLE\svc-script","241","2026-08-27T01:00:00.0000000Z","2026-08-27T02:00:00.0000000Z","8B7D44C0E3E2C188C785ABF311CD9A6D61F68D4E3911AD9731CF06F4DA6CD2CE","7AA13CE9080A15C502AF793154347B443708F6BE8AFC45FBAE69731EAF9B3E11","8B7D44C0E3E2C188C785ABF311CD9A6D61F68D4E3911AD9731CF06F4DA6CD2CE","Modified","Copied"

0 commit comments

Comments
 (0)