A PowerShell window flashed up on my desktop every 30 minutes, on the dot. Not an error, not a crash, just a black console that appeared and vanished while I was in the middle of something else. The cause turned out to be one property on a scheduled task, and the fix removed the window completely with no stored password. The interesting part is the wrong turn I almost took to get there.

What was actually happening

The culprit was a Windows scheduled task I’d set up called BenHQ-PMTick. It runs every 30 minutes to do a small bit of orchestration work: classify what’s in my queue and write out a proposal file. Nothing about it should ever draw a window. It does local file work and makes one outbound HTTPS call to an LLM API.

The task was created with its principal set to LogonType = Interactive. In Task Scheduler terms that is the “Run only when user is logged on” option. The whole reason a console flashed is buried in that one word. An Interactive task runs attached to your desktop session. So when it launches a PowerShell process, that process gets a real console window on your actual desktop, and you see it pop up and disappear every time the trigger fires.

So the flash wasn’t a bug in my script. It was the task’s logon type telling Windows “this thing belongs to the user’s desktop,” and Windows dutifully drew it there.

I made the first registration with Interactive and watched the console flash on every 30-minute run. That was the wrong logon type for a task that never needed the desktop.

The fix: S4U

The fix is to stop attaching the task to the desktop. The clean way to do that in Task Scheduler is the “Run whether user is logged on or not” option, which under the hood uses an S4U principal (Service-For-User). An S4U task runs in a non-interactive session with no desktop to draw on, so there is no window to flash. And critically, S4U does not require you to store the account’s password the way the older “run whether logged on or not, with password” path does.

In PowerShell that’s:

Terminal window
New-ScheduledTaskPrincipal -LogonType S4U -RunLevel Limited

Re-register the task with that principal and the every-30-minute flash is gone. No window, no stored secret.

The wrong turn I almost took

Before I made the change I ran the situation past codex to sanity-check it. Codex came back recommending a stored-password logon over S4U, on the grounds that S4U has more environment limitations and “no network/EFS access.”

That is a real, documented limitation of S4U, and on the surface it sounds disqualifying for a task that makes an outbound API call. If S4U can’t touch the network, and my task hits an API, then S4U is out and I need the stored-password path, right?

No. And this is the gotcha worth carrying away.

S4U’s “no network access” caveat is specifically about authenticating to network resources as the user. Think SMB shares, mapped drives, anything where Windows has to present your credentials to another machine to prove who you are. S4U logons don’t carry the credentials needed for that outbound authentication, so they can’t auth to a file share.

It is not a statement about outbound IP connectivity. An ordinary outbound HTTPS call to an API does not need your Windows credentials presented to the remote end. The TCP connection and the TLS handshake go out fine, and the API’s own auth is an app-layer bearer key, not a Windows logon. My task does local file work plus one outbound HTTPS API call, and neither of those touches the share-authentication path that S4U restricts.

So S4U was sufficient, and reaching for stored-password would have been solving a problem I didn’t have. There’s also a practical wall in the way of the stored-password route here: you can’t type a Windows password into a non-interactive shell to register the task in the first place. The thing codex recommended as the safer default was both unnecessary and more friction.

The reviewer was right that the limitation exists. It was wrong about whether the limitation applied. Those are two different things, and the second one is where you have to do your own thinking.

Verifying the fix actually ran

A scheduled task that no longer flashes a window is easy to “confirm” by just not seeing the window anymore. That proves the symptom is gone. It does not prove the task still works. An S4U task runs in a different session than your interactive one, and session 0 can surprise you in ways your desktop session never does.

So I triggered a run manually under the new S4U principal and checked the result code. It returned LastResult 0x0, a clean exit, and it wrote a fresh proposal file. That is the actual confirmation: the task ran to completion in the non-interactive session and produced its real output, not just “I stopped seeing the window.”

For reference, the two result codes you’ll bump into here:

  • 0x41301 means the task is currently running. You’ll see this if you check the result while a run is in flight.
  • 0x0 means a clean exit. That’s the one you want.

A couple of things are worth checking before you assume a task that worked Interactive will work under S4U, because session 0 doesn’t inherit your interactive environment:

  • Is every drive the task touches a real local drive? A path on a network drive that mapped fine in your logged-on session may not exist at all in the non-interactive session. In my case I had to confirm G: was a genuine local disk and not a session-scoped mapping.
  • Do the executables the task calls resolve without your interactive PATH? Tools like node and npm often live on a PATH that your interactive profile sets up. The non-interactive session may not have it, so a command that runs fine from your shell can fail to be found under S4U.

Those were the exact two things codex flagged to check first, and that part of its advice was good. Verify the drive is local, verify the binaries resolve, then trust the green.

Cleaning up the rest

Once the pattern was clear it was the same fix everywhere. I switched the daily and weekly cron-style tasks over to S4U too, since none of them need a desktop either. The one task I deliberately left as Interactive was a browser broker that genuinely drives a real browser session and does need the desktop attached. That’s the distinction: leave a task Interactive only when it actually has to paint on or read from the desktop. Everything else that’s flashing a window is flashing it for no reason.

The takeaway

If a scheduled task flashes a console window on your desktop at regular intervals, the cause is almost always that its principal is set to Interactive (“Run only when user is logged on”). That logon type attaches the task to your desktop session, which is why the process gets a visible console.

Switch it to S4U (“Run whether user is logged on or not”) and the window disappears, with no stored password to manage. And when someone, human or model, tells you S4U is out because “it has no network access,” check whether your task actually needs the thing S4U restricts. That caveat is about authenticating to network shares, not about making an outbound HTTP call. Don’t reach for a stored-password logon you don’t need.