← trentontompkins.com

A SYSTEM Command Queue: letting automation run elevated without a UAC every time

I have an automation — a Claude Code agent that does real work on my machine — and every so often it needs to do something that requires administrator rights. Delete a registry key that's been ACL-locked. Kill a protected process. Install a service. Each time, it would write me a little self-elevating script and ask me to double-click it and approve a UAC prompt. Which works, and which is also exactly the kind of papercut that makes automation not feel automatic.

UAC is doing its job: it refuses to let a non-elevated process silently become administrator. You can't “just elevate” unattended, and you shouldn't be able to. So the trick isn't to defeat UAC — it's to separate who is allowed to ask from who actually runs. This is the build: a privileged worker that runs continuously and takes orders from an unprivileged drop box. And because it is genuinely a loaded gun, I'll be just as blunt about why you might not want it.

The shape of the idea

Three pieces:

  • A queue file — an ordinary text file that my normal, unprivileged user can write to. To enqueue a job, you append a command line.
  • A runner — a script that reads the queue, runs each line, and logs the result.
  • A scheduled task that runs the runner as NT AUTHORITY\SYSTEM on a timer. This is where the privilege comes from: the task is registered once (the one elevation prompt you'll ever see), and from then on it executes whatever it finds in the queue with full rights.

The asymmetry is the whole design. Anyone (any unprivileged process) can drop a command. Only the SYSTEM task runs it. No UAC at drop time, because dropping a line in a text file needs no privilege at all.

The runner

The runner reads every non-blank line, empties the queue immediately (so a job runs once), then executes each line and writes a structured result — the command, its output, and its exit code — to a log. If the queue is empty it exits without touching anything.

# admin_hook_runner.ps1 — fired on a timer by the SYSTEM task.
$queue = 'C:\admin_commands.ps1'
$log   = 'C:\admin_commands.log'
if (-not (Test-Path $queue)) { exit 0 }

$lines = @(Get-Content $queue | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
if ($lines.Count -eq 0) { exit 0 }
Set-Content -Path $queue -Value '' -Encoding ASCII   # claim the work: blank the queue now

$blocks = foreach ($line in $lines) {
    $global:LASTEXITCODE = 0
    $text = try { (Invoke-Expression $line *>&1 | Out-String).TrimEnd() }
            catch { "ERROR: " + $_.Exception.Message }
    $code = if ($null -ne $LASTEXITCODE) { $LASTEXITCODE } else { 0 }
    "[$line]`r`n$text`r`n>> Exited with code $code"
}
Set-Content -Path $log -Value (($blocks -join "`r`n`r`n") + "`r`n") -Encoding UTF8

Note the blank-the-queue-then-run order. The runner claims the work before doing it, so if a second tick fires while it's busy, the same command can't run twice.

Registering the SYSTEM task

One command, run once from an elevated shell, creates the privileged worker. It runs every minute, as SYSTEM, at the highest run level, with a hidden window:

$tr = 'powershell -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File C:\admin_hook_runner.ps1'
schtasks /Create /F /TN "AdminCommandQueue" /TR $tr /SC MINUTE /MO 1 /RU SYSTEM /RL HIGHEST

That /RU SYSTEM is the entire point: the task — and therefore every command it pulls off the queue — runs as the most privileged account on the machine. You approve the creation of this task exactly once. After that, no prompts, ever.

The popup gotcha. My first version ran the runner from a small service inside my own logged-in session, and it flashed a PowerShell console on my desktop every single minute. The fix is the design above: a /RU SYSTEM task runs in session 0 — the isolated, non-interactive services session — which physically cannot draw a window on your desktop. (If you ever do spawn the runner from a user session, pass the process-creation flag CREATE_NO_WINDOW.) Session 0 is why the finished version is completely silent.

Verifying it actually runs elevated

Drop one line in the queue and wait for the next tick:

# enqueue (no elevation needed to write the file):
'whoami' | Set-Content C:\admin_commands.ps1
# ~60s later, read the log:
Get-Content C:\admin_commands.log
[whoami]
nt authority\system
>> Exited with code 0

nt authority\system. A command I wrote from a perfectly ordinary, unprivileged shell just ran as SYSTEM, and nothing popped up to ask. That's the capability working exactly as intended.

A thin client so you're not poking files by hand

Writing to the queue and parsing the log by hand gets old, so I wrapped it. The wrapper enqueues one or more commands, waits for the SYSTEM task to pick them up (it polls until the queue is blank and the log is fresh), parses the log, and hands back {command, output, exit_code} per command. The interesting parts:

QUEUE = r"C:\admin_commands.ps1"
LOG   = r"C:\admin_commands.log"

def run_elevated(commands, timeout=180):
    cmds = commands if isinstance(commands, list) else [commands]
    write_mtime = time.time()
    with open(QUEUE, "w", encoding="utf-8", newline="\n") as f:   # 'w' clears stale lines
        f.write("\n".join(cmds) + "\n")

    deadline = time.time() + timeout
    while time.time() < deadline:                 # wait for the SYSTEM tick (~60s)
        time.sleep(2)
        queue_blank = not open(QUEUE, encoding="utf-8").read().strip()
        log_fresh   = os.path.getmtime(LOG) >= write_mtime - 1
        if queue_blank and log_fresh:
            break
    return parse_log(LOG)[-len(cmds):]            # [{command, output, exit_code}, ...]

So the messy reality — a drop box, a timer, a log — collapses into one call: run_elevated(["whoami", "Get-Date"]) returns both results, each having run as SYSTEM. The full version (with task auto-registration, separators, and timeouts) lives in my hooks repo.

Now the part nobody likes to say out loud

This is a local privilege-escalation surface, on purpose. You have created a standing channel where anything that can write one text file gets to run code as SYSTEM with no prompt. That is the feature. It is also, structurally, exactly what a piece of malware would install to gain persistence and escalate. A security tool that flagged it would be right to.

So treat it like what it is:

  • Only on a machine you own and control. Never on a shared box, a work laptop, a server other people touch, or anything under someone else's management. This is a personal-rig convenience, full stop.
  • The queue file's ACL is the entire security boundary. Whoever can write it can be SYSTEM. Lock it down to just your user. If a low-privileged process on your machine gets compromised and it can write that file, it just got SYSTEM — you've removed UAC as a speed bump for attackers too, not only for yourself.
  • You traded a prompt for a permanent capability. UAC is annoying precisely because it's a checkpoint. Replacing “approve each time” with “approve once, forever” is a real reduction in your own defenses. Make that trade knowingly, and know how to revoke it: schtasks /Delete /TN AdminCommandQueue /F removes the worker and the whole capability in one line.

I run this on one personal machine, with the queue locked to my account, because the convenience is worth it to me, there. That sentence has three qualifiers and all three matter.

The lesson

Elevation is about the identity of the thing that runs a command, not the thing that asks for it. UAC guards the moment a process tries to become admin — so instead of fighting that moment, you stand up one already-admin worker, once, and feed it from a humble queue the rest of the time. It's a clean little pattern. It's also a sharp one. Both of those are true at the same time, and pretending otherwise is how people end up with a SYSTEM backdoor they forgot they built.

The scripts in this article are released under the MIT License — Copyright © 2026 Trent Tompkins. Build this only on hardware you own; the queue file's permissions are its only security boundary.