在PowerShell中按列拆分文本

时间:2015-03-18 14:55:31

标签: regex powershell select awk

我是PowerShell新手(Bash通常是我的东西)谁正在尝试获取qwinsta输出以显示谁以“rdpwd”(rdesktop)用户身份登录,以便我可以根据用户名列表检查每个用户名,如果它们不匹配,请将其注销。

我目前正在解决两个问题:

  1. 我无法将qwinsta输出拆分为只留下用户名 - 我尝试了“拆分”功能,但到目前为止我遇到了语法问题或奇怪的结果;一个抱怨似乎是'\ s +'匹配字母S而不是空格;其他时候我设法拆分到第二列,但只显示第1行的输出
  2. 虽然我还没有,但我觉得第二步也会遇到问题,即循环遍历不可登录的用户数组(从本地用户组获取)< / LI>

    我现在专注于问题1!

    我得到的文字是:

    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
    

    我想要的输出是:

    user.name1
    user.name2
    user.name3
    

    (目的是创建一个循环,简单地说,“在列表中预告用户,如果不在本地组中,则注销用户”。)

    到目前为止,我已经选择了带有'rdpwd'的文字,但是对于“拆分”使用了各种各样的变化,我还没有比这更进一步。

    我很高兴分享我已经拥有的东西,但是我觉得它不会对任何人有所帮助!

    非常感谢任何帮助。 :)

12 个答案:

答案 0 :(得分:4)

老实说,我会查找更好的方法来执行此操作,但您可以通过一些文本操作和ConvertFrom-Csv cmdlet来捏造它:

$(qwinsta.exe) -replace "^[\s>]" , "" -replace "\s+" , "," | ConvertFrom-Csv | select username

首先用任何内容替换任何前导空格或>字符,然后用逗号替换任何空格。然后,您可以管道到ConvertFrom-Csv并将数据作为对象使用。

修改

实际上,上面有一些问题,主要是\s+,因为如果列为空,则无法将其正确识别为空白字段,并且下一个文本被错误地提升为当前字段。

以下是此命令的完整解析器,可能适用于本机Windows exe的任何类型的列表输出:

$o = @()
$op = $(qwinsta.exe)

$ma = $op[0] | Select-String "(?:[\s](\w+))" -AllMatches
$ErrorActionPreference = "Stop"

for($j=1; $j -lt $op.length; $j++) {
    $i = 0
    $obj = new-object pscustomobject
    while ($i -lt $ma.matches.count) { 
      $prop = $ma.matches[$i].groups[1].value; 
      $substrStart = $ma.matches[$i].index 
      $substrLen = $ma.matches[$i+1].index - $substrStart
      try {
        $obj | Add-Member $prop -notepropertyvalue $op[$j].substring($substrStart,$substrLen).trim() 
      }
      catch [ArgumentOutOfRangeException] {
        $substrLen = $op[$j].length - $substrStart 
        if($substrLen -gt 0) {
          $obj | Add-Member $prop -notepropertyvalue $op[$j].substring($substrStart,$substrLen).trim()
        }
        else {
          $obj | Add-Member $prop -notepropertyvalue ""
        }
      }
      $i++
    }
    $o += ,$obj
}

$o | ? { $_.type -eq 'rdpwd'} | select username

USERNAME
--------
user.name1
user.name2
user.name3

答案 1 :(得分:2)

无法确定,但听起来您正在尝试使用字符串.split()方法进行正则表达式拆分。这不起作用。使用Powershell -split运算符进行正则表达式分割:

(@'
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  Liste
'@).split("`n") |
foreach {$_.trim()} | sv x


$x -match 'rdpwd' |
foreach { ($_ -split '\s+')[1] }

user.name1
user.name2
user.name3

答案 2 :(得分:1)

我对基于位置的分隔符的看法。所有其他答案都可以获得您正在寻找的信息,但很像Arco我正在寻找基于PowerShell对象的答案。这假设$data填充了新的行已删除文本,就像您从get-content获得的那样可以轻松地从qwinsta.exe(例如$data = (qwinsta.exe) -split "`r`n")分割输出

$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

此方法基于标题字段的位置。没有任何硬编码,它是基于这些索引和字段名称的所有自定义构建。使用那些$headerIndexes我们将每一行划分出来并将结果(如果存在)放入其各自的列中。有一些逻辑可以确保我们不会尝试抓取可能不存在的字符串,并将最后一个字段视为特殊字符。

$results不会将您的文字包含为自定义psobject。现在,您可以像处理任何其他对象集一样进行过滤。

以上示例的输出

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             

现在我们显示type是rdpwd

的所有用户名
$results | Where-Object{$_.type -eq "rdpwd"} | Select-Object -ExpandProperty username

答案 3 :(得分:1)

在第二栏中打印第4,5和6栏。

awk 'NR>3&&NR<7{print $2}' file

    user.name1
    user.name2
    user.name3

答案 4 :(得分:0)

看起来有一些答案,但这是另一个答案。

你可以根据这样的位置从每一行中提取子串。

$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)

答案 5 :(得分:0)

如果你的shell是bash,那就完全一样了:

$ awk '$NF=="rdpwd"{print $2}' file 
user.name1
user.name2
user.name3
警告:我不知道“powershell”是什么,但你用awk标记了这个问题所以我认为“powershell”是某种shell并且从中调用awk是一种选择。

答案 6 :(得分:0)

[编辑:我喜欢Matt关于动态确定列名称的想法,所以我更新了我对更强大解决方案的回答。]

以这种方式:

# 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
}

这会将命令的输出转换为可以过滤,排序等的自定义对象。

这意味着您可以运行以下命令来仅获取TYPE列为rdpwd的用户名:

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

输出:

user.name1
user.name2
user.name3

答案 7 :(得分:0)

如何使用正在运行的进程为登录用户查找资源管理器实例? (或者您知道您的用户正在运行的其他一些过程):

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

将提供与运行资源管理器进程相关联的所有用户名。

答案 8 :(得分:0)

我喜欢Matt's answer,但它在列标题中存在空格问题(一般来说它们都有问题,但有时候你做不了多少)。这是一个经过调整的功能化版本,可以提供帮助。请注意,您可以调整preproc以包括例如制表符或其他分隔符,但仍然依赖于每行索引不变。

function Convert-TextColumnsToObject([String]$data)
{
    $splitLinesOn=[Environment]::NewLine
    $columnPreproc="\s{2,}"
    $headerString = $data.Split($splitLinesOn) | select -f 1
    #Preprocess to handle headings with spaces
    $headerElements = ($headerString -replace "$columnPreproc", "|") -split "\|" | Where-Object{$_}
    $headerIndexes = $headerElements | ForEach-Object{$headerString.IndexOf($_)}
    $results = $data.Split($splitLinesOn) | 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
    }

    return $results
} 

示例:

$data= @"
    DRIVER              VOLUME NAME
    local               004e9c5f2ecf96345297965d3f98e24f7a6a69f5c848096e81f3d5ba4cb60f1e
    local               081211bd5d09c23f8ed60fe63386291a0cf452261b8be86fc154b431280c0c11
    local               112be82400a10456da2e721a07389f21b4e88744f64d9a1bd8ff2379f54a0d28
    "@ 

$obj=Convert-TextColumnsToObject $data
$obj | ?{ $_."VOLUME NAME" -match "112be" }

答案 9 :(得分:0)

这里的一些答案值得称道地尝试将输入解析为对象,然而,这是(a)非常重要的努力和(b)以牺牲性能为代价。

作为替代方案,考虑使用PowerShell的-split运算符进行文本解析,以其一元形式将行分隔为类似于标准{{{ Unix平台上的1}}实用程序

在Windows上,如果您首先安装awk端口awk,则可以直接调用awk,如Gawk for Windows所示。在Unix上(使用PowerShell Core ),默认情况下可以使用awk 下面的解决方案类似于Ed,但它不会表现良好。

qwinsta | % { if (($fields = -split $_)[4] -eq 'rdpwd') { $fields[1] } }
  • -split $_通过空格运行将输入行($_)拆分为字段数组,忽略前导和尾随空格。

  • (...)[4] -eq 'rdpwd'测试第五个字段(通常情况下,索引为0 - 基于)感兴趣的值。

  • 如果匹配,$fields[1]则输出第二个字段,(假设为非空)用户名。

答案 10 :(得分:0)

我写了一个可重用的ConvertFrom-SourceTable cmdlet,可以从PowerShell Gallery下载,也可以从GitHub iRon7/ConvertFrom-SourceTable存储库下载。

$Object = ConvertFrom-SourceTable '
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
'

它非常灵活,能够读取很多表格格式,包括读取结果的输出。或者即使例如ID列右对齐意味着它将涉及整数而不是字符串:

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

详见:ConvertFrom-SourceTable -?

答案 11 :(得分:-1)

一种简单的方法

  

仅获取活跃用户列表

$logonusers = qwinsta /server:ts33 | Out-String -Stream | Select-String "Active"
  

使用-replace命令

清除除用户之外的所有信息
$logonusers = $logonusers -replace("rdp-tcp") -replace("Active") -
replace("rdpwd") -replace("#") -replace '\s+', ' ' -replace '[0-9]',' '

$logonusers
然后

将列出所有活跃用户。