Split text by columns in PowerShell

后端 未结 12 2086
猫巷女王i
猫巷女王i 2020-11-28 15:37

I\'m a PowerShell novice (Bash is my thing normally) who\'s currently trying to obtain qwinsta output to show who is logged in as an \'rdpwd\' (rdesktop) user so that I can

相关标签:
12条回答
  • 2020-11-28 16:08

    Do it exactly the same way you should if your shell was bash:

    $ awk '$NF=="rdpwd"{print $2}' file 
    user.name1
    user.name2
    user.name3
    

    Caveat: I've no idea what "powershell" is but you tagged the question with awk so I assume "powershell" is some kind of shell and calling awk from it is an option.

    0 讨论(0)
  • 2020-11-28 16:12

    My take of the position based delimiter. All the other answers get you the information you are looking for but much like Arco I was looking for a PowerShell object based answer. This assumes $data is populated with new line delimeted text like you would get from get-content could easily split the output from qwinsta.exe ($data = (qwinsta.exe) -split "`r`n" for example)

    $headerString = $data[0]
    $headerElements = $headerString -split "\s+" | Where-Object{$_}
    $headerIndexes = $headerElements | ForEach-Object{$headerString.IndexOf($_)}
    
    $results = $data | Select-Object -Skip 1  | ForEach-Object{
        $props = @{}
        $line = $_
        For($indexStep = 0; $indexStep -le $headerIndexes.Count - 1; $indexStep++){
            $value = $null            # Assume a null value 
            $valueLength = $headerIndexes[$indexStep + 1] - $headerIndexes[$indexStep]
            $valueStart = $headerIndexes[$indexStep]
            If(($valueLength -gt 0) -and (($valueStart + $valueLength) -lt $line.Length)){
                $value = ($line.Substring($valueStart,$valueLength)).Trim()
            } ElseIf ($valueStart -lt $line.Length){
                $value = ($line.Substring($valueStart)).Trim()
            }
            $props.($headerElements[$indexStep]) = $value    
        }
        [pscustomobject]$props
    } 
    
    $results | Select-Object sessionname,username,id,state,type,device | Format-Table -auto
    

    This approach is based on the position of the header fields. Nothing is hardcoded and it is all custom build based on those indexes and field names. Using those $headerIndexes we carve up every line and place the results, if present, into its respective column. There is logic to ensure that we don't try and grab and part of the string that might not exist and treat the last field special.

    $results would not contain your text as a custom psobject. Now you can do filtering like you would any other object collection.

    Output from above sample

    SESSIONNAME USERNAME   ID    STATE  TYPE  DEVICE
    ----------- --------   --    -----  ----  ------
    services               0     Disc               
    console                1     Conn               
    rdp-tcp#0   user.name1 2     Active rdpwd       
    rdp-tcp#1   user.name2 3     Active rdpwd       
    rdp-tcp#1   user.name3 4     Active rdpwd       
    rdp-tcp                65536 Listen             
    

    Now we show all usernames where the type is rdpwd

    $results | Where-Object{$_.type -eq "rdpwd"} | Select-Object -ExpandProperty username
    
    0 讨论(0)
  • 2020-11-28 16:12

    Some of the answers here commendably try to parse the input into objects, which, however, is (a) a nontrivial effort and (b) comes at the expense of performance.

    As an alternative, consider text parsing using PowerShell's -split operator, which in its unary form splits lines into fields by whitespace similar to the standard awk utility on Unix platforms:

    On Windows, if you first install an awk port such as Gawk for Windows, you could invoke awk directly, as demonstrated in Ed Morton's answer. On Unix (using PowerShell Core), awk is available by default.
    The solution below is similar to Ed's, except that it won't perform as well.

    qwinsta | % { if (($fields = -split $_)[4] -eq 'rdpwd') { $fields[1] } }
    
    • -split $_ splits the input line at hand ($_) into an array of fields by runs of whitespace, ignoring leading and trailing whitespace.

    • (...)[4] -eq 'rdpwd' tests the 5th field (as usual, indices are 0-based) for the value of interest.

    • In case of a match, $fields[1] then outputs the 2nd field, the (assumed to be nonempty) username.

    0 讨论(0)
  • 2020-11-28 16:12

    [Edit: I liked Matt's idea of dynamically determining the column names, so I updated my answer to a more robust solution.]

    Here's one way:

    # Get-SessionData.ps1
    $sessionData = qwinsta
    $headerRow = $sessionData | select-object -first 1
    # Get column names
    $colNames = $headerRow.Split(' ',[StringSplitOptions]::RemoveEmptyEntries)
    # First column position is zero
    $colPositions = @(0)
    # Get remainder of column positions
    $colPositions += $colNames | select-object -skip 1 | foreach-object {
      $headerRow.IndexOf($_)
    }
    $sessionData | select-object -skip 1 | foreach-object {
      # Create output object
      $output = new-object PSCustomObject
      # Create and populate properties for all except last column
      for ( $i = 0; $i -lt $colNames.Count - 1; $i++ ) {
        $output | add-member NoteProperty $colNames[$i] ($_[$($colPositions[$i])..$($colPositions[$i + 1] - 1)] -join "").Trim()
      }
      # Create property for last column
      $output | add-member NoteProperty $colNames[$colNames.Count - 1] ""
      # Remainder of text on line, if any, is last property
      if ( ($_.Length - 1) -gt ($colPositions[$colPositions.Count - 1]) ) {
        $output.$($colNames[$colNames.Count - 1]) = $_.Substring($colPositions[$colPositions.Count - 1]).Trim()
      }
      $output
    }
    

    This converts the command's output into custom objects that you can filter, sort, etc.

    This means you could run the following command to get only the usernames where the TYPE column is rdpwd:

    Get-SessionData | where-object { $_.TYPE -eq "rdpwd" } |
      select-object -expandproperty USERNAME
    

    Output:

    user.name1
    user.name2
    user.name3
    
    0 讨论(0)
  • 2020-11-28 16:14

    How about using running processes to look for explorer instances for the logged-in users? (Or some other process you know your users to be running):

    Get-WmiObject -ComputerName "Machine" -Class win32_process | Where-Object {$_.Name -match "explorer"} | ForEach-Object {($_.GetOwner()).User}
    

    Will provide all usernames associated with running explorer processes.

    0 讨论(0)
  • 2020-11-28 16:15

    Looks like there are a few answers on this but here's another one.

    You could extract the substring from each line according to the position like this.

    $Sessions=qwinsta.exe
    $SessionCount=$Sessions.count
    [int]$x=1
    do
        {$x++
         if(($Sessions[$x]) -ne $null){$Sessions[$x].subString(19,21).Trim()}
        }until($x -eq $SessionCount)
    
    0 讨论(0)
提交回复
热议问题