# Scripts That Run Without You

A script run by a scheduled task has nobody to answer its questions and doesn't start in its own folder, so it takes parameters instead of asking, builds its paths from $PSScriptRoot, leaves a dated log and a Start-Transcript recording, ends with an honest exit code, and runs as the account the task names.

Freelancer · PowerShell · Operating System · Updated November 10, 2026

## After this lesson you can

- Test a script the way a task runs it, with pwsh -NonInteractive -File from another folder, and fix questions and relative paths
- Record what an unattended script did with a dated log, Start-Transcript, and an exit code that says whether it worked
- Choose the account a task runs as with New-ScheduledTaskPrincipal, from you while signed in, to you at any time, to SYSTEM

## Key takeaways

- Test a scheduled script the way the task runs it: pwsh -NoProfile -NonInteractive -File and its full path, from another folder.
- Under -NonInteractive, Read-Host fails, and a script that carries on can still end with exit code 0: "it worked".
- A task with no start-in folder starts in C:\Windows\System32, so build every path from $PSScriptRoot.
- A log function adds a dated line for each event, run after run; Start-Transcript records everything one run printed, errors included.
- A task runs as you while you're signed in, unless its principal says otherwise. Run as yourself unless the job needs more.

The morning after Sam scheduled the evening backup, the backup folder is empty. Task Scheduler says the last run's result was 0: it worked. Both are true. The task ran the script, and the script did nothing, because it was written for a person at the keyboard, and at 6 PM there was no keyboard, no screen, and not even the right folder. A script that runs with nobody watching is an **unattended script**, and it needs a few habits that a script you run yourself can do without. IT techs learn them the hard way, from a backup that "worked" for a month.

## Run it the way the task will

Make a practice folder with a little school work in it:

```powershell
New-Item -ItemType Directory -Force -Path "$HOME\terminal-practice\nightly\school-work\science" | Out-Null
Set-Content -Path "$HOME\terminal-practice\nightly\school-work\history-essay.txt" -Value 'Why the Town Library Matters', 'Second draft, with the quotes fixed.'
Set-Content -Path "$HOME\terminal-practice\nightly\school-work\science\lab-report.txt" -Value 'Lab 4: how fast does ice melt in salt water?'
```

Save this first try as `Backup-SchoolWork.ps1` in the `nightly` folder. It works fine when you run it yourself from that folder:

```powershell
# Backup-SchoolWork.ps1, first try: fine at the keyboard
$answer = Read-Host 'Back up school-work now? (y/n)'
if ($answer -eq 'y') {
    Copy-Item -Path school-work -Destination backup -Recurse -Force
    'Backup done.'
}
```

Now run it the way the task from *Scheduled Tasks* will. A task with no start-in folder starts in `C:\Windows\System32`, so go there (only to look; nothing there changes) and start the script with the task's own command:

```powershell
Set-Location C:\Windows\System32
pwsh -NoProfile -NonInteractive -File "$HOME\terminal-practice\nightly\Backup-SchoolWork.ps1"
$LASTEXITCODE
```

```text
Read-Host: C:\Users\Sam\terminal-practice\nightly\Backup-SchoolWork.ps1:2
Line |
   2 |  $answer = Read-Host 'Back up school-work now? (y/n)'
     |            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | PowerShell is in NonInteractive mode. Read and Prompt functionality is not available.

0
```

`-NonInteractive` tells PowerShell nobody is there, so the question fails, `$answer` stays empty, the copy is skipped, and the script ends normally: exit code 0. *Asking Questions: `Read-Host`* gave the fix. A script that runs alone takes **parameters**, with defaults for everything, and never asks.

## Full paths, from the script's own folder

Take the question out, save, and run it the same way:

```powershell
# Backup-SchoolWork.ps1, second try: no question
Copy-Item -Path school-work -Destination backup -Recurse -Force
'Backup done.'
```

```powershell
pwsh -NoProfile -NonInteractive -File "$HOME\terminal-practice\nightly\Backup-SchoolWork.ps1"
$LASTEXITCODE
```

```text
Copy-Item: C:\Users\Sam\terminal-practice\nightly\Backup-SchoolWork.ps1:2
Line |
   2 |  Copy-Item -Path school-work -Destination backup -Recurse -Force
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Cannot find path 'C:\Windows\System32\school-work' because it does not exist.
Backup done.

0
```

Two problems. `school-work` was looked for in the folder the task started in, and the script then said `Backup done.` and ended with 0 anyway. The cure for the first is *Run It from Anywhere*'s `$PSScriptRoot`, the script's own folder, in front of every path. Not `$HOME`: as you'll see below, a task can run as another account, with another home folder.

## Leave a record: a log and Start-Transcript

Nobody sees a scheduled script's screen, so it writes down what happened. Here's the script, rewritten for running alone:

```powershell
# Backup-SchoolWork.ps1: safe to run with nobody there
param(
    [string]$Source = "$PSScriptRoot\school-work",
    [string]$Destination = "$PSScriptRoot\backup"
)
Start-Transcript -Path "$PSScriptRoot\last-run.txt" -UseMinimalHeader | Out-Null

function Write-Log {
    param([string]$Message)
    $line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $Message"
    Add-Content -Path "$PSScriptRoot\backup.log" -Value $line
    $line
}

Write-Log "Backup started: $Source"
try {
    New-Item -ItemType Directory -Force -Path $Destination | Out-Null
    Copy-Item -Path "$Source\*" -Destination $Destination -Recurse -Force -ErrorAction Stop
    Write-Log 'Backup finished'
    $code = 0
}
catch {
    Write-Log "Backup failed: $_"
    $code = 1
}
Stop-Transcript | Out-Null
exit $code
```

Three records, three jobs. `Write-Log` adds one dated line per event to `backup.log`, run after run: the diary. `Start-Transcript` (read "start transcript") records everything one run prints, errors included, into `last-run.txt`, like a recording of the window nobody watched; `-UseMinimalHeader` keeps its heading short. And `exit $code` makes the exit code, the number Task Scheduler shows, tell the truth. Save it, and run it as the task would, with a typo in `-Source`, the way it would go on a night the folder had been renamed:

```powershell
pwsh -NoProfile -NonInteractive -File "$HOME\terminal-practice\nightly\Backup-SchoolWork.ps1" -Source "$HOME\terminal-practice\nightly\school-wrok"
$LASTEXITCODE
Get-Content "$HOME\terminal-practice\nightly\last-run.txt"
```

```text
2026-11-09 08:13:38 Backup started: C:\Users\Sam\terminal-practice\nightly\school-wrok
2026-11-09 08:13:38 Backup failed: Cannot find path 'C:\Users\Sam\terminal-practice\nightly\school-wrok' because it does not exist.

1

**********************
PowerShell transcript start
Start time: 20261109081338
**********************
2026-11-09 08:13:38 Backup started: C:\Users\Sam\terminal-practice\nightly\school-wrok
PS>TerminatingError(Copy-Item): "The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: Cannot find path 'C:\Users\Sam\terminal-practice\nightly\school-wrok' because it does not exist."
2026-11-09 08:13:38 Backup failed: Cannot find path 'C:\Users\Sam\terminal-practice\nightly\school-wrok' because it does not exist.
**********************
PowerShell transcript end
End time: 20261109081338
**********************
```

Exit code 1, so Task Scheduler's last result says something went wrong, and the transcript even caught the error that `catch` handled. Your times will be your own. Windows PowerShell 5.1 has no `-UseMinimalHeader`: leave it out there, and the heading lists your user name, computer and versions.

## Which account it runs as

A task runs as somebody. With no say from you, it's you, only while you're signed in: it can do what you can do, and no more. `New-ScheduledTaskPrincipal` (a *principal* is the account a task runs as) builds the other choices, and Windows' own tasks show the strongest one:

```powershell
New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U | Select-Object UserId, LogonType, RunLevel
(Get-ScheduledTask -TaskName ScheduledDefrag).Principal | Select-Object UserId, LogonType, RunLevel
```

```text
UserId      LogonType RunLevel
------      --------- --------
Sam               S4U  Limited
SYSTEM ServiceAccount  Highest
```

`S4U` runs as you whether you're signed in or not, without saving your password, but it can't reach network drives. `SYSTEM` is Windows' own account, stronger than an administrator, and its home folder is `C:\Windows\System32\config\systemprofile`, which is one more reason for `$PSScriptRoot`. Hand a principal to `Register-ScheduledTask` with `-Principal $principal`; on most PCs, a task that runs while you're signed out, or as SYSTEM, has to be registered from an administrator window. The rule IT calls the **principle of least privilege** says pick the weakest account that can do the job, and for a school-work backup that's you.

## Check yourself

2 questions to try on the lesson page.

## So what

No questions, full paths, a log, a transcript and an honest exit code: that's the difference between a backup that says it worked and one that did.

## Key terms

- [$PSScriptRoot](https://www.ask-codey.com/glossary/psscriptroot)
- [Start-Transcript](https://www.ask-codey.com/glossary/start-transcript)
- [Log File](https://www.ask-codey.com/glossary/log-file)
- [Exit Code](https://www.ask-codey.com/glossary/exit-code-operating-system)
- [SYSTEM Account](https://www.ask-codey.com/glossary/system-account)
- [Principle of Least Privilege](https://www.ask-codey.com/glossary/principle-of-least-privilege)

## Cheat sheets

- [Scripts That Run Without You](https://www.ask-codey.com/cheat-sheets/powershell-unattended-scripts-cheat-sheet)

## Related

- [Scheduled Tasks](https://www.ask-codey.com/lessons/powershell-scheduled-tasks)
- [Asking Questions: \`Read-Host\`](https://www.ask-codey.com/lessons/powershell-read-host)
- [Run It from Anywhere](https://www.ask-codey.com/lessons/powershell-run-from-anywhere)
- [\`try\`, \`catch\` and \`finally\`](https://www.ask-codey.com/lessons/powershell-try-catch)

Source: https://www.ask-codey.com/lessons/powershell-unattended-scripts
