invoke-command + CIM question

Welcome Forums General PowerShell Q&A invoke-command + CIM question

Viewing 6 reply threads
  • Author
    Posts
    • #225483
      Participant
      Topics: 4
      Replies: 12
      Points: 89
      Rank: Member

      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.

      • This topic was modified 4 weeks, 1 day ago by mitchvh05.
    • #225507
      Participant
      Topics: 3
      Replies: 342
      Points: 1,130
      Helping Hand
      Rank: Community Hero

      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

    • #225783
      Participant
      Topics: 4
      Replies: 12
      Points: 89
      Rank: Member

      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!

    • #225804
      Participant
      Topics: 3
      Replies: 342
      Points: 1,130
      Helping Hand
      Rank: Community Hero

      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?

    • #225849
      Participant
      Topics: 3
      Replies: 342
      Points: 1,130
      Helping Hand
      Rank: Community Hero

      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

    • #226197
      Participant
      Topics: 4
      Replies: 12
      Points: 89
      Rank: Member

      [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'}
              }
          }
      }
    • #226254
      Participant
      Topics: 3
      Replies: 342
      Points: 1,130
      Helping Hand
      Rank: Community Hero

      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
Viewing 6 reply threads
  • You must be logged in to reply to this topic.