Scheduled Tasks are one of the most useful built-in deployment tools on Windows. They are not as polished as Intune, Configuration Manager, or a real RMM platform, but they are available almost everywhere and work well for controlled admin jobs: copy a script, run a cleanup task, install an MSI, start a BAT file, or schedule a one-time maintenance command.

I use scheduled tasks when I need something more reliable than “open a remote PowerShell session and hope the command keeps running.” A task can run as SYSTEM, run whether a user is logged on or not, keep its own history, and start at a specific time. That makes it useful for local work and for remote deployments to a small group of machines.

Quick answer

Use New-ScheduledTaskAction, New-ScheduledTaskTrigger, and Register-ScheduledTask to create Windows scheduled tasks from PowerShell. For local deployment, copy files into a fixed folder such as C:\ProgramData\Company\Deploy, then create a task that runs PowerShell, Robocopy, msiexec.exe, or a BAT file. For remote computers, copy the deployment folder over an admin share, then use PowerShell remoting or schtasks.exe /S to register and start the task. Always log output, test with one machine first, and clean up the task after it finishes if it is a one-time deployment.


When scheduled tasks are a good fit

Scheduled tasks are a good fit when the target machine already trusts your administrator account and the job needs to run locally on that machine. They are especially useful when the command may take longer than an interactive remoting session, or when it needs local system context.

Good examples:

  • Run a PowerShell script during a maintenance window.
  • Copy files from a local staging folder.
  • Run robocopy with retry and logging options.
  • Install an MSI silently with msiexec.exe.
  • Run a vendor-provided .bat file.
  • Start a cleanup job after reboot.
  • Run a one-time remediation task as SYSTEM.

Bad examples:

  • Long-term software management across hundreds of machines without reporting.
  • Jobs that require complex dependency tracking.
  • Tasks that need rich approval workflows.
  • Deployments where you cannot verify success.

For larger environments, use Intune, Configuration Manager, Group Policy, or your RMM if you have one. Scheduled tasks are still useful as a practical fallback.


Create a local scheduled task that runs a PowerShell script

This example creates a folder, writes a simple script, registers a one-time task, and starts it immediately.

$deployRoot = 'C:\ProgramData\PwshTips\Deploy'
$scriptPath = Join-Path $deployRoot 'Run-Maintenance.ps1'
$logPath = Join-Path $deployRoot 'maintenance.log'

New-Item -Path $deployRoot -ItemType Directory -Force | Out-Null

@"
`$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
"`$timestamp Starting maintenance task" | Add-Content -Path '$logPath'

Get-Service | Where-Object Status -eq 'Stopped' |
    Select-Object -First 10 Name, Status |
    Out-File -FilePath '$deployRoot\stopped-services.txt'

`$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
"`$timestamp Finished maintenance task" | Add-Content -Path '$logPath'
"@ | Set-Content -Path $scriptPath -Encoding utf8

$action = New-ScheduledTaskAction `
    -Execute 'powershell.exe' `
    -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""

$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1)

Register-ScheduledTask `
    -TaskName 'PwshTips-Maintenance' `
    -Action $action `
    -Trigger $trigger `
    -Description 'One-time maintenance task created by PowerShell' `
    -User 'SYSTEM' `
    -RunLevel Highest `
    -Force

Start-ScheduledTask -TaskName 'PwshTips-Maintenance'

This runs as SYSTEM, which is useful for machine-level maintenance. If the task needs network access, be careful: SYSTEM on one computer accesses the network as that computer account, not as your user account.


Check the task result

After the task runs, check its state and last result:

Get-ScheduledTask -TaskName 'PwshTips-Maintenance' |
    Get-ScheduledTaskInfo |
    Select-Object LastRunTime, LastTaskResult, NextRunTime

LastTaskResult of 0 usually means success. Non-zero values need investigation. Also check your own log file because Task Scheduler does not know whether your script logic produced the expected result.

Get-Content 'C:\ProgramData\PwshTips\Deploy\maintenance.log'

For script deployments, always write a log. Task Scheduler history is useful, but the script log tells you what your code actually did.


Run Robocopy from a scheduled task

Robocopy is often better than Copy-Item for large file copies. It supports retries, restartable mode, multithreading, and good logs.

This example creates a task that copies a folder from a file share to a local folder:

$source = '\\fileserver\packages\AppFiles'
$dest = 'C:\ProgramData\PwshTips\AppFiles'
$log = 'C:\ProgramData\PwshTips\Deploy\robocopy.log'

New-Item -Path (Split-Path $log) -ItemType Directory -Force | Out-Null

$arguments = "`"$source`" `"$dest`" /MIR /Z /R:3 /W:5 /MT:16 /LOG+:`"$log`""

$action = New-ScheduledTaskAction `
    -Execute 'robocopy.exe' `
    -Argument $arguments

$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1)

Register-ScheduledTask `
    -TaskName 'PwshTips-Robocopy-AppFiles' `
    -Action $action `
    -Trigger $trigger `
    -User 'SYSTEM' `
    -RunLevel Highest `
    -Force

Start-ScheduledTask -TaskName 'PwshTips-Robocopy-AppFiles'

Be careful with /MIR. It mirrors the destination to match the source, including deletion of files that are not in the source. Use /E instead of /MIR if you only want to copy subfolders without deleting extra destination files.

If the source is a network share, confirm the computer account has read access when running as SYSTEM. For example, grant read access to DOMAIN\COMPUTERNAME$ or to a group containing the computer accounts.


Install an MSI with a scheduled task

For MSI packages, run msiexec.exe silently and write an install log.

$msiPath = 'C:\ProgramData\PwshTips\Deploy\ExampleApp.msi'
$msiLog = 'C:\ProgramData\PwshTips\Deploy\ExampleApp-install.log'

$action = New-ScheduledTaskAction `
    -Execute 'msiexec.exe' `
    -Argument "/i `"$msiPath`" /qn /norestart /L*v `"$msiLog`""

$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1)

Register-ScheduledTask `
    -TaskName 'PwshTips-Install-ExampleApp' `
    -Action $action `
    -Trigger $trigger `
    -User 'SYSTEM' `
    -RunLevel Highest `
    -Force

Start-ScheduledTask -TaskName 'PwshTips-Install-ExampleApp'

Common MSI exit codes:

Exit code Meaning
0 Success
1603 Fatal install error
1618 Another installation is already in progress
3010 Success, reboot required

Task Scheduler may only show the process exit code. The MSI log is where the real install detail lives.


Run a BAT file with a scheduled task

Some vendors still ship .bat files. You can run them through cmd.exe.

$batPath = 'C:\ProgramData\PwshTips\Deploy\install-driver.bat'
$batLog = 'C:\ProgramData\PwshTips\Deploy\install-driver.log'

$action = New-ScheduledTaskAction `
    -Execute 'cmd.exe' `
    -Argument "/c `"$batPath`" > `"$batLog`" 2>&1"

$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1)

Register-ScheduledTask `
    -TaskName 'PwshTips-Run-BAT' `
    -Action $action `
    -Trigger $trigger `
    -User 'SYSTEM' `
    -RunLevel Highest `
    -Force

Start-ScheduledTask -TaskName 'PwshTips-Run-BAT'

The > log 2>&1 part redirects normal output and errors to the same log file. That matters because BAT files often write useful detail only to the console.


Deploy files and tasks to a remote computer

For a small number of computers, I usually stage the files first, then register the scheduled task remotely.

This example copies a deployment folder to the remote computer over the admin share:

$computerName = 'PC001'
$localPackage = 'C:\Deploy\ExampleApp'
$remoteDeployRoot = "\\$computerName\C$\ProgramData\PwshTips\Deploy"

New-Item -Path $remoteDeployRoot -ItemType Directory -Force | Out-Null

robocopy $localPackage $remoteDeployRoot /E /Z /R:3 /W:5 /LOG+:".\deploy-$computerName.log"

Then create the task with PowerShell remoting:

$computerName = 'PC001'

Invoke-Command -ComputerName $computerName -ScriptBlock {
    $scriptPath = 'C:\ProgramData\PwshTips\Deploy\Install-App.ps1'
    $logPath = 'C:\ProgramData\PwshTips\Deploy\Install-App.log'

    $action = New-ScheduledTaskAction `
        -Execute 'powershell.exe' `
        -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`" *> `"$logPath`""

    $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1)

    Register-ScheduledTask `
        -TaskName 'PwshTips-Install-App' `
        -Action $action `
        -Trigger $trigger `
        -User 'SYSTEM' `
        -RunLevel Highest `
        -Force

    Start-ScheduledTask -TaskName 'PwshTips-Install-App'
}

This requires PowerShell remoting to be enabled and allowed by firewall policy. In domain environments, this is usually manageable with Group Policy. In workgroup environments, authentication and TrustedHosts need more care.


Deploy to multiple remote computers

For multiple machines, keep the loop boring and log every result.

$computers = Get-Content '.\computers.txt'
$localPackage = 'C:\Deploy\ExampleApp'

foreach ($computer in $computers) {
    Write-Host "Deploying to $computer" -ForegroundColor Cyan

    try {
        $remoteDeployRoot = "\\$computer\C$\ProgramData\PwshTips\Deploy"
        New-Item -Path $remoteDeployRoot -ItemType Directory -Force | Out-Null

        robocopy $localPackage $remoteDeployRoot /E /Z /R:3 /W:5 /LOG+:".\deploy-$computer.log"

        Invoke-Command -ComputerName $computer -ErrorAction Stop -ScriptBlock {
            $scriptPath = 'C:\ProgramData\PwshTips\Deploy\Install-App.ps1'
            $logPath = 'C:\ProgramData\PwshTips\Deploy\Install-App.log'

            $action = New-ScheduledTaskAction `
                -Execute 'powershell.exe' `
                -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`" *> `"$logPath`""

            $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1)

            Register-ScheduledTask `
                -TaskName 'PwshTips-Install-App' `
                -Action $action `
                -Trigger $trigger `
                -User 'SYSTEM' `
                -RunLevel Highest `
                -Force

            Start-ScheduledTask -TaskName 'PwshTips-Install-App'
        }

        Write-Host "Started task on $computer" -ForegroundColor Green
    }
    catch {
        Write-Host "Failed on $computer: $($_.Exception.Message)" -ForegroundColor Red
    }
}

For production use, export a CSV report instead of relying only on console output.


Use schtasks when remoting is not available

If PowerShell remoting is blocked but RPC and Task Scheduler remote management are available, schtasks.exe can create a remote task.

$computerName = 'PC001'
$taskName = 'PwshTips-Install-App'
$scriptPath = 'C:\ProgramData\PwshTips\Deploy\Install-App.ps1'

schtasks /Create `
    /S $computerName `
    /TN $taskName `
    /SC ONCE `
    /ST 23:00 `
    /RL HIGHEST `
    /RU SYSTEM `
    /TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" `
    /F

schtasks /Run /S $computerName /TN $taskName

I prefer PowerShell remoting when it is available because the code is easier to read and returns better objects. schtasks is still useful on older systems or locked-down networks.


Clean up one-time tasks

If the task is only for deployment, remove it after you confirm the result.

Unregister-ScheduledTask -TaskName 'PwshTips-Install-App' -Confirm:$false

For remote cleanup:

Invoke-Command -ComputerName 'PC001' -ScriptBlock {
    Unregister-ScheduledTask -TaskName 'PwshTips-Install-App' -Confirm:$false
}

Do not remove the logs until you have verified the deployment. Logs are usually more useful than the task definition after the run finishes.


Final checks before using this in production

Before deploying scheduled tasks to many computers, I check these items:

  • The script works locally when run as Administrator.
  • The script works as SYSTEM if the task will run as SYSTEM.
  • Network shares are accessible by the account that runs the task.
  • MSI installs write verbose logs.
  • Robocopy commands use /E or /MIR intentionally.
  • The task has a clear name and description.
  • The deployment writes logs to a predictable folder.
  • One test machine succeeds before a wider rollout.
  • There is a cleanup plan for one-time tasks.

Scheduled tasks are not a full deployment platform, but they are reliable when used carefully. The main rule is to make each step visible: copy the files, create the task, start the task, check the result, read the log, and clean up only after you know what happened.