I'm designing a PowerShell cleanup script for a directory that may contain files created by people or other jobs. A path and age check alone aren't sufficient, so I'm considering an ownership manifest written when each artifact is created. The manifest would record a run ID, normalized path, size, and hash, and the cleanup script would require those values to match before deleting anything. I also want to use SupportsShouldProcess and a high confirmation impact so the command works normally with -WhatIf and confirmation prompts.
What other safeguards should surround this design? I'm considering rejecting anything outside the resolved root, refusing reparse points, failing closed if the manifest is incomplete, logging the file identity before and after validation, and separating discovery from deletion so the candidate list can be reviewed. How can the script reduce or eliminate the time-of-check/time-of-use gap if another process can replace a file between validation and removal?
4 Answers
The first question is whether this level of ownership tracking is actually necessary. A unique naming scheme containing a GUID, UUID, or ULID may be enough, especially if the cleanup directory is reserved exclusively for temporary artifacts. Store those files in a dedicated directory, use a clear naming convention, and only process files older than a reasonable threshold. Hashes and manifests add complexity without preventing every race condition.
Keep discovery and deletion as separate phases. Resolve the root once, canonicalize every candidate, reject paths outside that root, reject reparse points unless you explicitly support them, and treat missing or malformed manifest records as a reason to skip the item. Record the path, size, timestamps, identity information, and validation result, then require an explicit review or a second invocation to delete. Use -WhatIf and -Confirm through ShouldProcess, but remember that confirmation is an operator safeguard, not a concurrency guarantee.
The cleanest architecture is for each producer to coordinate with cleanup instead of having several independent jobs compete over the same filenames. Give each job its own directory or namespace, write a completion marker only after the artifact is closed, and have cleanup process only completed artifacts older than a retention period. If a file can still be actively used, skip it and try again later rather than trying to predict every possible replacement case.
For the race between checking and deleting, don’t continue working on a name that other processes can modify. Claim the file first by atomically renaming or moving it into a private processing directory, then validate and remove the claimed copy. Another option is opening it with restrictive sharing, such as exclusive write access, before performing the final operation. If the claim fails, skip the file rather than forcing deletion.
The important part is that the claim operation itself must be atomic and remain on the same volume when possible. A hash or manifest proves what you saw, but it does not by itself reserve the pathname.

That works best when every producer follows the convention. If unrelated users or jobs can write into the same directory, the cleanup process still needs an explicit ownership rule and should fail closed when it cannot establish one.