Automating with PowerShell: Impersonating users while running as SYSTEM

I’ve demonstrated in a couple of blogs like the OneDrive Sync Monitoring and the OneDrive File Monitoring that it’s possible to impersonate the current user when a script is actually started by the NT AUTHORITY\SYSTEM account.

My friends asked me if it would not be possible for other scripts to use the same approach. In the previous blogs I’ve shown that by loading the component by MurrayJu we got the ability to impersonate. I converted this into a module which you can find on

This module allows you to run any script that is initiated by SYSTEM and execute it as the currently logged on user. This gives us a lot of freedom. Most RMM systems(and intune!) don’t allow monitoring under the currently logged on user. This often means that you have to work around accessing resources directly in their profile.

Some examples would be accessing installers that run in the users AppData folder, or registry items created under HKCU. Another could be scripts that require accessing shared drives or printers that are only mapped in user-space.

This is also super useful for intune scripts, because you just need to present things to the user or install things using their credentials directly.

Using the module

So, using the module is very straight forward. To install the module execute the following command:

install-module RunAsUser

After you’ve installed the module you can jump straight into scripting. There are some things to account for; The script requires SYSTEM credentials or the SeDelegateSessionUserImpersonatePrivilege privilege.

The second thing is that the output can’t be directly captured. If you want to get output from the script you’ll have to write it to a file and pick that up again in the SYSTEM session. This might sound a little confusing so I have an example below.

$scriptblock = {
$IniFiles = Get-ChildItem "$ENV:LOCALAPPDATA\Microsoft\OneDrive\settings\Business1" -Filter 'ClientPolicy*' -ErrorAction SilentlyContinue

if (!$IniFiles) {
    write-host 'No Onedrive configuration files found. Stopping script.'
    exit 1
$SyncedLibraries = foreach ($inifile in $IniFiles) {
    $IniContent = get-content $inifile.fullname -Encoding Unicode
        'Item Count' = ($IniContent | Where-Object { $_ -like 'ItemCount*' }) -split '= ' | Select-Object -last 1
        'Site Name'  = ($IniContent | Where-Object { $_ -like 'SiteTitle*' }) -split '= ' | Select-Object -last 1
        'Site URL'   = ($IniContent | Where-Object { $_ -like 'DavUrlNamespace*' }) -split '= ' | Select-Object -last 1
$SyncedLibraries | ConvertTo-Json | Out-File 'C:\programdata\Microsoft OneDrive\OneDriveLibraries.txt'
Invoke-AsCurrentUser -scriptblock $scriptblock
} catch{
write-error "Something went wrong"
start-sleep 2 #Sleeping 2 seconds to allow script to write to disk.
$SyncedLibraries = (get-content "C:\programdata\Microsoft OneDrive\OneDriveLibraries.txt" | convertfrom-json)
if (($SyncedLibraries.'Item count' | Measure-Object -Sum).sum -gt '280000') { 
write-host "Unhealthy - Currently syncing more than 280k files. Please investigate."
else {
write-host "Healthy - Syncing less than 280k files."

In the script, we’re executing the Script Block using Invoke-AsCurrentUser command. This runs that entire block of code as the currently logged on user. We then sleep for 2 seconds allowing the script block to finish writing to disk. After this finishes, we pick up the file again under the system account and process the results.

So in short; using this module opens up a lot of user-based monitoring for systems that normally only allow executing under the SYSTEM account. Hopefully this helps people solve some challenges.

As a closing remark I’d like to thank Ben Reader (@Powers_hell) for his help on the module. He assisted in cleaning up the code right after release, making it all look and feel a lot smoother and he assisted in better error handling. Thanks Buddy! 🙂

As always, Happy PowerShelling.

11 thoughts on “Automating with PowerShell: Impersonating users while running as SYSTEM

  1. Steve

    Cool, looking forward to giving this a try, thanks. 🙂

    An alternative method I’ve used up till now is to save the user script block as a PS1 locally somewhere and then create a new scheduled task in the users context that points to the saved script, trigger it, and then remove it again… that kind of works fine, but this looks much more comprehensive. 👍

  2. Kushal

    Hello, does it have to have sedelegatesessionuserimpersonateprivilege set to enable ?
    can you share how to set this attribute to enable ?

  3. Kushal

    For me I get this error :
    invoke-ascurrentuser : Not running with correct privilege. You must run this script as system or have the SeDelegateSessionUserImpersonatePrivilege token.
    At line:3 char:1
    + invoke-ascurrentuser -scriptblock $scriptblock

  4. SRTechOps

    As it stands (unless I’m mistaken) if no user is logged in, the task will execute successfully but not actually work.
    Is there any way we could have it ‘park’ the script and then run as user the next time someone logs in?
    I’ve used this runasuser module combined with burnttoast based on your suggestions and I’m set it up in our RMM so we can send custom messages to users (To notify of Office365 outages or maintenance windows). However if a user isn’t logged in at the time we send the message they will never see it, so it means that sending a message at 7am when we discover an issue won’t be seen by most users as they start later in the day.
    Also – as always, thank you for your amazing contributions to the community.

  5. Eric Chapman

    I’m attempting to run this from “Backstage” in Connectwise Control. (formerly screenconnect).

    This is a powershell window running under the context of SYSTEM.

    The commands seem to run just fine, but no output seems to work. just a number is output to the shell. Is it possible the cmdlet is not acting as expected due to being on a different console?

    Any ideas?

  6. Adam Wheeless

    If I launch a command prompt as SYSTEM (psexec.exe -s -i cmd.exe), I can call a HelloWorld.ps1 based on the example above successfully. However, if I run a PowerShell ISE terminal as SYSTEM (spexec.exe -s -i powershell_ise.exe) and try to execute the same code I get a Windows PowerShell ISE error stating: Error processing arguments: There is no option with the following name: ExecutionPolicy.

    Any idea about what is going on?

    1. Kelvin Tegelaar Post author

      The module tries to use the same exectuable as it was launched under, because ISE is the actual executable and has no support for running scripts directly it fails.

      You can use the -UseWindowsPowerShell option in the invoke-ascurrentuser command, it uses the Windows PowerShell host in that case.

  7. Brad

    Is it possible to pass arguments into the script block?
    I am trying to use RunAsUser along with BurntToast to display a message to the current user while sending the script from my RMM.

    $messagetext = $args[0]

    $scriptblock = {
    $heroimage = New-BTImage -Source ‘’ -HeroImage
    $Text1 = New-BTText -Content “Message from RMM”
    $Text2 = New-BTText -Content $messagetext
    $Button = New-BTButton -Content “Snooze” -snooze -id ‘SnoozeTime’
    $Button2 = New-BTButton -Content “Dismiss” -dismiss
    $5Min = New-BTSelectionBoxItem -Id 5 -Content ‘5 minutes’
    $10Min = New-BTSelectionBoxItem -Id 10 -Content ’10 minutes’
    $1Hour = New-BTSelectionBoxItem -Id 60 -Content ‘1 hour’
    $4Hour = New-BTSelectionBoxItem -Id 240 -Content ‘4 hours’
    $1Day = New-BTSelectionBoxItem -Id 1440 -Content ‘1 day’
    $Items = $5Min, $10Min, $1Hour, $4Hour, $1Day
    $SelectionBox = New-BTInput -Id ‘SnoozeTime’ -DefaultSelectionBoxItemId 10 -Items $Items
    $action = New-BTAction -Buttons $Button, $Button2 -inputs $SelectionBox
    $Binding = New-BTBinding -Children $Text1, $Text2 -HeroImage $heroimage
    $Visual = New-BTVisual -BindingGeneric $Binding
    $Content = New-BTContent -Visual $Visual -Actions $action
    Submit-BTNotification -Content $Content

    Invoke-AsCurrentUser -scriptblock $scriptblock

    The problem is $args[0] doesn’t seem to get pulled in to the script block and -ArgumentList doesn’t seem to work. It doesn’t seem to matter if the $messagetext = $args[0] is inside the script block or outside.

    I am hoping to have the single script in the RMM and just set the text when I run the script so I can send custom messages each time.

    Great module by the way, I’ve got it working for a few other things but just can’t seem to get this to work. Thank you.


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.