By default, recent versions of Windows ship with something called User Account Control (UAC). Because of this, “standard users and administrators access resources and run apps in the security context of standard users”. For this to work Windows creates two different types of tokens based on the user privileges:
- Standard user access token
- Administrator access token
All users receive the standard access token, but only members of the Administrators group will have the administrator token available. However, this token is not used by default. Each time the administrator user needs to perform a task that requires administrator permissions, Windows will prompt the user for approval. According to Windows documentation, this is called an elevation prompt:
When you have access to the interface, is simple just to click Yes. But what if you are working in an automatization task and need this process to be automatic?
Defining the problem
Imagine you need to install something on a Windows Server. For that, you have to be domain admin but also you need to run a command from an elevated Powershell session. In other words, you need both tokens for this particular operation: the standard and the administrator one. You check the usual cmdlets. Perhaps there is something useful in the Start-Process documentation. After a brief glance to the available parameters, you spot two great candidates for this job: -credential, which will allow you to create a Pscredential object and -Runas, that is going to elevate the session. Exactly what you need. Then you start testing. The first thing you will notice is that -Runas and -Credential are mutually exclusive. Just a little thing, there must be something you can do. This should be something simple, but then you realize you are in a Stackoverflow question from 10 years ago. Maybe you can call Start-Process with the -credential that you need, and in the -Argument you can make another call to Start-Process but this time with the -Runas paramenter. An Inception kind of thing. You keep testing but nothing seems to work. Then you start testing the parameters separately, -credential and -runas. When you review the runas documentation, you realize that Windows is expecting you to click Yes on the UAC prompt and that is obviously an issue for automatization purposes. In this scenario, perhaps a corporate one, turning UAC off is definitely not an option. So, to summarize, we have to find a way to automatically do the following:
- We need to run a task that requires both standard user and administrator user tokens.
- We do not have a way to click yes on the UAC prompt.
- We are not allowed to turn UAC off.
- Installing a third party apps is off the table.
With that said, let us review a way to do just that with little to non compromises.
Task scheduler cmdlets to the rescue
This is an option suggested in several entries in the web, but I got my definitive answer by checking the parameters of the task related cmdlets in Powershell. To create a task in PS, you need to create a couple of elements and then register the task. The basic logic goes like this: you create an action, stablish a trigger and then register the task. For each of those there is a cmdlet.
Action | New-ScheduledTaskAction |
Trigger | New-ScheduledTaskTrigger |
Register | Register-ScheduledTask |
Create the action: New-ScheduledTaskAction
Here you define what is going to be executed when the task runs. In our case, we want to execute something with Powershell so it could be -command plus the code you need to run or -file to call a .ps1 script.
$command = "-command `"Set-ExecutionPolicy Bypass -Force`""
$taskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $command
Define the trigger: New-ScheduledTaskTrigger
In this case I only want to execute a command once. The way to execute the task immediately after it has been registered is to use Get-Date in the -At parameter. However, I found this approach sometimes doesn’t work and the task fails to execute. To avoid issues, you can add a delay of a few seconds:
$taskTrigger = New-ScheduledTaskTrigger -Once -At ((Get-Date)+(New-TimeSpan -Seconds 3))
In your main code you can add a Start-Sleep to wait for the task to execute.
Register the task: Register-ScheduledTask
Here is where the magic happens. Once you set a user and password using -User and -Password parameters, this cmdlet also allows you to choose the -RunLevel. If your select Highest there, the administrator token is going to be used and the task is going to run in an elevated Powershell session.
Register-ScheduledTask -TaskName "Your IT Guy" -Action $taskAction -Trigger $taskTrigger -User 'Administrator' -Password 'StrongPassword' -RunLevel Highest
Remember that you should call this cmdlet from an account that is part of the Administrators group, otherwise it is going to fail. In the task scheduler you can check the results of the task. It should say completed if everything worked, but that does not mean that the code executed succesfully. That is a something you have to deal with, since you cannot see the results of the code in task in the current output you are looking at. But the task properties will give you hints in case you need to troubleshoot something.