quser.exe works in console and ISE but not in service

Ask your Windows PowerShell-related questions, including questions on cmdlet development!
Forum rules
Do not post any licensing information in this forum.

Any code longer than three lines should be added as code using the 'Select Code' dropdown menu or attached as a file.
Locked
mac_of_london
Posts: 9
Joined: Tue Dec 11, 2018 1:06 pm

quser.exe works in console and ISE but not in service

Post by mac_of_london » Thu Oct 31, 2019 10:09 am

I have the following function in one of my service scripts, it gets the user sessions on the local device using quser.exe, collects more information for each user account from Get-LocalUser and then returns an array of objects containing the desired user information.

When I run this function in the console it works as expected. However, when the function is run by the service the $QUserToStringOutput is empty because the service cannot find or run quser.exe.

I tried using `$QUserToStringOutput = & quser.exe`, it said it could not be found. I then tried `$QUserToStringOutput = & $ENV:SystemRoot\System32\quser.exe 2>$null` which returned The term 'C:\Windows\System32\quser.exe' 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.

Code: Select all

Function Get-UserSessions{

    Try{

        $QUserToStringOutput = cmd.exe /C $Env:SystemRoot\System32\quser.exe 2>$null

        If(!$QUserToStringOutput){
            New-ServiceEvent -EventID 920 -Severity "Error" -Message "Failed to query user sessions."
        }else{

            $UserSessions = @()

            ForEach($Record in $QUserToStringOutput){

                # Skip the 'column titles' row from quser
                If($Record -match "logon time"){Continue}

                $Additional = (Get-LocalUser -ErrorAction Stop | Where-Object{ $_.Name -eq $Record.SubString(1, 20).Trim()} | Select-Object *)[0]

                $UserSessions += @{
                    Username        = [string]$Record.SubString(1, 20).Trim()
                    FullName        = [string]$Additional.FullName
                    SID             = [string]$Additional.SID
                    PrincipalSource = [string]$Additional.PrincipalSource
                    SessionName     = [string]$Record.SubString(23, 17).Trim()
                    ID              = [int]$Record.SubString(42, 2).Trim()
                    State           = [string]$Record.SubString(46, 6).Trim()
                    Idle            = [int]$Record.SubString(54, 9).Trim().Replace('+', '.')
                    LogonTime       = [string]$Record.SubString(65)
                }
            }

            If($UserSessions.Count -gt 0){
                Save-Payload -Target "user" -Data $UserSessions
            }
        }

    }Catch{
        New-ServiceEvent -EventID 920 -Severity "Error" -Message "Unable to query user sessions. $($_.Exception.Message)"
    }
}
When run in the console, this is the expected/actual ouput:

Code: Select all

PS C:\Project> $UserSessions | Convertto-json
[
    {
        "PrincipalSource":  "MicrosoftAccount",
        "FullName":  "My Name",
        "Username":  "MyUsername",
        "SID":  "S-1-5-21-1001",
        "ID":  1,
        "State":  "Active",
        "LogonTime":  "31/10/2019 11:06",
        "Idle":  42,
        "SessionName":  "console"
    },
    {
        "PrincipalSource":  "Local",
        "FullName":  "",
        "Username":  "test user",
        "SID":  "S-1-5-21-1008",
        "ID":  2,
        "State":  "Disc",
        "LogonTime":  "31/10/2019 12:38",
        "Idle":  42,
        "SessionName":  ""
    }
]
To rule out the user account as the issue, I have tried running the service as SYSTEM and as my administrative MicrosoftAccount, this didn't help.

Does anyone have any tacit knowledge to share? I'm assuming this is a quirk when creating a service over a simple script..?

jvierra
Posts: 14016
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: quser.exe works in console and ISE but not in service

Post by jvierra » Thu Oct 31, 2019 10:49 am

A service runs with no profile and does not have execution access to the system folders. You need to give the service account execution access to the exe and its support DLLs as well as using the full path to the EXE.


I also suggest using Start-Process and not using CMD to call the process.
$output = Start-Process "$env:windir\system32\quser.exe" -NoNewWindow

mac_of_london
Posts: 9
Joined: Tue Dec 11, 2018 1:06 pm

Re: quser.exe works in console and ISE but not in service

Post by mac_of_london » Fri Nov 01, 2019 10:14 am

Hey, thanks for coming back to me. This makes sense, I'll see what I can do!

Also, this wouldn't work, as the output wouldn't be written to the variable:
$output = Start-Process "$env:windir\system32\quser.exe" -NoNewWindow

jvierra
Posts: 14016
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: quser.exe works in console and ISE but not in service

Post by jvierra » Fri Nov 01, 2019 10:56 am

Then do it this way:

$output = . "$env:windir\system32\quser.exe"

Or this way:
$output = Invoke-Expression "$env:windir\system32\quser.exe"

mac_of_london
Posts: 9
Joined: Tue Dec 11, 2018 1:06 pm

Re: quser.exe works in console and ISE but not in service

Post by mac_of_london » Mon Nov 04, 2019 8:29 am

Thanks! I ended up going with this one: $QUserToRichObject = ((Invoke-Expression "$env:windir\system32\quser.exe") -replace '\s{2,}', ',' | ConvertFrom-Csv)

This is the only part I cannot get working, my service is almost complete and I can't believe the only thing I'm having issues with is listing 'active sessions' with some additional user details. I honestly thought this would be the easiest bit!

I still cannot get quser to work. I have tried alternative methods such as `query session` and joining win32_logonsession and win32_loggedonuser, only issue I have is the win32 classes don't seem to show/respect AzureAD accounts!

Code: Select all

$report = @()
$sessions = ((Invoke-Expression 'query session') -replace '\s{2,}', ',' | ConvertFrom-Csv)
ForEach($Session in $Sessions){
    If($Session.SESSIONNAME -Like ">*"){$Session.SESSIONNAME = ($Session.SESSIONNAME -Replace ">", "")}
    $temp = @{
        SessionName = $Session.SESSIONNAME;
        Username = $Session.USERNAME;
        State = $Session.STATE;
        Id = $Session.ID;
    }
    $report += $temp
}
If anyone has a method of getting a list of local user sessions (with SID) in a service I'll be forever in your debt

jvierra
Posts: 14016
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: quser.exe works in console and ISE but not in service

Post by jvierra » Mon Nov 04, 2019 10:56 am

QUSER is used to get RDS sessions and is not used for local sessions but does return the console session.

Azure AD is used to get Azure accounts and does not give sessions.

Here is an example of how to use quser in PowerShell: https://gallery.technet.microsoft.com/s ... s-7cbe93ea

mac_of_london
Posts: 9
Joined: Tue Dec 11, 2018 1:06 pm

Re: quser.exe works in console and ISE but not in service

Post by mac_of_london » Mon Nov 04, 2019 3:08 pm

I created a bounty on StackOverflow, the issue was very silly and nothing to do with the service or service permissions. It was to do with the service being a 32-bit process on a 64-bit system.

There is not a quser.exe in C:\Windows\SysWOW64 but you can work around this on a 64-bit OS by running C:\Windows\Sysnative\quser.exe (see File System Redirector here for details: https://docs.microsoft.com/en-us/window ... redirector).

While you were kindly helping me (and I'm truly grateful), stating the service runs with no profile and does not have execution access to the system folders is simply incorrect.

Hopefully this helps someone in the future, it's something I was completely unaware of (I didn't even see a need to test in a 32-bit console, which in hindsight was a huge failure on my part).

All the best, and thanks again for your willingness to support your customers and community.
Mac

mac_of_london
Posts: 9
Joined: Tue Dec 11, 2018 1:06 pm

Re: quser.exe works in console and ISE but not in service

Post by mac_of_london » Mon Nov 04, 2019 3:11 pm

jvierra wrote:
Mon Nov 04, 2019 10:56 am
QUSER is used to get RDS sessions and is not used for local sessions but does return the console session.

Azure AD is used to get Azure accounts and does not give sessions.

Here is an example of how to use quser in PowerShell: https://gallery.technet.microsoft.com/s ... s-7cbe93ea
Great link, thanks again! :D

I have it doing what I needed now and have realised I need to slow down a little and prepare better before I dive in to the script creation

jvierra
Posts: 14016
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: quser.exe works in console and ISE but not in service

Post by jvierra » Mon Nov 04, 2019 3:30 pm

Again - how the service runs and the access it has is totally about the account that the system runs under. Many accounts like SYSTEM and NETWORK do not have a classic user profile and do not have access to the file system everywhere.

Yes - many system utilities will not be available in 32 bit mode on a 64 bit system. You would do best by running only 64 bit services on a 64 bit system. No modern servers are 32 bit. The last 32 bit server OS was WS2008. As of WS2008r2 only 64 bit architectures are available. The 32 bit layer is only to support legacy apps and will soon disappear.

User avatar
ITEngineer
Posts: 171
Joined: Wed Oct 12, 2011 10:52 am

Re: quser.exe works in console and ISE but not in service

Post by ITEngineer » Tue Nov 05, 2019 11:05 pm

I've been using this code to track down users in the AD domain based on the root OU.

Code: Select all

$threadLimit = 50
$regex = '^ *(?<Interactive>\>)?(?<User>\S+) +(?:(?<Session>\S+) +)?(?<ID>\d+) +(?<SessionState>\S+) +(?<IdleTime>\S+) +(?<LogonTime>\S+ \S+)$'

Get-ADComputer -Properties OperatingSystem -Filter {Enabled -eq $True} -SearchBase "DC=MyDomain,DC=com" |
	Where-Object {Test-Connection $_.Name -Count 1 -Quiet} | ForEach-Object {
    $computerName = $_.Name

    while ((Get-Job -State Running | Measure-Object).Count -ge $threadLimit) {
        Start-Sleep -Seconds 5
    }
    $null = Start-Job -ArgumentList $computerName, $regex -ScriptBlock {
        param(
            $ComputerName,
            $Regex
        )

        $output = query user /server:$ComputerName 2>&1
        if ($null -ne $output -and $output[1] -is [System.Management.Automation.ErrorRecord]) {
            $_ | Select-Object Name, User, Interactive, Session, ID, SessionState, IdleTime, LogonTime,
                @{n='State';e={
                    'Failed ({0})' -f $output[1].Exception.Message.Trim()
                }}
        } elseif ($null -ne $output) {
            $output | Where-Object { $_ -match $regex } | ForEach-Object {
                [PSCustomObject]@{
                    Name         = $ComputerName
                    User         = $matches.User
                    Interactive  = [Boolean]$matches.Interactive
                    Session      = $matches.Session
                    ID           = $matches.ID
                    SessionState = $matches.SessionState
                    IdleTime     = $matches.IdleTime
                    LogonTime    = Get-Date $matches.LogonTime
                    State        = 'OK'
                }
            }
        }
    }
}

# Variable assignment to show different output options
$report = Get-Job | Wait-Job | Receive-Job | Select-Object * -ExcludeProperty RunspaceId
# Tidy up
Get-Job | Remove-Job

#----------------------
# Custom user filtering
#----------------------
#
#$report | Where-Object User -eq 'someone'
#$report | Where-Object { $_.User -eq 'someone' -and $_.SessionState -ne 'Active' }

# GridView
#$report | Out-GridView
# File
$report | Export-Csv -path C:\LoggedIn.csv -NoTypeInformation
Hope this helps.
/* IT Engineer */

Locked