How to run a script on a schedule with Task Scheduler and PowerShell

You have a PowerShell script you run by hand, perhaps a cleanup, a report, a backup copy or a check, and you want Windows to run it for you every night or every week. It has to keep running when nobody is logged on, and when it goes wrong you want to be able to find out why without guessing.

Task Scheduler does this on every supported version of Windows. This article shows two ways to create the task (the Task Scheduler window and PowerShell), how to choose the account it runs as, how to see the result, and the handful of results you are most likely to meet.

Applies to: Windows 10, Windows 11 and Windows Server. The PowerShell commands are in the ScheduledTasks module that ships with Windows, and need an elevated PowerShell window (Run as administrator). The examples start Windows PowerShell 5.1 (powershell.exe). For PowerShell 7, use pwsh.exe and its full path instead.

What is going on

A scheduled task is four things stored together: an action (the program and arguments to run), a trigger (when), a principal (which account, and with what rights), and settings (what to do if the start is missed, how long it may run, what if it is already running). Task Scheduler starts the program for you, then records only a little about what happened: when it started, when it ended, and the program’s exit code.

That is why most “my task does not work” cases are one of three things: the task ran as an account that cannot reach what the script needs, the script ran in the wrong folder, or the script failed and nobody captured its output. The steps below deal with each.

Before you start / check first

1. Prove the script works in the same conditions

Task Scheduler only starts the script, so the script has to work first. Microsoft’s troubleshooting guide for tasks that do not run says to test the script directly in PowerShell before putting it in a task. Run it from an elevated window, using the exact command line the task will use, so you also catch problems with quoting and with the execution policy:

powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Nightly.ps1"

Use full paths for everything inside the script (files, other scripts, output folders). The task does not start in your profile folder, and by default it starts in C:\Windows\System32 unless you set a Start in folder.

2. Decide where the script lives and who can write to it

Put it in a folder such as C:\Scripts where only administrators can create or change files. See the security warning under The fix, and Notes.

3. Decide which account runs it

AccountUse it whenWatch out for
NT AUTHORITY\SYSTEMThe script works only on this computer (files, services, registry, local software). The simplest choice, and no password to manage.Very powerful. It uses the computer account on the network, so it has no access to a file share unless that share grants it to the computer. Anything you run here runs with full local rights.
A dedicated service account (domain or local user)The script has to reach network resources as a named account, and you want that access to be auditable and limited.The password is stored with the task. When it expires or changes, the task stops working. The account needs the “Log on as a batch job” right.
A group Managed Service Account (gMSA)Domain environment, and you want a service identity whose password Windows manages for you.Needs Active Directory set up for gMSAs and the computer allowed to retrieve the password. Test it before you rely on it.
Your own account, “Run only when user is logged on”A quick test, or a script that has to show something on your desktop.Does nothing when you are logged off. Not for production.

Whichever you pick, give it the least rights the script needs. Do not make the task run as SYSTEM or as a domain admin just because it is quicker.

Root cause

The reason the settings below matter is that a task started by the Task Scheduler service has none of the things you take for granted at a prompt: no logged-on desktop, no profile, no mapped drives, a different current folder, and possibly a different account. Setting the principal, the Start in folder, the time limit and the missed-start behaviour on purpose, and logging from inside the script, is what makes a scheduled run behave like your manual run and leave a trail.

The fix

Read this before you register anything. A scheduled task runs the script as the account you choose, often with the highest privileges. Anyone who can edit that script file can therefore run their own code as that account. Keep the script in an administrators-only folder, and check the folder’s permissions. Never point a SYSTEM task at a script in a user profile, a shared folder, or C:\Temp.

Option A: the Task Scheduler window

Use Create Task, not Create Basic Task. The basic wizard hides most of the options that decide whether a task works unattended. Open Task Scheduler (taskschd.msc) as an administrator, and in the Actions pane choose Create Task.

  1. General tab: give the task a name and a description. Under Security options, click Change User or Group and choose the account from the table above. Choose Run whether user is logged on or not, so it runs with nobody logged on. For a normal account, Windows asks for the password when you save. Tick Run with highest privileges if the script needs administrator rights.
  2. Triggers tab: choose New, then set Begin the task to On a schedule and pick daily, weekly or monthly with a start time. Leave Enabled ticked.
  3. Actions tab: choose New, with Action set to Start a program. In Program/script enter powershell.exe. In Add arguments enter -NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Nightly.ps1". In Start in (optional) enter C:\Scripts with no quotes, so relative paths in the script resolve.
  4. Conditions tab: on a laptop, untick the power conditions (start only on AC power, stop on battery) if the task must run on battery. On a server, the defaults are usually fine.
  5. Settings tab: tick Run task as soon as possible after a scheduled start is missed, so a computer that was off at 02:00 catches up when it next starts. Tick Stop the task if it runs longer than and set a sensible limit such as 1 hour, so a hung script cannot run forever. Set If the task is already running, then the following rule applies to Do not start a new instance.

Select OK, and enter the password if you are asked. Then right-click the task and choose Run to test it.

Option B: PowerShell

The same task, written as code, which is easier to repeat on other computers and to keep in version control. Save the script first, then run this in an elevated PowerShell window.

# 1. What to run. -WorkingDirectory sets the "Start in" folder; the default is C:\Windows\System32.
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
    -Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Nightly.ps1"' `
    -WorkingDirectory 'C:\Scripts'

# 2. When to run it.
$trigger = New-ScheduledTaskTrigger -Daily -At 2:00AM

# 3. Who runs it, and with what rights.
$principal = New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\SYSTEM' -LogonType ServiceAccount -RunLevel Highest

# 4. How it behaves: catch up after a missed start, give up after one hour, never run two copies at once.
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable `
    -ExecutionTimeLimit (New-TimeSpan -Hours 1) `
    -MultipleInstances IgnoreNew

# 5. Save it. TaskPath puts it in its own folder so it is easy to find.
Register-ScheduledTask -TaskName 'Nightly' -TaskPath '\KEH\' `
    -Action $action -Trigger $trigger -Principal $principal -Settings $settings `
    -Description 'Runs C:\Scripts\Nightly.ps1 every night at 02:00'

What each part does:

  • New-ScheduledTaskAction -Execute powershell.exe -Argument ... sets the program and its arguments. -NoProfile skips the profile scripts, which keeps the run predictable. -File runs your script. -WorkingDirectory is the Start in folder.
  • New-ScheduledTaskTrigger has -Once, -Daily, -Weekly, -AtStartup and -AtLogOn forms. For example, -Weekly -DaysOfWeek Monday -At 6:00AM. -RandomDelay spreads the start time, which helps if many computers share a file server.
  • New-ScheduledTaskPrincipal -UserId ... -LogonType ... -RunLevel Highest sets the account and runs the task with the highest privileges. -LogonType ServiceAccount is for SYSTEM and the other built-in service accounts. For a normal account, use -LogonType Password (see below).
  • New-ScheduledTaskSettingsSet: -StartWhenAvailable lets the task start after its scheduled time has passed (the catch-up behaviour), -ExecutionTimeLimit sets the longest it may run, and -MultipleInstances IgnoreNew ignores a new start while the previous run is still going. Without an execution time limit, Task Scheduler stops a task 72 hours after it starts.
  • Register-ScheduledTask saves the definition. With -Principal, Task Scheduler can run the task whether or not that account is logged on.

Running as a service account or a gMSA

To use a normal account, pass its name and password to Register-ScheduledTask. The password is ignored for SYSTEM, LOCAL SERVICE and NETWORK SERVICE, and is needed for everything else.

# A normal domain or local account. The password is stored by Task Scheduler.
$principal = New-ScheduledTaskPrincipal -UserId 'CONTOSO\svc-nightly' -LogonType Password -RunLevel Highest
Register-ScheduledTask -TaskName 'Nightly' -TaskPath '\KEH\' `
    -Action $action -Trigger $trigger -Principal $principal -Settings $settings `
    -User 'CONTOSO\svc-nightly' -Password 'the-password'

That puts the password in your command history. Run it from a session where history is not kept, or prompt for it (Read-Host -AsSecureString and convert it only inside that one command), and clear the history afterwards. A gMSA avoids the problem completely.

For a gMSA, the account is created in Active Directory first (Microsoft documents New-ADServiceAccount for this) and the computer is allowed to retrieve its password. Microsoft lists scheduled tasks among the things that can run as a gMSA. The task is then registered with the gMSA name, which ends in a dollar sign, and no password:

# A group Managed Service Account: note the trailing $ and that no password is supplied.
$principal = New-ScheduledTaskPrincipal -UserId 'CONTOSO\gmsa-nightly$' -LogonType Password -RunLevel Highest
Register-ScheduledTask -TaskName 'Nightly' -TaskPath '\KEH\' `
    -Action $action -Trigger $trigger -Principal $principal -Settings $settings

[UNCONFIRMED: that -LogonType Password is the correct logon type for a gMSA principal. Microsoft Learn lists the logon types but I could not confirm a gMSA example. Test on one computer and check the task’s Run As user in Task Scheduler before you roll it out.] The gMSA, like any account, also needs the “Log on as a batch job” right on the computer, and read access to the script.

Verification

Test it now

Do not wait until 02:00 to find out. Start the task manually and look at the result:

Start-ScheduledTask -TaskName 'Nightly' -TaskPath '\KEH\'
Start-Sleep -Seconds 10
Get-ScheduledTaskInfo -TaskName 'Nightly' -TaskPath '\KEH\'

In the Task Scheduler window, the equivalent is to right-click the task and choose Run, then press F5 to refresh the Last Run Result column. A manual run uses the task’s own account and settings, so it is a fair test of everything except the trigger time. To test the trigger, temporarily set it a few minutes ahead.

Get the result

# Last run time, last result, next run time, missed runs
Get-ScheduledTaskInfo -TaskName 'Nightly' -TaskPath '\KEH\'

# The result as hex, which is how the codes are documented
'0x{0:X}' -f (Get-ScheduledTaskInfo -TaskName 'Nightly' -TaskPath '\KEH\').LastTaskResult

# Current state: Ready, Running or Disabled
Get-ScheduledTask -TaskName 'Nightly' -TaskPath '\KEH\' | Select-Object TaskName, State

Get-ScheduledTaskInfo gives the last run time, the next run time and LastTaskResult. The same number is in the Last Run Result column in the window. The next table covers the values you will see most.

Last resultWhat Microsoft documents it asWhat to check
0x0The operation completed successfully.The program started and returned 0. That does not prove the script did what you wanted, so check its log or output.
0x1Incorrect function (ERROR_INVALID_FUNCTION).For a PowerShell script this is most often the script’s own exit code of 1: it ran and failed. Look at the script log. [UNCONFIRMED: Microsoft Learn documents 0x1 only as a Windows error code. That Task Scheduler passes the program’s exit code through as the result is common behaviour that I could not find stated in the docs.]
0x2The system cannot find the file specified (ERROR_FILE_NOT_FOUND).The program or file could not be found. Check the path in Program/script, and use powershell.exe or its full path. Check the path after -File too, and the Start in folder.
0x41301SCHED_S_TASK_RUNNING: the task is currently running.Not an error. If it stays this way, the script is hung or waiting for input. Look in Task Manager, and set an execution time limit.
0x41303SCHED_S_TASK_HAS_NOT_RUN: the task has not yet run.Normal for a new task. If it persists past the trigger time, check the trigger is enabled and the computer was on.
0x80041320SCHED_E_USER_NOT_LOGGED_ON: the task will not run because the user is not logged on.The task is set to “Run only when user is logged on”. Change it to “Run whether user is logged on or not”.

If you see a value that is not in this table, convert it to hex and look it up. Codes beginning 0x8004 are from the Task Scheduler error list, and small numbers such as 1 and 2 are Windows system error codes. Both lists are on Microsoft Learn (see Notes).

See the history

In the Task Scheduler window, select the task and open the History tab. It shows each step: triggered, started, action started, action completed. Microsoft’s guide points to this tab first when a task does not run as expected. If the tab is empty, history logging is probably off. [UNCONFIRMED: the exact menu name for switching history on in the Task Scheduler window. It is an action in the Actions pane, at the root of Task Scheduler Library, named something like “Enable All Tasks History”.] You can turn on the same log from an elevated command prompt, which Microsoft documents for the Operational log:

# Turn the Task Scheduler Operational log on (elevated prompt)
wevtutil sl Microsoft-Windows-TaskScheduler/Operational /e:true

# Check it is enabled
wevtutil gl Microsoft-Windows-TaskScheduler/Operational

# Read the 20 most recent events
Get-WinEvent -LogName 'Microsoft-Windows-TaskScheduler/Operational' -MaxEvents 20 | Format-List TimeCreated, Id, Message

The log is Event Viewer > Applications and Services Logs > Microsoft > Windows > TaskScheduler > Operational. Microsoft notes that this log has to be enabled manually before it records anything, so turn it on before you need it. It shows whether the task started at all, which account it ran as, and the result code, which is exactly what is missing when the task seems to do nothing.

Log from the script itself

Task Scheduler does not keep your script’s output. Add your own logging at the top of the script so every run leaves a record, and so a failure produces a non-zero exit code that you can see in Last Run Result:

# Top of Nightly.ps1: write everything to a log file next to the script.
$ErrorActionPreference = 'Stop'
$log = Join-Path $PSScriptRoot 'Nightly.log'
Start-Transcript -Path $log -Append | Out-Null
try {
    "Started $(Get-Date -Format s) as $(whoami)"
    # ... the real work goes here ...
    "Finished OK"
}
catch {
    "FAILED: $($_.Exception.Message)"
    exit 1          # a non-zero exit code shows up as the task's Last Run Result
}
finally {
    Stop-Transcript | Out-Null
}

Keep the log in the same administrators-only folder, or in a separate log folder, and trim old logs from the script. For a PowerShell script, Microsoft also suggests turning on PowerShell transcription (a Group Policy setting) to capture errors, and trying Run only when user is logged on temporarily to find out whether the problem is the security context.

Remove the task

# Look first, then remove
Get-ScheduledTask -TaskName 'Nightly' -TaskPath '\KEH\'
Unregister-ScheduledTask -TaskName 'Nightly' -TaskPath '\KEH\' -Confirm:$false

# Optional: keep a copy of the definition before you delete it
Export-ScheduledTask -TaskName 'Nightly' -TaskPath '\KEH\' | Out-File C:\Scripts\Nightly-task.xml

Deleting the task does not delete the script or its logs. Remove those yourself if they are no longer needed. In the window, right-click the task and choose Delete.

Notes

  • About -ExecutionPolicy Bypass. It makes this one powershell.exe run the script with nothing blocked and no warnings. That is why it is common for scheduled scripts: it stops the execution policy from silently blocking an unsigned script when nobody is there to answer a prompt. It also means this launch checks nothing about the script. The execution policy is a safety setting, not a lock, so the real protection is who is allowed to change the file. Keep the script in a folder where only administrators have write access. If you can, sign the script and use AllSigned instead.
  • Check the folder permissions. Right-click the folder, choose Properties, then Security. Standard users and “Authenticated Users” should have Read at most, not Modify or Write. Do not use a path that is user-writable, or a network share where others can edit the file, for a task that runs as SYSTEM or as an administrator.
  • Mapped drives do not exist. A task running with nobody logged on has no drive letters from your session. Use UNC paths (\\server\share) and make sure the task’s account has access to the share.
  • The password lives with the task. If the service account’s password is changed or expires, the task fails until you update it. Set the account to a password that does not expire (and protect it), or use a gMSA.
  • Quotes in the arguments. Use double quotes around the script path in -File if it contains a space. In the window, the arguments go in Add arguments, not in the Program/script box, and the Start in value is entered without quotes.
  • Old tasks and time limits. The default limit is 72 hours. A hung script can hold the task in Running for days, and with -MultipleInstances IgnoreNew the next scheduled run is ignored while it is still going. Set a realistic limit.
  • Change a task later. Edit it in the window, or use Set-ScheduledTask. To copy a task to another computer, use Export-ScheduledTask and import the XML with Register-ScheduledTask -Xml.
  • The dialog wording. [UNCONFIRMED: the exact labels on the Create Task dialog (for example “Run whether user is logged on or not”, “Start in (optional)”, “Run task as soon as possible after a scheduled start is missed”). They are from the current Windows 10, 11 and Server dialogs but are not quoted on the Microsoft Learn pages I checked. The settings they control are the documented PowerShell ones above.]
  • Microsoft sources: Register-ScheduledTask, New-ScheduledTaskAction, New-ScheduledTaskTrigger, New-ScheduledTaskPrincipal, New-ScheduledTaskSettingsSet, Start-ScheduledTask, Get-ScheduledTaskInfo, Unregister-ScheduledTask, Troubleshoot issues with scheduled tasks not running, Task Scheduler error and success constants, System error codes (0 to 499), ITaskSettings (72-hour default limit), about_Execution_Policies, wevtutil, Task Scheduler Operational log must be enabled manually, Manage group Managed Service Accounts.

Task runs but does nothing, or ends with a result code not listed here? Post the result code and what the script is meant to do (with names and passwords removed) in The Patch Panel and we will work through it. The best answer earns points on Top of the Stack.

Was this helpful?

Updated on October 14, 2026