Dividir texto por columnas en PowerShell

Soy un principiante de PowerShell (Bash es lo mío normalmente) que actualmente está tratando de obtener la salida de qwinsta para mostrar quién inició sesión como un usuario ‘rdpwd’ (rdesktop) para que pueda verificar cada nombre de usuario con una lista de nombres de usuario, y si no coinciden, apáguelos.

Actualmente estoy trabajando en dos problemas:

  1. No puedo dividir el resultado de qwinsta con solo el nombre de usuario. He intentado con la función “dividir” pero hasta ahora recibo problemas de syntax o resultados extraños; una queja parece ser que ‘\ s +’ coincide con la letra S en lugar del espacio en blanco; otras veces he logrado dividirme en la segunda columna, pero solo aparece la salida de la línea 1
  2. Aunque todavía no estoy allí, siento que también tendré problemas con el segundo paso, es decir, recorrer la matriz de usuarios no aptos para el registro (que se obtendrán de un grupo de usuarios local).

¡Me centraré en el problema 1 por ahora!

El texto que tengo es:

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 

El resultado que quiero es:

 user.name1 user.name2 user.name3 

(Con el objective de crear un ciclo que diga, en términos breves, “usuario foreach en la lista, si no es un grupo local, desconecte al usuario”).

Hasta ahora, he llegado a seleccionar texto con ‘rdpwd’, pero usando todo tipo de variaciones en “división”, no he avanzado mucho más que eso.

Me complace compartir lo que ya tengo, ¡pero no creo que ayude a nadie!

Cualquier asistencia será muy apreciada. 🙂

Honestamente, buscaría una forma mejor de hacerlo, pero puede usar una manipulación de texto y el cmdlet ConvertFrom-Csv :

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

En primer lugar, reemplace los espacios iniciales o > caracteres sin nada, luego reemplace los espacios en blanco con una coma. Luego puede canalizar a ConvertFrom-Csv y trabajar con los datos como un objeto.

EDITAR

En realidad, lo anterior tiene algunos problemas, principalmente con el \s+ porque si una columna está en blanco, no se reconoce correctamente como un campo en blanco, y el siguiente texto se promociona incorrectamente al campo actual.

El siguiente es un analizador completo para este comando, y probablemente funcione para cualquier tipo de salida tabulada de un exe de Windows nativo:

 $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 

No puedo asegurarlo, pero parece que estás tratando de hacer una división de .split() regulares usando el .split() cadena .split() . Eso no funciona. Utilice el operador de -split Powershell para hacer una división de -split regulares:

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

Mi toma del delimitador basado en posición. Todas las demás respuestas le brindan la información que busca, pero al igual que Arco, estaba buscando una respuesta basada en objetos PowerShell. Esto supone que $data se rellena con nueva línea de texto delimitado como lo que obtendría de get-content podría dividir fácilmente la salida de qwinsta.exe ( $data = (qwinsta.exe) -split "`r`n" por ejemplo)

 $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 

Este enfoque se basa en la posición de los campos de encabezado. Nada está codificado y todo es creación personalizada basada en esos índices y nombres de campo. Usando esos $headerIndexes cada línea y colocamos los resultados, si están presentes, en su columna respectiva. Existe una lógica para garantizar que no intentemos capturar y que parte de la cadena no exista y tratar el último campo como especial.

$results no contendría su texto como un psobject personalizado. Ahora puede filtrar como lo haría con cualquier otra colección de objetos.

Salida de la muestra anterior

 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 

Ahora mostramos todos los nombres de usuario donde el type es rdpwd

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

Imprimir el campo 4,5 y 6 en la segunda columna.

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

Parece que hay algunas respuestas sobre esto, pero aquí hay otra.

Puede extraer la subcadena de cada línea de acuerdo con la posición de esta manera.

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

Hazlo exactamente de la misma manera que si tu concha fuera bash:

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

Advertencia: no tengo idea de qué es “powershell”, pero tagste la pregunta con awk, así que supongo que “powershell” es una especie de shell y llamar a awk es una opción.

¿Qué le parece usar procesos en ejecución para buscar instancias de explorador para los usuarios que han iniciado sesión? (O algún otro proceso que sabe que sus usuarios están ejecutando):

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

Proporcionará todos los nombres de usuario asociados con la ejecución de los procesos del explorador.

[Editar: Me gustó la idea de Matt de determinar dinámicamente los nombres de columna, así que actualicé mi respuesta a una solución más sólida.]

Aquí hay una forma:

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

Esto convierte la salida del comando en objetos personalizados que puede filtrar, ordenar, etc.

Esto significa que puede ejecutar el siguiente comando para obtener solo los nombres de usuario donde la columna TYPE es rdpwd :

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

Salida:

 user.name1 user.name2 user.name3 

Me gusta la respuesta de Matt para esto, sin embargo tiene problemas con los espacios en los encabezados de las columnas (son problemáticos en general, pero a veces no se puede hacer mucho). Aquí hay una versión modificada y funcional para ayudar. Tenga en cuenta que probablemente podría ajustar el preproceso para incluir, por ejemplo, tabs u otros delimitadores, pero aún así se basa en que los índices por línea son constantes.

 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 } 

Ejemplo:

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

Algunas de las respuestas aquí encomiablemente intentan analizar la entrada en objetos , que, sin embargo, es (a) un esfuerzo no trivial y (b) viene a expensas del rendimiento.

Como alternativa, considere el análisis de texto utilizando el operador de -split de PowerShell , que en su forma -split divide las líneas en campos por espacios en blanco similares a la utilidad awk estándar en plataformas Unix:

En Windows, si instala primero un puerto awk como Gawk para Windows , puede invocar awk directamente, como se demostró en la respuesta de Ed Morton . En Unix (utilizando PowerShell Core ), awk está disponible de forma predeterminada.
La solución a continuación es similar a la de Ed, excepto que no funcionará tan bien.

 qwinsta | % { if (($fields = -split $_)[4] -eq 'rdpwd') { $fields[1] } } 
  • -split $_ divide la línea de entrada disponible ( $_ ) en una matriz de campos mediante la ejecución de espacios en blanco, ignorando los espacios en blanco iniciales y finales.

  • (...)[4] -eq 'rdpwd' prueba el 5to campo (como de costumbre, los índices están basados ​​en 0 ) para el valor de interés.

  • En caso de una coincidencia, $fields[1] luego genera el 2do campo, el nombre de usuario (se supone que no está vacío).

Escribí un cmdlet ConvertFrom-SourceTable reutilizable que está disponible para descargar en la Galería PowerShell y el código fuente del 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 ' 

Es bastante flexible y capaz de leer mucho formato de tabla, incluida la lectura de los resultados. O incluso si, por ejemplo, la columna ID está alineada a la derecha, lo que significa que se trataría de enteros en lugar de cadenas:

 $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 detalles, ver: ConvertFrom-SourceTable -?

una manera simple

obtener una lista de usuarios activos solamente

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

borra toda la información, aparte de los usuarios, con el comando -replace

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

a continuación, se enumerarán todos los usuarios activos.