PowerShell ยท Troubleshooting

PowerShell Script Works Interactively But Fails Under Task Scheduler

A script that runs cleanly by hand can fail silently under Task Scheduler because the task launches it in a completely different context, and this walks through why and how to fix it.

Quick idea: A PowerShell script that runs cleanly when you test it by hand can still fail under Task Scheduler, because the task launches it in a different working directory, a different profile context, a different execution policy scope, and with no console attached to catch what went wrong.
Batch Logon

Task Scheduler runs your script under a non-interactive batch logon, not the session you tested it in.

Working Directory

Without an explicit path, the task’s default working folder is %windir%\system32, not your script’s own folder.

-NonInteractive

Any cmdlet that would normally prompt throws a terminating error instead of waiting for input that will never come.

When the Script Runs Everywhere Except Task Scheduler

The script works. You’ve tested it from an elevated PowerShell console, watched it run end to end, and it did exactly what it should. Then you wire it up as a scheduled task, walk away, and come back to find nothing happened, or something happened but not what you expected. Task Scheduler reports the run completed, but the output file you were expecting isn’t there.

This is one of the most common gaps between “the script works” and “the automation works,” and it catches experienced PowerShell users as often as beginners, because the failure has nothing to do with the script’s logic. It’s about the environment Task Scheduler actually runs it in, which is a much narrower, more isolated context than an interactive console session.

What the Failure Actually Looks Like

There isn’t one single error here, which is part of what makes this frustrating to search for. The two most common signatures in the Task Scheduler Last Run Result column are:

Example: Last Run Result: The operation completed successfully. (0x0), but the script’s expected output never appears. This is the more confusing case, because Task Scheduler is telling you nothing went wrong.
Example: Last Run Result: Incorrect function. (0x1), a generic Win32 error that Task Scheduler surfaces when it can’t launch or interpret the action as configured, most often from a bad path, missing quoting, or an argument string PowerShell never receives the way you intended.

Neither of these tells you what actually went wrong inside the script. That detail only shows up if you’ve deliberately captured it, which is exactly what the diagnosis steps below are for.

Why Task Scheduler Isn’t Just “Running Your Script”

Think of Task Scheduler as a completely different login walking up to the same computer: same machine, but a stranger’s desk. There’s no folder they were already sitting in, no shortcuts they’d pinned, no assumption about where anything is kept. Your interactive session carries all of that context around without you noticing it. A scheduled task carries none of it unless you supply it explicitly.

Concretely, four things differ between your interactive test and the scheduled run: the working directory, the user profile that gets loaded, which execution policy scope actually applies, and whether the session can interact with you at all if something needs input.

Key point: None of this is Task Scheduler being unreliable. It’s running exactly what you configured, in exactly the context you configured. That context just isn’t the one your interactive session gave you for free.

How to Diagnose It

Work from the cheapest check to the deepest. Most of the time the answer shows up in the first two steps.

# Check the last recorded result and run time for the task
Get-ScheduledTaskInfo -TaskName "YourTaskName"

# Task History is off by default; turn it on before you need it
# (Task Scheduler GUI: right-click the task library root -> Enable All Tasks History)

# Reproduce the exact same invocation Task Scheduler uses, from a non-interactive
# context, instead of your normal interactive console
powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Scripts\YourScript.ps1"

# Add explicit logging inside the script itself so failures aren't silent
Start-Transcript -Path "C:\Scripts\logs\run-$(Get-Date -Format yyyyMMdd-HHmmss).log"
# ... script body ...
Stop-Transcript
Practical rule: If a script only fails under the scheduler, reproducing it with powershell.exe -NoProfile -NonInteractive -File from a fresh, non-interactive shell almost always reproduces the same failure, because that’s a close approximation of the actual context Task Scheduler launches it in.

Common Causes

Cause How to Confirm Fix
Wrong working directory The script uses a relative path (.\data.csv, .\config.json). Task Scheduler’s default working directory is %windir%\system32 when none is set, not the script’s own folder. Set the task’s working directory explicitly with New-ScheduledTaskAction -WorkingDirectory, or reference paths inside the script using $PSScriptRoot instead of a relative path.
Execution policy blocks the run-as context The script runs interactively but the scheduled run reports a policy-restriction error, or nothing at all if the error is swallowed. Add -ExecutionPolicy Bypass to the task’s argument string. This sets the Process-scope policy for that one launch only and doesn’t touch the registry, and Process scope takes precedence over CurrentUser and LocalMachine (though not over a Group Policy-enforced policy).
Script depends on something defined in the PowerShell profile Run the script manually with -NoProfile. If it fails the same way, the script relies on a function, alias, or drive that only exists because your interactive profile loaded it. Move any required setup into the script itself. Don’t assume anything from $PROFILE is available; Task Scheduler never loads it.
CurrentUser-scope module isn’t visible to the run-as account Check $env:PSModulePath under the account the task actually runs as. A module installed with Install-Module -Scope CurrentUser lives under that account’s own $HOME\Documents\WindowsPowerShell\Modules, and $HOME differs between your interactive account and a service account or SYSTEM. Install the module at AllUsers scope instead, or have the script import it by its full path rather than relying on module autoloading.
Script contains something that expects interactive input A Read-Host call, an unconfirmed destructive cmdlet, or an interactive sign-in prompt. Under -NonInteractive, these throw a terminating error immediately instead of waiting. Remove interactive elements entirely. Pass -Force or -Confirm:$false where a cmdlet would otherwise prompt, and use a non-interactive auth method (certificate, client secret, managed identity) for any sign-in.
Failures are invisible because nothing captures them The task reports success even though the intended outcome didn’t happen. A non-terminating error inside the script doesn’t necessarily set a failing exit code Task Scheduler can see. Add explicit logging inside the script (Start-Transcript/Stop-Transcript, or redirecting output to a file) so a “successful” run that did nothing useful is visible the next time you check.

Fixing the Most Common Case

The single most frequent combination is a relative path plus a restrictive execution policy in the run-as context. Here’s the end-to-end fix, rebuilding the task’s action with both addressed explicitly:

# Rebuild the action with an explicit working directory and a safe,
# session-scoped execution policy override
$Action = New-ScheduledTaskAction -Execute "powershell.exe" `
  -Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Scripts\YourScript.ps1"' `
  -WorkingDirectory "C:\Scripts"

# Fire once daily at 6 AM
$Trigger = New-ScheduledTaskTrigger -Daily -At "6:00 AM"

# Run as SYSTEM so the task doesn't depend on any interactive user's profile at all
$Principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest

# Register the task fresh, or use Set-ScheduledTask against an existing one
Register-ScheduledTask -TaskName "YourTaskName" -Action $Action -Trigger $Trigger -Principal $Principal -Force

# Confirm the registered action actually has the working directory set
(Get-ScheduledTask -TaskName "YourTaskName").Actions | Select-Object Execute, Arguments, WorkingDirectory
Production note: After changing a task that’s already relied on in production, run it once manually with Start-ScheduledTask -TaskName "YourTaskName" and check Get-ScheduledTaskInfo -TaskName "YourTaskName" for a clean LastTaskResult before trusting the schedule again.

Preventing It Next Time

Most of this comes down to writing scripts that don’t quietly depend on the environment being the one you happen to be sitting in. Reference paths with $PSScriptRoot instead of relative paths. Don’t assume anything from your PowerShell profile is available. Add logging before you need it, not after a scheduled run has failed silently for a week. And test every scheduled script once with the same -NoProfile -NonInteractive invocation Task Scheduler will actually use, before trusting the schedule.

For the mechanics of Task Scheduler itself, including triggers, run-as accounts, and the logon-right issues that affect service accounts, see the Task Scheduler guide.

Final Thoughts

A script that only fails under the scheduler almost never has a logic problem. It has a context problem: a path, a profile, a policy, or a missing console that your interactive testing quietly provided and the scheduled run doesn’t.

Treat every scheduled PowerShell script as if it will run on a machine you’ve never logged into, because as far as its working directory, profile, and console access are concerned, that’s close to the truth.

Key takeaway: If a script fails only under Task Scheduler, reproduce it with powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "path" from a fresh shell first. That alone surfaces most of the causes above before you touch the task itself.
Next in this series

A natural follow-on is structured logging for scheduled PowerShell jobs: capturing enough detail on every run, success or failure, that “it completed successfully but didn’t do anything” stops being a mystery.