Dividi il testo per colonne in PowerShell

Sono un novizio di PowerShell (Bash è la mia cosa normale) che sta attualmente cercando di ottenere l’output di qwinsta per mostrare chi è loggato come utente ‘rdpwd’ (rdesktop) in modo che possa controllare ogni nome utente con un elenco di nomi utente, e se non corrispondono, li disconnettono.

Attualmente sto lavorando a due problemi:

  1. Non riesco a dividere l’output di qwinsta da lasciare con solo il nome utente – Ho provato la funzione “split” ma fino ad ora ho riscontrato problemi di syntax o risultati strani; sembra che una sola lamencanvas sia che ‘\ s +’ corrisponde alla lettera S invece che agli spazi bianchi; altre volte sono riuscito a dividere la seconda colonna, ma viene visualizzato solo l’output della riga 1
  2. Anche se non ci sono ancora, ho la sensazione che avrò problemi anche con il secondo passo, ovvero il looping attraverso l’array di utenti non log-offable (che devono essere ottenuti da un gruppo di utenti locali)

Mi concentrerò sul problema 1 per ora!

Il testo che ho è:

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 

L’output che voglio è:

 user.name1 user.name2 user.name3 

(Con l’objective di creare un ciclo che dice, in breve, “foreach user in list, if not in localgroup, logoff user”.)

Finora, ho avuto fino a selezionare il testo con ‘rdpwd’, ma usando tutte le modalità di variazioni su “split”, non ho avuto più avanti di così.

Sono felice di condividere quello che ho già, ma purtroppo non credo che aiuterà nessuno!

Qualsiasi assistenza sarebbe più apprezzata. 🙂

Onestamente, vorrei cercare un modo migliore per farlo, ma è ansible fonderlo con alcune manipolazioni del testo e il cmdlet ConvertFrom-Csv :

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

Sostituire innanzitutto spazi iniziali o > personaggi senza nulla, quindi sostituire eventuali spazi bianchi con una virgola. Quindi è ansible redirect a ConvertFrom-Csv e lavorare con i dati come object.

MODIFICARE

In realtà, quanto sopra ha alcuni problemi, principalmente con \s+ perché se una colonna è vuota non viene riconosciuta correttamente come campo vuoto e il testo successivo viene promosso in modo errato nel campo corrente.

Il seguente è un parser completo per questo comando, e probabilmente funzionerebbe per qualsiasi tipo di output tabulato da un exe di 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 

Non posso dirlo con certezza, ma sembra che tu stia cercando di eseguire una divisione .split() utilizzando il metodo string .split() . Questo non funziona. Utilizzare l’operatore Powershell -split per eseguire una divisione -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 

La mia interpretazione del delimitatore basato sulla posizione. Tutte le altre risposte ti forniscono le informazioni che stai cercando, ma molto come Arco, stavo cercando una risposta basata su oggetti PowerShell. Ciò presuppone che $data vengano popolati con il testo delimitato dalla nuova riga, come si otterrebbe dal get-content potrebbe facilmente dividere l’output da qwinsta.exe ( $data = (qwinsta.exe) -split "`r`n" per esempio)

 $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 

Questo approccio è basato sulla posizione dei campi dell’intestazione. Nulla è hardcoded e si tratta di una compilazione personalizzata basata su tali indici e nomi di campi. Usando questi $headerIndexes ogni linea e posizioniamo i risultati, se presenti, nella rispettiva colonna. C’è una logica per garantire che non proviamo a prendere e parte della stringa che potrebbe non esistere e trattare l’ultimo campo speciale.

$results non conterranno il tuo testo come un object personalizzato. Ora puoi fare il filtraggio come faresti con qualsiasi altra raccolta di oggetti.

Uscita dal campione sopra

 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 

Ora mostriamo tutti i nomi utente in cui il type è rdpwd

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

Stampa i campi 4,5 e 6 nella seconda colonna.

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

Sembra che ci siano alcune risposte su questo ma eccone un altro.

È ansible estrarre la sottostringa da ogni riga in base alla posizione come questa.

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

Fallo esattamente nello stesso modo che dovresti se la tua shell fosse bash:

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

Caveat: Non ho idea di cosa sia “powershell” ma tu hai taggato la domanda con awk quindi presumo che “powershell” sia una specie di shell e chiamare awk da esso sia un’opzione.

Che ne dici di utilizzare i processi in esecuzione per cercare le istanze di Explorer per gli utenti che hanno effettuato l’accesso? (O qualche altro processo in cui sai che i tuoi utenti devono essere in esecuzione):

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

Fornirà tutti i nomi utente associati all’esecuzione dei processi di explorer.

[Modifica: mi è piaciuta l’idea di Matt di determinare dynamicmente i nomi delle colonne, quindi ho aggiornato la mia risposta a una soluzione più solida.]

Ecco un modo:

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

Converte l’output del comando in oggetti personalizzati che puoi filtrare, ordinare, ecc.

Ciò significa che è ansible eseguire il comando seguente per ottenere solo i nomi utente in cui la colonna TYPE è rdpwd :

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

Produzione:

 user.name1 user.name2 user.name3 

Mi piace la risposta di Matt per questo, ma ha problemi con gli spazi nelle intestazioni delle colonne (sono problematiche in generale, ma a volte non si può fare molto). Ecco una versione ottimizzata e funzionalizzata per aiutare. Si noti che probabilmente è ansible modificare il preproc da includere, ad esempio, tabulazioni o altri delimitatori, ma si basa ancora su indici per linea costanti.

 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 } 

Esempio:

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

Alcune delle risposte qui lodevolmente cercano di analizzare l’input in oggetti , che, tuttavia, è (a) uno sforzo non banale e (b) arriva a scapito delle prestazioni.

In alternativa, -split considerazione l’analisi del testo usando l’operatore -split di PowerShell , che nella sua forma unaria divide le righe in campi in spazi bianchi simili all’utilità standard di awk sulle piattaforms Unix:

Su Windows, se si installa per la prima volta una porta awk come Gawk per Windows , è ansible richiamare direttamente awk , come dimostrato nella risposta di Ed Morton . Su Unix (utilizzando PowerShell Core ), awk è disponibile per impostazione predefinita.
La soluzione sotto è simile a quella di Ed, tranne che non funzionerà altrettanto bene.

 qwinsta | % { if (($fields = -split $_)[4] -eq 'rdpwd') { $fields[1] } } 
  • -split $_ divide la riga di input a disposizione ( $_ ) in una matrice di campi per le serie di spazi bianchi, ignorando gli spazi bianchi iniziali e finali.

  • (...)[4] -eq 'rdpwd' il quinto campo (come al solito, gli indici sono a base 0 ) per il valore di interesse.

  • In caso di una corrispondenza, $fields[1] restituisce il secondo campo, il nome utente (presumibilmente non vuoto).

Ho scritto un cmdlet ConvertFrom-SourceTable riutilizzabile che è disponibile per il download nella Galleria PowerShell e il codice sorgente dal 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 ' 

È abbastanza flessibile e capace di leggere un sacco di formati di tabelle, compresa la lettura dell’output dei risultati. O anche se, ad esempio, la colonna ID è allineata a destra, il che significa che riguarderebbe gli interi piuttosto che le stringhe:

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

Per dettagli vedi: ConvertFrom-SourceTable -?

un modo semplice

ottenere l’elenco solo degli utenti attivi

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

cancella tutte le informazioni separatamente dagli utenti, con il comando -replace

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

elencherà quindi tutti gli utenti attivi.