
How to Deploy USER Context Scheduled Tasks in Microsoft Intune
There are moments when working with Microsoft Intune makes me miss the simplicity of classic Group Policy Objects (GPOs), especially the Logon/Logoff Scripts feature. If you wanted to trigger an action at logon, startup, or any other event, GPO scripts were perfect.
You could argue that Intune Remediation Scripts fill part of that gap. While they are a solid replacement for certain scenarios, they run on a schedule, not on an event trigger. On top of that, many Microsoft 365 tenants license users with Microsoft 365 Business Premium, which does not include remediation scripts at all.
Fortunately, we do have an alternative: Scheduled Tasks. By creating scheduled tasks on endpoints – and binding them to system or user events – we can recreate much of the old GPO behavior. The only catch is that we must deploy them using PowerShell combined with a Win32 app in Intune.
In this post, I’ll walk you through creating a scheduled task that runs in USER context. If you’re looking for a scheduled task that runs under SYSTEM context, I’ve covered that in a separate article which you can find here.
All source files are available on my GitHub page.
Required Files for Deploying a Scheduled Task with a Win32 App
To deploy a scheduled task through Intune using a Win32 app, you’ll need the following files:
- install.ps1 – Creates the scheduled task.
- uninstall.ps1 – Removes the scheduled task when it’s no longer needed.
- task-user.ps1 – Contains the actions the scheduled task will execute.
- detection.ps1 – Detects whether the scheduled task is installed (Intune uses this for Win32 app detection rules).
Creating the files
Let’s start by preparing the folder structure and files needed for the Intune Win32 app that will deploy our USER‑level scheduled task.
sourceuser
o install.ps1
o uninstall.ps1
- files
o task-.ps1
Install.ps1
With the required structure in place, we can begin writing the installation logic. This script will:
- Ensure no duplicate tasks exist
- Create the scheduled task
- Copy task-user.ps1 to a location accessible by the task
We start with a few basic checks. The scheduled task needs access to the PowerShell script, so we create a directory under C:\ProgramData. If the directory doesn’t exist, the script will create it.
# Working directory
$basePath = "$env:ProgramData\ComSim-ScheduledTask-User\"
# Script used by the scheduled task
$userScript = Join-Path -Path "$basePath" -ChildPath "task-user.ps1"
# Create the directory if it does not exists
if (!(Test-Path -Path $basePath)) {
New-Item `
-Path $basePath `
-ItemType Directory `
-Force | Out-Null
}Next, we verify whether a scheduled task with the same name already exists. This can happen if multiple versions of the Win32 app are deployed (for example, version 1 and version 2). If the task is not present, we copy task-user.ps1 from the files directory into the ProgramData folder.
We do include the Security Identifier (SID) of the user in the taskname here. When creating scheduled tasks under USER context, we need to ensure that each user on the device gets their own unique task. Unlike SYSTEM‑level tasks -which apply to the entire machine- USER‑level tasks must not conflict with one another.
On shared devices, multiple users may sign in, and Intune may run the Win32 app for each of them. If every user received a scheduled task with the same name, Windows would overwrite the existing task or fail to create a new one. To prevent this, we generate a unique task name using the user’s Security Identifier (SID).
# SID of current user, we will use this to create a unique name for the task
$SID = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
# Remove the scheduled task if it is present. We will create a new one with the same name.
Unregister-ScheduledTask `
-TaskName "ComSim - ScheduledTask User - $SID" `
-Confirm:$false `
-ErrorAction SilentlyContinue
# Copy the script we want to run with the scheduled task to the directory
Copy-Item `
(Join-Path -Path "$PSScriptRoot\files" -ChildPath "task-user.ps1") `
$systemScript `
-ForceNow that the script is in place and we’ve confirmed no conflicting tasks exist, we can create our new scheduled task.
We define:
- Action – What the task should execute (in this case, a PowerShell script).
- Trigger – When the task should run (here: at logon of the user who installed it).
- TaskSettings – General configuration such as allowing the task to run on battery power.
Because this scheduled task runs under USER context, we need to ensure it is uniquely tied to the correct user. To achieve this, we include the user’s Security Identifier (SID) in the task name. The SID is a unique value assigned to every Windows user account, and using it prevents conflicts on shared devices where multiple users may sign in.
We also specify the -User parameter when creating the trigger. A user can only register scheduled tasks that run under their own context, so this parameter ensures the task is correctly scoped to the current user.
Once these components are defined, we register the task using Register-ScheduledTask.
# 1. Lets define the action, we want to run PowerShell which will then execute the task in the given location
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$userScript`""
# 2. The action that will trigger the scheduled task, a logon of the user
# Because the user (under which this script is running) is not allowed to create tasks for other users
# We will have to specify the account the which can trigger the task (the current user).
$user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $user
$trigger.Delay = "PT2M" # If needed you can add a delay (e.g. 2 min), remove this line if it is not needed
# 3. Settings for the task::
# -StartWhenAvailable: Indicates that Task Scheduler can start the task at any time after its scheduled time has passed.
# -DontStopIfGoingOnBatteries: Indicates that the task does not stop if the computer switches to battery power.
# -AllowStartIfOnBatteries: Indicates that Task Scheduler starts if the computer is running on battery power. It will still run if its plugged in ;)
$taskSettings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable
Register-ScheduledTask `
-TaskName "ComSim - ScheduledTask User - $SID" `
-Action $action `
-Trigger $trigger `
-Settings $taskSettings `
-Description "Complex Simplicity scheduled task" `
-ForceFull install.ps1 script
The complete script looks like this:
# Working directory
$basePath = "$env:ProgramData\ComSim-ScheduledTask-User\"
# Script used by the scheduled task
$userScript = Join-Path -Path "$basePath" -ChildPath "task-user.ps1"
# Create the directory if it does not exists
if (!(Test-Path -Path $basePath)) {
New-Item `
-Path $basePath `
-ItemType Directory `
-Force | Out-Null
}
# SID of current user, we will use this to create a unique name for the task
$SID = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
# Remove the scheduled task if it is present. We will create a new one with the same name.
Unregister-ScheduledTask `
-TaskName "ComSim - ScheduledTask User - $SID" `
-Confirm:$false `
-ErrorAction SilentlyContinue
# Copy the script we want to run with the scheduled task to the directory
Copy-Item `
(Join-Path -Path "$PSScriptRoot\files" -ChildPath "task-user.ps1") `
$systemScript `
-Force
# Everything is in place, lets create the scheduled task
# 1. Lets define the action, we want to run PowerShell which will then execute the task in the given location
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$userScript`""
# 2. The action that will trigger the scheduled task, a logon of the user
# Because the user (under which this script is running) is not allowed to create tasks for other users
# We will have to specify the account the which can trigger the task (the current user).
$user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $user
$trigger.Delay = "PT2M" # If needed you can add a delay (e.g. 2 min), remove this line if it is not needed
# 3. Settings for the task::
# -StartWhenAvailable: Indicates that Task Scheduler can start the task at any time after its scheduled time has passed.
# -DontStopIfGoingOnBatteries: Indicates that the task does not stop if the computer switches to battery power.
# -AllowStartIfOnBatteries: Indicates that Task Scheduler starts if the computer is running on battery power. It will still run if its plugged in ;)
$taskSettings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable
Register-ScheduledTask `
-TaskName "ComSim - ScheduledTask User - $SID" `
-Action $action `
-Trigger $trigger `
-Settings $taskSettings `
-Description "Complex Simplicity scheduled task" `
-ForceUninstall.ps1
Just like any Intune Win32 app, our installation script needs a matching uninstall.ps1 to properly remove everything we created. The logic in this file is straightforward: we delete the folder ComSim-ScheduledTask-User in C:\ProgramData (including all files inside it) and remove the scheduled task itself.
# The directory where we stored the script which had to be run by the scheduleded task
$basePath = "$env:ProgramData\ComSim-ScheduledTask-User\"
# SID of the current user
$SID = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
# Remove the scheduled task if it is present.
Unregister-ScheduledTask `
-TaskName "ComSim - ScheduledTask User - $SID" `
-Confirm:$false `
-ErrorAction SilentlyContinue
# Remove the directory and all the files within
Remove-Item -Path "$basePath" -Recurse -ForceTask-user.ps1
The task-user.ps1 file contains the logic you want to execute under USER context whenever that specific user logs on to the device. This script defines the actual action your scheduled task will perform.
In the example below, the script simply writes a message to the console and waits for 10 seconds. When executed correctly under USER context, it will display a minimized PowerShell window for the user, confirming that the scheduled task is running in their session.
# Replace this with code with your own code :)
Write-Host "Hello World"
Start-Sleep 10Creating the Win32 App
Now that all required files and folders are in place, we can package everything into a Microsoft Intune Win32 app using the IntuneWinAppUtil tool. This tool converts your source folder into a .intunewin file, which Intune can deploy to your endpoints.
Detection.ps1
Every Intune Win32 app requires a detection method to verify whether the application (or in this case, the scheduled task) is installed. Since we cannot reliably check for a file or registry key, we use a simple detection script.
This script checks whether the scheduled task exists on the device. If the task is present, Intune will mark the Win32 app as installed. If not, Intune will trigger the installation logic again.
$SID = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
$taskName = "ComSim - ScheduledTask User - $SID"
$task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($task) {
# Task exists > detection success
exit 0
} else {
# Task not found > detection failed
exit 1
}Creating the Win32 App in Intune
After packaging your .intunewin file, upload it to Microsoft Intune. Once uploaded, configure the app with the following commands:
Install command: %SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -file .\install.ps1
Uninstall command: %SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -file .\uninstall.ps1
Using Sysnative ensures the script runs correctly in 64‑bit PowerShell, even when invoked from a 32‑bit Intune process.
Set the Install behavior to User. This is required because the scheduled task runs in User context, and the installation script must also execute with user‑level permissions. After configuring detection rules (using your detection.ps1 script) and assigning the app to the appropriate devices or groups, Intune will deploy the scheduled task automatically.