Dividir texto por colunas no PowerShell

Eu sou um novato PowerShell (Bash é coisa minha normalmente) que atualmente está tentando obter qwinsta saída para mostrar quem está logado como um usuário ‘rdpwd’ (rdesktop) para que eu possa verificar cada nome de usuário contra uma lista de nomes de usuários e se eles não correspondem, faça logoff.

Atualmente estou trabalhando em dois problemas:

  1. Não consigo dividir a saída do qwinsta com apenas o nome de usuário – tentei a function “split”, mas até agora estou obtendo problemas de syntax ou resultados estranhos; uma queixa parece ser que ‘\ s +’ corresponde à letra S em vez de espaço em branco; outras vezes eu consegui dividir a segunda coluna, mas apenas a saída da linha 1 aparece
  2. Embora eu ainda não esteja lá, sinto que também terei problemas com o segundo passo, ou seja, percorrendo a matriz de usuários não log-off (que devem ser obtidos de um grupo de usuários local).

Vou me concentrar no problema 1 por enquanto!

O texto que eu tenho é:

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 

A saída que quero é:

 user.name1 user.name2 user.name3 

(Com o objective de criar um loop que diz, em termos breves, “foreach usuário em lista, se não em localgroup, logoff usuário”.)

Até agora, cheguei a selecionar texto com ‘rdpwd’, mas usando todos os tipos de variações em “split”, eu não fui mais avançado do que isso.

Estou feliz em compartilhar o que eu já tenho, mas infelizmente eu não acho que vai ajudar ninguém!

Qualquer ajuda seria mais apreciada. 🙂

Honestamente, eu procuraria uma maneira melhor de fazer isso, mas você pode falsificá-lo com alguma manipulação de texto e o cmdlet ConvertFrom-Csv :

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

Em primeiro lugar, substitua os espaços iniciais ou > caracteres por nada e substitua os espaços em branco por vírgula. Em seguida, você pode canalizar para ConvertFrom-Csv e trabalhar com os dados como um object.

EDITAR

Na verdade, o problema acima tem alguns problemas, principalmente com o \s+ porque, se uma coluna estiver em branco, ela não será reconhecida corretamente como um campo em branco e o próximo texto será promovido incorretamente para o campo atual.

O abaixo é um analisador completo para este comando, e provavelmente funcionaria para qualquer tipo de saída tabulada de um exe nativo do windows:

 $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 

Não posso dizer com certeza, mas parece que você está tentando fazer uma divisão regex usando o método .split() da string. Isso não funciona. Use o operador Powershell -split para fazer uma divisão de regex:

 (@' 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 

Minha opinião sobre o delimitador baseado em posição. Todas as outras respostas dão a você a informação que você está procurando, mas muito parecido com o Arco Eu estava procurando por uma resposta baseada em object do PowerShell. Isso pressupõe que $data seja preenchido com nova linha delimitada por texto, como você obteria de get-content poderia facilmente dividir a saída de qwinsta.exe ( $data = (qwinsta.exe) -split "`r`n" por exemplo)

 $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 

Essa abordagem é baseada na posição dos campos de header. Nada é codificado e tudo é feito com base nesses índices e nomes de campo. Usando esses $headerIndexes , $headerIndexes cada linha e colocamos os resultados, se presentes, em sua respectiva coluna. Há uma lógica para garantir que não tentemos pegar e parte da string que pode não existir e tratar o último campo como especial.

$results não contém seu texto como um psobject customizado. Agora você pode filtrar como faria com qualquer outra coleção de objects.

Saída da amostra acima

 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 

Agora mostramos todos os nomes de usuários em que o type é rdpwd

 $results | Where-Object{$_.type -eq "rdpwd"} | Select-Object -ExpandProperty username 

Campo de impressão 4,5 e 6 na segunda coluna.

 awk 'NR>3&&NR<7{print $2}' file user.name1 user.name2 user.name3 

Parece que há algumas respostas sobre isso, mas aqui está outra.

Você pode extrair a substring de cada linha de acordo com a posição como esta.

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

Faça exatamente da mesma maneira que você deveria se o seu shell fosse bash:

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

Advertência: Eu não tenho idéia do que é “powershell”, mas você marcou a questão com o awk, então eu assumo que “powershell” é algum tipo de shell e chamar awk é uma opção.

Que tal usar processos em execução para procurar instâncias do explorador para os usuários logados? (Ou algum outro processo que você saiba que seus usuários estão executando):

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

Fornecerá todos os nomes de usuários associados à execução de processos do explorador.

[Edit: Eu gostei da idéia do Matt de determinar dinamicamente os nomes das colunas, então atualizei minha resposta para uma solução mais robusta.]

Aqui está um jeito:

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

Isso converte a saída do comando em objects personalizados que você pode filtrar, classificar etc.

Isso significa que você pode executar o seguinte comando para obter apenas os nomes de usuários em que a coluna TYPE é rdpwd :

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

Saída:

 user.name1 user.name2 user.name3 

Eu gosto da resposta de Matt para isso, no entanto, tem problemas com espaços em títulos de coluna (eles são problemáticos em geral, mas às vezes você não pode fazer muito). Aqui está uma versão funcional e otimizada para ajudar. Note que você provavelmente poderia ajustar o preproc para include, por exemplo, guias ou outros delimitadores, mas ainda assim se baseia em índices constantes por linha.

 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 } 

Exemplo:

 $data= @" DRIVER VOLUME NAME local 004e9c5f2ecf96345297965d3f98e24f7a6a69f5c848096e81f3d5ba4cb60f1e local 081211bd5d09c23f8ed60fe63386291a0cf452261b8be86fc154b431280c0c11 local 112be82400a10456da2e721a07389f21b4e88744f64d9a1bd8ff2379f54a0d28 "@ $obj=Convert-TextColumnsToObject $data $obj | ?{ $_."VOLUME NAME" -match "112be" } 

Algumas das respostas aqui, de maneira recomendável, tentam analisar a input em objects , o que, no entanto, é (a) um esforço não trivial e (b) vem à custa do desempenho.

Como alternativa, considere a análise de texto usando o operador -split do PowerShell , que em sua forma unária divide linhas em campos por espaços em branco semelhantes ao utilitário awk padrão em plataformas Unix:

No Windows, se você instala primeiro uma porta awk como o Gawk para Windows , você pode invocar o awk diretamente, como demonstrado na resposta de Ed Morton . No Unix (usando o PowerShell Core ), o awk está disponível por padrão.
A solução abaixo é semelhante à do Ed, exceto pelo fato de que ela não funcionará tão bem.

 qwinsta | % { if (($fields = -split $_)[4] -eq 'rdpwd') { $fields[1] } } 
  • -split $_ divide a linha de input em questão ( $_ ) em uma matriz de campos por execuções de espaço em branco, ignorando espaços em branco à esquerda e à direita.

  • (...)[4] -eq 'rdpwd' testa o quinto campo (como de costume, os índices são baseados em 0 ) para o valor de interesse.

  • No caso de uma correspondência, $fields[1] gera o segundo campo, o (supostamente não vazio) nome de usuário.

Escrevi um cmdlet ConvertFrom-SourceTable reutilizável que está disponível para download na Galeria do PowerShell e no código-fonte do iRon7/ConvertFrom-SourceTable 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 ' 

É bastante flexível e capaz de ler um monte de formato de tabela, incluindo a leitura da saída dos resultados. Ou mesmo se, por exemplo, a coluna de ID estiver alinhada à direita, o que significaria números inteiros em vez de strings:

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

Para detalhes, consulte: ConvertFrom-SourceTable -?

uma maneira simples

obter lista apenas de usuários ativos

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

limpa todas as informações separadas dos usuários, com o comando -replace

 $logonusers = $logonusers -replace("rdp-tcp") -replace("Active") - replace("rdpwd") -replace("#") -replace '\s+', ' ' -replace '[0-9]',' ' $logonusers 

então listará todos os usuários ativos.