Run a script as a scheduled task as Administrator

On Windows 11, from an Administrator PowerShell 7.4 instance, I configured a Scheduled task as follows:

$action = New-ScheduledTaskAction -Execute 'PowerShell' -Argument 'C:\myscript.ps1'
$trigger = New-ScheduledTaskTrigger -Daily -At 11:00
$task = New-ScheduledTask -Action $action -Trigger $trigger
Register-ScheduledTask check_updates -InputObject $task

The script simply checks for updates and installs them:

"Scheduled task starts. Exit code was: " | Out-File -Append -NoNewline C:\log1.txt

$candidates = Get-WindowsUpdate
$LASTEXITCODE | Out-File -Append C:\log1.txt

if ($candidates) {
    "There was at least one update" | Out-File -Append C:\log1.txt
    foreach ($available_update in $candidates) {
        if ($available_update.KB) {
            Get-WindowsUpdate -KBArticleID $available_update.KB -Install -Confirm:$false
            "There was a KB update" | Out-File -Append C:\log1.txt
        } else {
            Get-WindowsUpdate -Title $available_update.Title -Install -Confirm:$false
            "There was a generic update" | Out-File -Append C:\log1.txt
        }
    }
}

Get-Date | Out-File -NoNewline -Append C:\log1.txt
" Scheduled task end" | Out-File -Append C:\log1.txt

This Scheduled task does not behave as exepected.

The fact that it has been created from an Administrator shell probably did not grant Administrator privileges to the script execution. But even the simple check Get-WindowsUpdate requires Administrator privileges. $LASTEXITCODE is always empty.

Also, sometimes the Scheduled task does not run at all, even if the system is active (not suspended). This seems to occur randomly. Anyway, the script never managed to download and apply an update when available.

May the Administrator privileges be a first reason for some of these failures? How to run a script as a Scheduled task with Administrator privileges?

I’d start by reading the help for the cmdlets I’m about to use COMPLETELY including the examples.

Register-ScheduledTask -RunLevel ...
2 Likes

Take a look at the graphical snap-in for Task Scheduler, open your task and check the “Actions” tab. If your “Add arguments” field doesn’t start with a -f then that’s your problem.
Working example:
image
The reason is that the “Program/Script” field is expecting an executable to run. When you provide “Powershell” as the target it finds that in the PATH and runs
“C:\windows\System32\WindowsPowerShell\v1.0\powershell.exe”
If you look at how to execute a script file when calling powershell.exe you’ll see that you have to provide the parameter -File, which can be (and often is) abbreviated to -F.
image

Check on that first and then if that’s not it we’ll dig in to other stuff.

3 Likes

<“sarcasm”>

I have seen this more times than I care to admit. What I really love is the very informative information you get in the Task History :slight_smile:

<“/sarcasm”>

2 Likes

Thanks for your suggestion. I tried, but probably there is still something that I’m doing wrong.
According to the Help page, -RunLevel is referred to a -Principal, so I first tried to set

$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Password -RunLevel Highest

and then

Register-ScheduledTask -TaskName check_updates -Trigger $trigger -Action $action -Principal $principal

but nothing changed. The only example in the Register-ScheduledTask page does not deal with a -Principal. I tried as above after googling some more examples, but I did not find a univocal way to do this operation.

Thank you, I tried, but nothing changed.

Hearing this was partially consolatory.

If you use this approach the user has to have administrative rights. Does he or she?

I’d recommend to use a dedicated user account with the apropriate rights of you use the SYSTEM account. :man_shrugging:t3:

From looking at all my own examples of PS for registering scheduled tasks it appears I only ever set it to run as the logged on user, OR i’m setting it to run as System with the highest level privileges.
I tried to create a scheduled task in the way that you did @RileyWooten where I specified -LogonType Password but I keep getting different errors. I believe we have GPO on our work computers that prevents this. Our only options are to run a task as a user that is currently logged in OR run a task as system.
Since you need privileges I’d suggest the latter.

$action = New-ScheduledTaskAction -Execute 'PowerShell' -Argument '-F C:\myscript.ps1'
$trigger = New-ScheduledTaskTrigger -Daily -At 11:00
$principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal
$task.Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask check_updates -InputObject $task

this is what I’d suggest trying based on what you’ve provided so far. It creates the action, using the correct -F syntax for the argument.
It sets up the trigger time. It creates a principal for the SYSTEM to run this task as a service account with highest privilege (this is how all the scheduled tasks are set up on my work computer).
It creates the task using those objects, and then modifies the task settings to let this task run if the computer is on batteries. Then it registers it.

1 Like

Yes, the user has administrative rights. But, after your suggestions, I prefer to use the SYSTEM account.

Thanks for your very detailed example.

I had in fact a doubt about LogonType and the Google search didn’t solve it.

It makes no difference for me, so I follow your advice.

$principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest

Is -LogonType ServiceAccount mandatory, recommended, or can it be omitted?

This is very useful. I forgot to mention that the system is a laptop. The lack of the -AllowStartIfOnBatteries and -DontStopIfGoingOnBatteries options may be the cause of many missed runs (probably my configuration doesn’t meet the 3rd condition in “Windows task scheduler tasks trigger only if the task is”, provided in this page).

Some updates: now the scheduled task runs, but it still doesn’t as expected. I tried to add a code line as follows:

$candidates = Get-WindowsUpdate
$LASTEXITCODE | Out-File -Append C:\log1.txt
$candidates | Out-File -Append C:\log1.txt

Both $LASTEXITCODE and $candidates keep being empty, because nothing is written in the log file. I verified, instead, that some updates are actually available, because, from the PowerShell 7.4 console with Administrative rights, I can run:

PS C:\> $test1 = Get-WindowsUpdate
PS C:\> $test1

ComputerName Status      KB           Size Title
------------ ------      --           ---- -----
myhostname               KB<1>
myhostname               KB<2>

So, even with this configuration, the script is probably not able to run Get-WindowsUpdate. What could it be the reason?

The LogonType parameter is mandatory in my example when using SYSTEM as the principal and setting it to run as a service account. In your example from the other day you had LogonType set to “Password”. From reading the documentation, and testing, this actually requires that you provide to the -Password parameter when registering the scheduled task.
This is the equivalent of when you save a scheduled task in the GUI and it asks you for the password. Mine will never work on this computer due to Group Policy.
LogonType
New-ScheduledTaskPrincipal (LogonType)

Yeah, this bit me a couple times when I couldn’t figure out why a scheduled task was working on a bunch of computers but had failed on one a couple of times. Took a while digging through logs.

It appears I can’t help you with that last bit:
V5.1


V7.4

Keep in mind that Task Scheduler is running Windows Powershell v5.1. In your “Action” for the scheduled task when we put in just "Powershell" it ends up using C:\windows\System32\WindowsPowerShell\v1.0\powershell.exe.
If you want to try to use Powershell 7 you’ll need to change the Action to either pwsh or C:\Program Files\PowerShell\7\pwsh.exe (wherever yours resides).

I’m guessing Get-WindowsUpdate is part of the PSWindowsUpdate module?
https://github.com/mgajda83/PSWindowsUpdate
I’d want to verify that

  1. PSWindowsUpdate is installed on the system where this scheduled task is being set up
  2. That the cmdlet Get-WindowsUpdate works manually when executed as SYSTEM. To do this I would use psexec to run powershell interactively with SYSTEM right.

Lastly I think your use of $LASTEXITCODE might be throwing you off. That variable is for capturing the exit code of the last Windows program that ran. Get-WindowsUpdate is a Powershell cmdlet so you’d want to use the automatic variable $? which is a boolean for whether the previous operation succeeded or not.

$candidates = Get-WindowsUpdate
$$? | Out-File -Append C:\log1.txt
$candidates | Out-File -Append C:\log1.txt

But I think we can do better

$Candidates = try {
    Get-WindowsUpdate -ErrorAction Stop
} catch {
    Write-Output "Failed to get updates"
    $Error[0]
}
$Candidates | Out-File -Append C:\log1.txt

In this example we put Get-WindowsUpdate in a try/catch block. We deliberately specify the ErrorAction is “Stop” just to ensure that any error that’s thrown is treated as a terminating error. In the catch block we output a string message to ourselves and then also the most recent error.
Then we write the contents of the $Candidates variable to the file.
In my examples that log file now looks like this

Failed to get updates
Get-WindowsUpdate : The term 'Get-WindowsUpdate' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify 
that the path is correct and try again.
At line:2 char:5
+     Get-WindowsUpdate -ErrorAction Stop
+     ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Get-WindowsUpdate:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Maybe this will help you find out why Get-WindowsUpdate isn’t working in the task.

2 Likes

Yes, as mentioned in the first post I would like to use Powershell 7.4. This is also its location in my system.

Yes, exactly: it is that external, imported module.

That was the problem! The SYSTEM user did not have the module installed. Modifying the code as suggested, I got exactly the same error: The term 'Get-WindowsUpdate' is not recognized as the name of a cmdlet, function…

Then, I ran PowerShell with psexec interactively as SYSTEM (whoami returned nt authority\system) and manually installed the module. It now appears in the output of Get-InstalledModule and I can successfully run Get-WindowsUpdate from the SYSTEM user interactive prompt.

Despite this, the scheduled task didn’t run or failed. This is the updated script code:

Get-Date | Out-File -NoNewline -Append C:\log1.txt
" Scheduled task starts." | Out-File -Append C:\log1.txt

$candidates = try { 
    Get-WindowsUpdate -ErrorAction Stop
} catch {
    "Failed to get updates" | Out-File -Append C:\log1.txt
    $Error[0] | Out-File -Append C:\log1.txt
}

if ($candidates) {
    "There was at least one update" | Out-File -Append C:\log1.txt
    foreach ($available_update in $candidates) {
        if ($available_update.KB) {
            Get-WindowsUpdate -KBArticleID $available_update.KB -Install -Confirm:$false
            "There was a KB update" | Out-File -Append C:\log1.txt
        } else {
            Get-WindowsUpdate -Title $available_update.Title -Install -Confirm:$false
            "There was a generic update" | Out-File -Append C:\log1.txt
        }
    }
}

Get-Date | Out-File -NoNewline -Append C:\log1.txt
" Scheduled task end" | Out-File -Append C:\log1.txt

It generates

LastTaskResult : 4294770688

and it doesn’t write nothing to C:\log1.txt.

I configured the Scheduled task in a PowerShell 7.4 instance, acting as my local user, with Administrative privileges: so, it is not a SYSTEM user shell. May this be relevant?

Another weird behaviour is that, in the interactive SYSTEM shell, I have

PS C:\Windows\System32> Get-ScheduledTaskInfo -TaskName check_updates
Get-ScheduledTaskInfo: The term 'Get-ScheduledTaskInfo' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

but all the cmdlets used in the script are recognized.

Acting as an administrator should still let you create a scheduled task whose principal is SYSTEM. That’s how most of the tasks in my Org are done.
Why the ScheduledTasks cmdlets aren’t available to SYSTEM I’m not sure, but hopefully it’s not relevant to issue.

It sounds like the current variable that’s causing the problem is the fact that you’re trying to leverage PowerShell 7 for action execution.
I don’t think there’s anything in your script that requires PowerShell 7, what about just changing the scheduled task to use regular powershell.exe ?

Also, for clarification sake it might be helpful to open up the GUI for Scheduled Tasks, open the properties of your task, go to the Actions tab, and take a picture and paste it here (redacted if need be). That way we can see for sure how that part is set up.

1 Like

Ok!

Ok! I repeated the whole procedure and now it works.

If it can be useful, I recap all the steps made:

  1. I opened a Windows PowerShell 5.1 instance with psexec.exe acting as nt authority\system.
  2. From that shell, I ran Install-Module PSWindowsUpdate (which required the NuGet provider, automatically installed after giving permission interactively), giving permission to install from the repository PSGallery.
  3. I verified from that shell that Get-WindowsUpdate actually is recognized and acts as expected.
  4. (Is this relevant?) From that shell I also ran Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser, so this has been applied to user nt authority\system.
  5. From the GUI, I opened another Windows PowerShell 5.1 instance (note that this is WIndows PowerShell 5.1, not PowerShell 7.4), running as Administrator. So, this shell is ran by my local user account, acting as Administrator.
  6. I configured a Scheduled Task as suggested above:
$action = New-ScheduledTaskAction -Execute 'PowerShell' -Argument '-F C:\myscript.ps1'
$trigger = New-ScheduledTaskTrigger -Daily -At 11:00
$principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal
$task.Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask check_updates -InputObject $task
  1. Note that, by specifying -Execute 'PowerShell', this task is run using Windows PowerShell 5.1 and not PowerShell 7.4.
  2. The task successfully ran and the log file was written as expected.

This is ok now.

Probably the error of the previous post was due to the fact that for the steps 5-6 I was using a PowerShell 7.4 instance, instead of a Windows PowerShell 5.1 instance. However, scheduled tasks are visible on both PowerShell 7.4 and Windows PowerShell 5.1, regardless of where they have been created.

I repeated all the above steps using PowerShell 7.4 instead of Windows PowerShell 5.1. I had to perform step 4 before step 2.
This time, the script ran and it provided a LastTaskResult : 0. However, only unreadable characters were printed in the log file (and they are different each time), instead of the expected strings provided in the script.

This is the Actions tab in the properties of the Scheduled Task from the GUI Task Scheduler:

Actions
Start a program
C:\Program Files\PowerShell\7\pwsh.exe -F C:\myscript.ps1

With more detail, if I press the “Edit” button in the “Actions” tab:

Action: Start a program
Settings
Program/script: C:\Program Files\PowerShell\7\pwsh.exe
Add arguments (optional): -F C:\myscript.ps1
Start in (optional):

It would be good if this scheduled task could be configured and run also in PowerShell 7.4, but if it’s not possible, Windows PowerShell 5.1 is enough.

Thanks for your help and suggestions!

New-ScheduledTaskPrincipal -UserId "$env:computername\$env:USERNAME" -RunLevel Highest

If you use this you end up with the account of the user running the code/script creating the scheduled task - not the user currently logged on to the computer during the runtime of the task. :point_up:t3: :wink:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.