invoke-command + CIM question

If I run the below, I get the output I expect:

$LogonSession = Get-CimInstance -ClassName Win32_LogonSession | where-object logontype -in 2,10,11,12
foreach ($ls in $LogonSession) {
    $lu = Get-CimAssociatedInstance -InputObject $ls -ResultClassName Win32_UserAccount
    if ($LU) {
        New-Object -TypeName PSObject -Property @{
            LogonSession = $LS
            LogonUser = $LU
            ComputerName = $ENV:ComputerName
        }
    }
}
If however, I wrap that in an Invoke-Command, the Get-CimAssociatedInstance returns nothing (Meaning, if the New-Object wasn't wrapped in an if ($LU){...} the object it returned would have ComputerName and LogonSession populated, but LogonUser would be null). Example:
$Objects = invoke-command -Session $Sessions -ScriptBlock {
    $LogonSession = Get-CimInstance -ClassName Win32_LogonSession | where-object logontype -in 2,10,11,12
    foreach ($ls in $LogonSession) {
        $lu = Get-CimAssociatedInstance -InputObject $ls -ResultClassName Win32_UserAccount
        if ($LU) {
           New-Object -TypeName PSObject -Property @{
                LogonSession = $LS
                LogonUser = $LU
                ComputerName = $ENV:ComputerName
           }
       }
    }
}
I cannot reason my way through why this is failing... so even if there is a way to accomplish this task another way, I'd like to understand why this way doesn't work, in case I hit something like this again in the future.

Hi Mitch,

The reason this is happening is the famous double hop issue. Basically you can use credssp, scheduled task, etc. Check the following links, the first has a nice walk through of automating allowing/configuring credssp. You can confirm this is your issue at least. It’s not allowed for good reasons, you’ll have to consider and decide.

https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/understanding-and-avoiding-double-hop

https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/ps-remoting-second-hop?view=powershell-7

Hmm… so the Get-CimInstance doesn’t bump into that restriction, but the Get-CimAssociatedInstance does? Under the hood, is Get-CimAssociatedInstance trying to create a new cim session? Or am I just missing something way simpler? Obviously, I don’t expect you to know, but if you do - that’d be awesome to find out. Thanks regardless!

It’s not the command per se. It’s where the information is you’re trying to retrieve. If it’s locally stored on the remote machine, you’ll have access to it. But win32_useraccount in a domain environment comes from the DC. If your remote target is a DC, it should work. If it’s not, double hop issue. Win32_useraccount will still list local accounts over remoting but not the domain accounts without providing additional credentials. See this thread for more info.

https://powershell.org/forums/topic/unable-to-run-a-powershell-script-on-remote-server-with-additional-parameters/

Did you confirm it worked when using CredSSP?

This is an interesting article too. Sucks it’s “archived” like so many other good ones.

https://docs.microsoft.com/en-us/archive/blogs/ashleymcglone/powershell-remoting-kerberos-double-hop-solved-securely

[Beware: Wall of Text]

For starters - thanks for helping me realize the problem. No idea how long it would have taken for me to realize Win32_UserAccount itself needed to access the DC.

I skipped trying the CredSSP; since I work for an MSP - most of our clients won’t have that setup, and I’d like this to run without making any configuration changes. I just ended up doing the Win32_UserAccount query from the local machine, then adding fields to the information I pulled from remote. It’s a bit slow, but I can work on that. At least right now, I have the beginnings of a sort-of Super “query user” which lets me poll everything in an environment. Output from my custom class looks like:

StartTime              : 5/1/2020 8:39:13 AM
LogonType              : Remote Interactive
Name                   : <MyUserName>
Domain                 : <NetBiosDomainName>
AccountType            : UF_NORMAL_ACCOUNT
AccountTypeDescription : Default account type that represents a typical user.
AuthenticationPackage  : Kerberos
Lockout                : False
LogonSession           : Win32_LogonSession (LogonId = "5165456")
LogonUser              : Win32_UserAccount: NetBiosDomainName\UserName (Name = "UserName", Domain = "NetBiosDomainName")
ComputerName           : <MyComputerName>

Currently usable, but in my calling function, I’m grabbing Processes, but not using them in my class, which I’ll implement later. If you end up ever having a use for something like this, here’s the code I have so far. Second code block is my custom class.

Function Get-SMAuthenticatedUser {
    Param(
        [string[]]$ComputerName='localhost'
    )
    for ($i=0; $i -lt $ComputerName.Count; $i++) {
        if ($ComputerName[$i] -like $env:COMPUTERNAME) { $ComputerName[$i] = 'localhost' }
    }
    $Objects = invoke-command -computername $ComputerName -script {
        $loggedOnUser = Get-CimInstance -query 'select * from Win32_LoggedOnUser' 
        $objs = foreach ($lu in $loggedonuser) {
            $inst = get-ciminstance -ClassName win32_logonsession -Filter "logonid = '$($lu.dependent.logonid)'" | where-object logontype -in 10,11,12
            if ($inst) {
                $Procs = Get-CimAssociatedInstance -InputObject $inst -ResultClassName win32_Process
                new-object -TypeName PSObject -Property @{
                    LoggedOnUser = $lu
                    LogonSession = $inst
                    Process = $Procs
                    ComputerName = $env:COMPUTERNAME
                }
            }
        }
        write-output $Objs
    }

    $users = Get-CimInstance -class Win32_UserAccount
    $New = foreach ($o in $objects) {
        $user = $users | where-object name -like $o.LoggedOnUser.Antecedent.name
        [SM_LogonSession]::New($o.LogonSession,$User,$o.ComputerName)
    }
    write-output $New
}

 

Class SM_LogonSession {
    [datetime]$StartTime
    [string]$LogonType
    [string]$Name
    [string]$Domain
    [string]$AccountType
    [string]$AccountTypeDescription
    [string]$AuthenticationPackage
    [boolean]$Lockout
    [Microsoft.Management.Infrastructure.CimInstance]$LogonSession
    [Microsoft.Management.Infrastructure.CimInstance]$LogonUser
    [string]$ComputerName

    SM_LogonSession([Microsoft.Management.Infrastructure.CimInstance]$LS, [Microsoft.Management.Infrastructure.CimInstance]$LU, [string]$ComputerName) {
        if (($LS | get-member).typename -contains "Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_LogonSession") {
            if (($LU | get-member).typename -contains "Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_UserAccount") {
                $This.LogonSession = $LS
                $This.LogonUser = $LU
                $This.ComputerName = $ComputerName
                $This.Build_SMLogonSession()
            }
        }
    }

    Build_SMLogonSession() {
        $This.SetLogonType()
        $This.SetAccountType()
        $This.Build_ObjectProperties()
    }

    Build_ObjectProperties() {
        $This.AuthenticationPackage = $This.LogonSession.AuthenticationPackage
        $This.StartTime = $This.LogonSession.StartTime
        $This.Name = $this.LogonUser.Name
        $This.Domain = $This.LogonUser.Domain
        $This.Lockout = $This.LogonUser.Lockout
        $This.SetAccountType()
    }

    SetAccountType() {
        switch ($This.LogonUser.AccountType) {
            256 { $this.AccountType = 'UF_TEMP_DUPLICATE_ACCOUNT'; $This.AccountTypeDescription='Local user account for users who have a primary account in another domain. This account provides user access to this domain only—not to any domain that trusts this domain.' }
            512 { $this.AccountType = 'UF_NORMAL_ACCOUNT'; $This.AccountTypeDescription = 'Default account type that represents a typical user.' }
            2048 { $this.AccountType = 'UF_INTERDOMAIN_TRUST_ACCOUNT'; $This.AccountTypeDescription = 'Account for a system domain that trusts other domains.' }
            4096 { $this.AccountType = 'UF_WORKSTATION_TRUST_ACCOUNT'; $This.AccountTypeDescription = 'Computer account for a computer system running Windows that is a member of this domain.' }
            8192 { $this.AccountType = 'UF_SERVER_TRUST_ACCOUNT'; $This.AccountTypeDescription = 'Account for a system backup domain controller that is a member of this domain.' }
        }
    }

    SetLogonType() {
        switch ($this.LogonSession.LogonType) {
            0 {$this.LogonType = ''}
            2 {$this.LogonType = 'Interactive'}
            3 {$this.LogonType = 'Network'}
            4 {$this.LogonType = 'Batch'}
            5 {$this.LogonType = 'Service'}
            6 {$this.LogonType = 'Proxy'}
            7 {$this.LogonType = 'Unlock'}
            8 {$this.LogonType = 'NetworkCleartext'}
            9 {$this.LogonType = 'NewCredentials'}
            10 {$this.LogonType = 'Remote Interactive'}
            11 {$this.LogonType = 'cachedInteractive'}
            12 {$this.LogonType = 'cachedRemoteInteractive'}
            13 {$this.LogonType = 'CachedUnlock'}
            default {$this.LogonType = 'Unknown'}
        }
    }
}

Fantastic, thanks for sharing! If you don’t need to interact or get output from the remote script, and just need it to run. You could always use something like this. It wouldn’t be a second hop. I only list the credential part for completeness, I’m certain you can build your own.

$server = 'remoteserver'

$username = Read-Host -Prompt "Enter username"
$securePassword = Read-Host -Prompt "Enter password for $username" -AsSecureString
$credential = [System.Management.Automation.PSCredential]::new($username,$securePassword)

$params = @{
    Credential   = $credential
    ComputerName = $server
    Class        = 'win32_process'
    name         = 'create'
    ArgumentList = "powershell.exe -nop -ex bypass -file c:\scripts\somescript.ps1"
}
Invoke-WmiMethod @params