Easier way to parse 'query user' in PowerShell (or quser) - powershell
I currently have the following query in PowerShell:
query user /server:$server
Which returns output:
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
svc_chthost 2 Disc 1:05 8/16/2016 12:01 PM
myusername rdp-tcp 3 Active . 8/29/2016 11:29 AM
Currently, I'm using #(query user /server:$server).Count - 1 as a value to represent the number of users logged on (it's not pretty, I know). However now I would like to obtain information such as USERNAME, ID, and LOGON TIME to use in other parts of my script.
My question is surrounding an easier way to parse the information above, or maybe a better solution to my problem all together: Counting and gathering information related to logged on users.
I've found other solutions that seem to work better, but I'm sure there's got to be a simpler way to accomplish this task:
$ComputerName | Foreach-object {
$Computer = $_
try
{
$processinfo = #(Get-WmiObject -class win32_process -ComputerName $Computer -EA "Stop")
if ($processinfo)
{
$processinfo | Foreach-Object {$_.GetOwner().User} |
Where-Object {$_ -ne "NETWORK SERVICE" -and $_ -ne "LOCAL SERVICE" -and $_ -ne "SYSTEM"} |
Sort-Object -Unique |
ForEach-Object { New-Object psobject -Property #{Computer=$Computer;LoggedOn=$_} } |
Select-Object Computer,LoggedOn
}#If
}
catch
{
}
Old question, but it seems a workable solution:
(query user) -split "\n" -replace '\s\s+', ';' | convertfrom-csv -Delimiter ';'
This chunks the output into lines, as the answer above does, but then replaces more than one white space character (\s\s+) with a semi-colon, and then converts that output from csv using the semi-colon as a delimiter.
The reason for more than one white space is that the column headers have spaces in them (idle time, logon time), so with just one space it would try to interpret that as multiple columns. From the output of the command, it looks as if they always preserve at least 2 spaces between items anyway, and the logon time column also has spaces in the field.
Awesome references in the comments, and still open to more answers for this question as it should have an easier solution!
foreach ($s in $servers) #For Each Server
{
foreach($ServerLine in #(query user /server:$s) -split "\n") #Each Server Line
{
#USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
$Parsed_Server = $ServerLine -split '\s+'
$Parsed_Server[1] #USERNAME
$Parsed_Server[2] #SESSIONNAME
$Parsed_Server[3] #ID
$Parsed_Server[4] #STATE
$Parsed_Server[5] #IDLE TIME
$Parsed_Server[6] #LOGON TIME
}
}
This solution solves the problem for now, kind of sloppy.
For more in-depth solutions with more functionalities, check the comments on the original question :)
Function Get-QueryUser(){
Param([switch]$Json) # ALLOWS YOU TO RETURN A JSON OBJECT
$HT = #()
$Lines = #(query user).foreach({$(($_) -replace('\s{2,}',','))}) # REPLACES ALL OCCURENCES OF 2 OR MORE SPACES IN A ROW WITH A SINGLE COMMA
$header=$($Lines[0].split(',').trim()) # EXTRACTS THE FIRST ROW FOR ITS HEADER LINE
for($i=1;$i -lt $($Lines.Count);$i++){ # NOTE $i=1 TO SKIP THE HEADER LINE
$Res = "" | Select-Object $header # CREATES AN EMPTY PSCUSTOMOBJECT WITH PRE DEFINED FIELDS
$Line = $($Lines[$i].split(',')).foreach({ $_.trim().trim('>') }) # SPLITS AND THEN TRIMS ANOMALIES
if($Line.count -eq 5) { $Line = #($Line[0],"$($null)",$Line[1],$Line[2],$Line[3],$Line[4] ) } # ACCOUNTS FOR DISCONNECTED SCENARIO
for($x=0;$x -lt $($Line.count);$x++){
$Res.$($header[$x]) = $Line[$x] # DYNAMICALLY ADDS DATA TO $Res
}
$HT += $Res # APPENDS THE LINE OF DATA AS PSCUSTOMOBJECT TO AN ARRAY
Remove-Variable Res # DESTROYS THE LINE OF DATA BY REMOVING THE VARIABLE
}
if($Json) {
$JsonObj = [pscustomobject]#{ $($env:COMPUTERNAME)=$HT } | convertto-json # CREATES ROOT ELEMENT OF COMPUTERNAME AND ADDS THE COMPLETED ARRAY
Return $JsonObj
} else {
Return $HT
}
}
Get-QueryUser
or
Get-QueryUser -Json
For gathering information.
based on https://ss64.com/nt/query-user.html
$result = &quser
$result -replace '\s{2,}', ',' | ConvertFrom-Csv
My own column based take. I'm not sure how much the ID column can extend to the left. Not sure how wide the end is. This is turning out to be tricky. Maybe this way is better: Convert fixed width txt file to CSV / set-content or out-file -append?
# q.ps1
# USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
# js1111 rdp-tcp#20 136 Active . 6/20/2020 4:26 PM
# jx111 175 Disc . 6/23/2020 1:26 PM
# sm1111 rdp-tcp#126 17 Active . 6/23/2020 1:13 PM
#
# di111111 rdp-tcp#64 189 Active 33 7/1/2020 9:50 AM
# kp111 rdp-tcp#45 253 Active 1:07 7/1/2020 9:43 AM
#
#0, 1-22, 23-40, 41-45, 46-53, 54-64, 65-80/82
$q = quser 2>$null | select -skip 1
$q | foreach {
$result = $_ -match '.(.{22})(.{18})(.{5})(.{8})(.{11})(.{16,18})'
[pscustomobject] #{
USERNAME = $matches[1].trim()
SESSIONNAME = $matches[2].trim()
ID = [int]$matches[3].trim()
STATE = $matches[4].trim()
IdleTime = $matches[5].trim()
LogonTime = [datetime]$matches[6].trim()
}
if (! $matches) {$_}
}
Invoke-command example. This is good if you're using Guacamole.
$c = get-credential
icm comp1,comp2,comp3 q.ps1 -cr $c | ft
USERNAME SESSIONNAME ID STATE IdleTime LogonTime PSComputerName RunspaceId
-------- ----------- -- ----- -------- --------- -------------- ----------
js1 136 Disc . 6/20/2020 4:26:00 PM comp1 a8e670cd-4f31-4fd0-8cab-8aa11ee75a73
js2 137 Disc . 6/20/2020 4:26:00 PM comp2 a8e670cd-4f31-4fd0-8cab-8aa11ee75a74
js3 138 Disc . 6/20/2020 4:26:00 PM comp3 a8e670cd-4f31-4fd0-8cab-8aa11ee75a75
Here's another version. The number in the ID column can be at least 1 column before the header. I figure out where the line ends on every line. The Sessionname ends in 3 dots if it's too long, and at least 2 spaces are between each column. The column headers always start at the same place.
ID can be 4 digits. Tricky.
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
rwo rdp-sxs22010... 342 Active 48 2/8/2022 1:41 PM
ym326 rdp-sxs22062... 1012 Active 9 9/27/2022 3:42 PM
cw7 rdp-tcp#4 4 Active 11:16 9/26/2022 7:58 AM
# q2.ps1
$first = 1
quser 2>$null | ForEach-Object {
if ($first -eq 1) {
$userPos = $_.IndexOf("USERNAME")
$sessionPos = $_.IndexOf("SESSIONNAME") # max length 15
$idPos = $_.IndexOf("ID") - 4 # id is right justified
# $idPos = $_.IndexOf("SESSIONNAME") + 15
$statePos = $_.IndexOf("STATE") # max length 6
$idlePos = $_.IndexOf("IDLE TIME") - 2 # right justified too
$logonPos = $_.IndexOf("LOGON TIME")
$first = 0
}
else {
$user = $_.substring($userPos,$sessionPos-$userPos).Trim()
$session = $_.substring($sessionPos,$idPos-$sessionPos).Trim()
$id = [int]$_.substring($idPos,$statePos-$idPos).Trim()
$state = $_.substring($statePos,$idlePos-$statePos).Trim()
$idle = $_.substring($idlePos,$logonPos-$idlePos).Trim()
$logon = [datetime]$_.substring($logonPos,$_.length-$logonPos).Trim()
[pscustomobject]#{User = $user; Session = $session; ID = $id;
State = $state; Idle = $idle; Logon = $logon}
}
}
Output:
User Session ID State Idle Logon
---- ------- -- ----- ---- -----
rwo rdp-sxs22010... 342 Active 48 2/8/2022 1:41:00 PM
Edited: Looks like someone have already created a script that actually works pretty well: https://gallery.technet.microsoft.com/scriptcenter/Get-LoggedOnUser-Gathers-7cbe93ea
Cant believe after so many years there is still no native PowerShell for this.
I've touched up what Tyler Dickson has done and ensure the result comes back as PSCustomObject
$Servers = #("10.x.x.x", "10.y.y.y")
$Result = #()
foreach ($Server in $Servers) {
$Lines = #(query user /server:$s) -split "\n"
foreach($Line in $Lines) #Each Server Line
{
if ($Line -match "USERNAME\s+SESSIONNAME\s+ID\s+STATE\s+IDLE TIME\s+LOGON TIME") {
continue # If is the header then skip to next item in array
}
$Parsed_Server = $Line -split '\s+'
$Result += [PSCustomObject]#{
SERVER = $Server
USERNAME = $Parsed_Server[1]
SESSIONNAME = $Parsed_Server[2]
ID = $Parsed_Server[3]
STATE = $Parsed_Server[4]
IDLE_TIME = $Parsed_Server[5]
LOGON_TIME = $Parsed_Server[6]
}
}
}
$Result | Format-Table
Example output:
SERVER USERNAME SESSIONNAME ID STATE IDLE_TIME LOGON_TIME
------ -------- ----------- -- ----- --------- ----------
10.x.x.x user01 rdp-tcp#13 6 Active . 28/06/2020
10.x.x.x user02 rdp-tcp#35 11 Active 59 29/06/2020
10.y.y.y user03 rdp-tcp#38 12 Active . 29/06/2020
10.y.y.y user04 rdp-tcp#43 14 Active 5 29/06/2020
Unfortunately, no one that proposes solutions with replace method didn't notice that it will be a data collision if SESSIONNAME will empty (it will be when user disc)
So you will have SESSIONNAME contain ID, ID contain STATE etc.
It's not good.
So I`ve fixed it by -replace 'rdp-tcp#\d{1,3}' and propose to you solution with headers.
$Header = "UserName", "ID", "State", "Idle", "Logon", "Time"
$Result = $(quser) -replace 'rdp-tcp#\d{1,3}' -replace "^[\s>]", "" -replace "\s+", "," | ConvertFrom-Csv -Header $Header
Now you can access to any object $Result.Username, $Result.Idle
Was looking for the easy solution to the query user problem that also addresses the issue when SessionName is blank. Ended up combining bits and pieces from the above and came up with this. This isn't perfect, but it does seem to work better than most.
$q = (query user) -split "\n" -replace '\s{18}\s+', " blank "
$qasobject = $q -split "\n" -replace '\s\s+', "," | convertfrom-csv
The First pass with -split will replace any chunk of 18 or more spaces with " blank ", NOTE; there are 2 spaces before and after blank.
The second pass with -split will replace anything with 2 or more spaces with a ",", then pass that through convertfrom-csv to make it an object.
If you want a quick solution and don't need all information, you can also do this:
$a = Get-CimInstance -ClassName Win32_UserProfile -ComputerName "Server-1" | where {$_.Loaded -and $_.LocalPath.split('\')[1] -eq "Users" -and $_.Special -eq $false}
$a | ft -a #{N='Name';E={$_.LocalPath.split('\')[2]}},LastUseTime,Loaded
I Further appended the above code to properly format and also consider the Disconnected users
$HaSH = #()
foreach($ServerLine in #(query user) -split "\n") {
$Report = "" | Select-Object UserName, Session, ID, State, IdleTime, LogonTime
$Parsed_Server = $ServerLine -split '\s+'
if($Parsed_Server -like "USERNAME*") {
Continue
}
$Report.UserName = $Parsed_Server[1]
$Report.Session = $Parsed_Server[2]
$Report.ID = $Parsed_Server[3]
$Report.State = $Parsed_Server[4]
$Report.IdleTime = $Parsed_Server[5]
$Report.LogonTime = $Parsed_Server[6]+" " +$Parsed_Server[7]+" "+$Parsed_Server[8]
if($Parsed_Server[3] -eq "Disc") {
$Report.Session = "None"
$Report.ID = $Parsed_Server[2]
$Report.State = $Parsed_Server[3]
$Report.IdleTime = $Parsed_Server[4]
$Report.LogonTime = $Parsed_Server[5]+" " +$Parsed_Server[6]+" "+$Parsed_Server[7]
}
if($Parsed_Server -like ">*") {
$Parsed_Server=$Parsed_Server.Replace(">","")
$Report.UserName = $Parsed_Server[0]
$Report.Session = $Parsed_Server[1]
$Report.ID = $Parsed_Server[2]
$Report.State = $Parsed_Server[3]
$Report.IdleTime = $Parsed_Server[4]
$Report.LogonTime = $Parsed_Server[5]+" " +$Parsed_Server[6]+" "+$Parsed_Server[7]
}
$HaSH+=$Report
}
$result = (&quser) -replace '\s{2,}', ',' | ConvertFrom-Csv | Select -ExpandProperty USERNAME
$loggedinuser = $result.Trim(">")
Related
Trying to extract specific text and merge output with existing output
I want to extract text from a .txt file. The way the file is layed out is in this format (below first block). Optimally, I would like for the powershell script to take the content of username and votecount and output them side by side. With an integer of 25>= add the letter D beside it. With the output adding itself to a pre-existing output file. Say this week is week 1. And testuser voted 25 times. They should have the output "testuser" 25D. But say in week 2 they voted 24 times. Then it should be "testuser" 49D. However say they had 25 again. Output should then be "testuser" 50DD or 50D2?.. I have what I think should work as an initial baseline for the script which in itself doesn't work.. But combining an output with a pre existing output is beyond my capability. This needs to parse an entire txt file of some 100+ people. So imagine there's like an extra 100 users.. { "username": "testuser", "votecount": "42", "votesclaimed": "0", "lastvotetime": "2022-11-04 09:08:29", "steamid": "00000000000000000000" } Below is what I am working with. Get-Content -Raw C:\Users\--------\Desktop\votes.txt | ConvertFrom-txt | ForEach-Object { [pscustomobject] #{ UserName = $_.username VoteCount = '{0}{1}' -f $_.votecount, ('', 'D')[[int] $_.votecount -gt 25] } } | Export-Csv -NoTypeInformation -Encoding utf8 C:\Users\---------\Desktop\outvotes.csv
Try following : $match = Select-String -Path "c:\temp\test.txt" -Pattern '^\s*"(?<key>[^"]+)"\s*:\s*"(?<value>[^"]+)' $table = [System.Collections.ArrayList]::new() foreach( $row in $match.Matches ) { $key = $row.Groups["key"].Value $value = $row.Groups["value"].Value if($key -eq "username") { $newRow = New-Object -TypeName psobject $table.Add($newRow) | Out-Null } $newRow | Add-Member -NotePropertyName $key -NotePropertyValue $value } $table | Format-Table $groups = $table | Group-Object {$_.username}
How do I log off all currently logged on users?
I would like to create a GPO that includes a scheduled task, that should be run once every day. The scheduled task should get a list of all users currently logged on a specific workstation and log them off (just lock Windows would be prefered, but a regular log off seems easier to make) First I tried to execute the following with the scheduled task: "shutdown.exe /f /l" to log off users. But the command was being executed by the SYSTEM user, so all regular users was not being logged off. 2nd idea was then to create a Powershell Script and place on each workstation. The script should be able to extract all currently logged on users on the workstation and log off / lock screen for all users. $Sessions = quser # Parse the session IDs from the output $SessionIds = ($Sessions -split ' +')[3] $SessionIds # Loop through each session ID and pass each to the logoff command ForEach-Object ($SessionId in $SessionIds){ logoff $SessionId }
The expression ($Sessions -split ' +')[3] gives you the 4th element out of all elements produced by the split. What you actually want is the 4th element from each split line. Also, you should index from the end of the line, because otherwise you'd miss disconnected sessions, as those don't have a session name: PS C:\> quser USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME foo 7 Disc 1:23 23.10.2019 09:12 >bar rdp-tcp#114 12 Active . 23.10.2019 10:12 baz rdp-tcp#112 13 Active . 23.10.2019 10:13 Something like this should work: quser | Select-Object -Skip 1 | ForEach-Object { $id = ($_ -split ' +')[-5] logoff $id } You could also use qwinsta instead of quser, but then you should filter out the listener as well as the "services" (and maybe the "console") session before terminating sessions.
Neither of the above solutions worked for me for all cases because of the variation in blank fields and time formatting. The following worked better. $first = 1 quser | ForEach-Object { if ($first -eq 1) { $userPos = $_.IndexOf("USERNAME") $sessionPos = $_.IndexOf("SESSIONNAME") $idPos = $_.IndexOf("ID") $statePos = $_.IndexOf("STATE") $first = 0 } else { $user = $_.substring($userPos,$sessionPos-$userPos).Trim() $session = $_.substring($sessionPos,$idPos-$sessionPos).Trim() $id = $_.substring($idPos,$statePos-$idPos).Trim() Write-Output "Logging off user:$user session:$session id:$id" logoff $id } }
Might as well turn this into a parser. # q.ps1 $first = 1 quser 2>$null | ForEach-Object { if ($first -eq 1) { $userPos = $_.IndexOf("USERNAME") $sessionPos = $_.IndexOf("SESSIONNAME") $idPos = $_.IndexOf("ID") - 2 # ID is right justified $statePos = $_.IndexOf("STATE") $idlePos = $_.IndexOf("IDLE TIME") $logonPos = $_.IndexOf("LOGON TIME") $first = 0 } else { $user = $_.substring($userPos,$sessionPos-$userPos).Trim() $session = $_.substring($sessionPos,$idPos-$sessionPos).Trim() $id = [int]$_.substring($idPos,$statePos-$idPos).Trim() $state = $_.substring($statePos,$idlePos-$statePos).Trim() $idle = $_.substring($idlePos,$logonPos-$idlePos).Trim() $logon = [datetime]$_.substring($logonPos,$_.length-$logonPos).Trim() [pscustomobject]#{User = $user; Session = $session; ID = $id; State = $state; Idle = $idle; Logon = $logon} } } .\q | ft User Session ID State Idle Logon ---- ------- -- ----- ---- ----- admin console 111 Active none 2/6/2022 11:21:00 PM .\q | % { logoff $_.id }
I had to change the -5 to -6 based on #AnsgarWiecher's answer for Windows 10. quser | Select-Object -Skip 1 | ForEach-Object { $id = ($_ -split ' +')[-6] logoff $id } Thank you Ansgar.
Split array column and result into extra Colums
I have a table (CSV) which shows all the users that have ever logged on to a bunch of computers. Users can have 2 accounts, one username starts with "a" the other with "b", like a100 and b100 (the user behind is the same person). Now I need to get the computers that have more then 2 accounts logged on which do not belong the same users. So A64 and B64 are not reported as separate users. Here is the base list I have: PC1,A64,B52,B64,A41 PC2,A51,B42,B51,A23 PC3,A42,B51 PC4,A5,B5 PC5,A1,B1,A14,A6 My plan was to split the "User"-column into more columns, so the table would look like this: Computername,user1,user2,user3,user4,UserX After this was done, I could iterate through the table and remove the leading letter in the Username, then I would try to get rid of doubles. Do you think that makes sense? Now I got stuck in the first task already. I know how to iterate though the second Column but how do I managed to get the result into another array so the output would be like: Computername,user1,user2,user3,user4,UserX Can you help me split? $UserComputers = import-csv -Delimiter ";" "input.csv" -Header 'Computername','user1','user2','user3','user4' $UserComputers | Select-Object *, #{n='User1';e={$_.User1.Split(',')[0]}}, #{n='User2';e={$_.User1.Split(',')[1]}} I get the error: Select-Object : The property cannot be processed because the property "User1" already exists.
It is useful to make "user" an array. Get-Content "input.csv" | foreach { $name, $users = $_.Split(",") [pscustomobject]#{ Name = $name; Users = $users } } | Where-Object { ($_.Users.Substring(1) | Select-Object -Unique).Count -gt 2 } The output is below. Name Users ---- ----- PC1 {A64, B52, B64, A41} PC2 {A51, B42, B51, A23} PC3 {A42, B51} PC5 {A1, B1, A14, A6}
Input File (input.csv) PC1,A64,B52,B64,A41 PC2,A51,B42,B51,A23 PC3,A42,B51 PC4,A5,B5 PC5,A1,B1,A14,A6 Powershell Script Get-Content -Path .\input.csv | Select-Object #{ Name = "Computer"; Expression = { $_.Split(',')[0] } }, #{ Name="Users"; Expression = { $_.Split(',')[1..($_.Split(',').Length-1)] | Foreach-Object { $_.Substring(1) } | Select-Object -Unique } } | Where-Object { $_.Users.Count -gt 2 } Result: Computer Users ------------- ----- PC1 {64, 52, 41} PC2 {51, 42, 23} PC5 {1, 14, 6} P.S. Bonus: If you want to see more than 4 elements of the array on the screen change the variable $FormatEnumerationLimit = 20 Explanation of the variable meaning
If your file is like your base list, you can do the following to build a new file with all the columns you need: $maxColCount = 0 $data = get-content input.csv foreach ($line in $data) { $MaxColCount = [math]::Max($maxcolcount,($line -split ",").count) } $headers = #("ComputerName") $MaxUserCount = $MaxColCount - 1 Foreach ($c in (1..$MaxUserCount)) { $Headers += "User$c" } $Headers = $Headers -join "," $Headers,$data | Set-Content "output.csv" The code above assumes input.csv has the following format and each column after the first is a user: PC1,A64,B52,B64,A41 PC2,A51,B42,B51,A23 PC3,A42,B51 PC4,A5,B5 PC5,A1,B1,A14,A6
How to get the status each logged in user status details
$dat = query user /server:$SERVER this query gives below data USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME >vm82958 console 1 Active 1:28 2/9/2018 9:18 AM adminhmc 2 Disc 1:28 2/13/2018 10:25 AM nn82543 3 Disc 2:50 2/13/2018 3:07 PM I would like to get each independent user details like STATE, USERNAME, ID details. I tried below code but it is not giving any data foreach($proc in $dat) { $proc.STATE # This is not working this command not giving any data. $proc.ID # This is not working this command not giving any data. } Please help me on this. The result of $dat.GetType() is: IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True String System.Object
This is very similar to this StackOverflow post, but you have blank fields in your data. One solution is to deal with this first. Example below but this may break given data that is very different to your example. For a more robust and complete solution see Matt's comment # replace 20 spaces or more with TWO commas, because it signifies a missing field $dat2 = $dat.Trim() -replace '\s{20,}', ',,' # replace 2 spaces or more with a single comma $datTable = $dat2.Trim() -replace '\s{2,}', ',,' foreach($proc in $datTable) { $proc.STATE $proc.ID }
Another option is to use fixed Columns with string.Insert , like this: $content = quser /server:$SERVER $columns = 14,42,46,54,65 | Sort -Descending $Delimiter = ',' $dat = $content | % { $line = $_ $columns | % { $line = $line.Insert($_, $Delimiter) } $line -replace '\s' } | ConvertFrom-Csv -Delimiter $Delimiter And Then: foreach($proc in $dat) { $proc.STATE $proc.ID # Will show the relevant Data }
Query user /server:server (filter content)
I am working on query user command in PowerShell to filter the content to get the users who wer disconnected for more than 2 days on the server. This is my result: USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME a_admin 2 Disc 20+16:56 19.08.2015 b_admin 3 Disc . 10.12.2015 c_admin 4 Disc 5+22:33 24.08.2015 d_admin 5 Disc 17:47 17.12.2015 e_admin 6 Disc 101+18:58 02.09.2015 f_admin 7 Disc 1+01:27 14.12.2015 The problem is the query user don't retrieve the data as an object format, so I can't select any column from these data, can any one help me to find a way to filter this content? Also, I am having a problem in the content of the idle time. It seems weird!? I tried to put the output in a text file then get the content back and do some filtration, but the result is the same (USERNAME with empty records).
query user produces string output. You can't convert that to objects by piping it into Format-Table. And Select-Object won't do with the output of Format-Table what you seem to expect anyway. Use a regular expression match to transform the string output into a list of objects: $server = 'servername' $re = '(\w+)\s+?(\S*)\s+?(\d+)\s+Disc\s+(\S+)\s+(\d+\.\d+\.\d+)' query user /server:$server | Where-Object { $_ -match $re } | ForEach-Object { New-Object -Type PSCustomObject -Property #{ 'Username' = $matches[1] 'SessionID' = $matches[3] 'IdleTime' = $matches[4] 'LogonTime' = $matches[5] } } | Select-Object Username, IdleTime This will give you everything as string values, though. Since you want to filter on the idle time you may want to convert the values to appropriate types. Using a more elaborate regular expression (with named groups) will help with that. $server = 'servername' $re = '(?<username>\w+)\s+?' + '(\S*)\s+?' + '(?<session>\d+)\s+' + 'Disc\s+' + '(?:(?:(?<days>\d+)\+)?(?<hours>\d+):)?(?<minutes>\d+)\s+' + '(?<logon>\d+\.\d+\.\d+)' query user /server:$server | Where-Object { $_ -match $re } | ForEach-Object { New-Object -Type PSCustomObject -Property #{ 'Username' = $matches['username'] 'SessionID' = [int]$matches['session'] 'IdleTime' = if ($matches['days']) { New-TimeSpan -Days $matches['days'] -Hours $matches['hours'] -Minutes $matches['minutes'] } elseif ($matches['hours']) { New-TimeSpan -Hours $matches['hours'] -Minutes $matches['minutes'] } else { New-TimeSpan -Minutes $matches['minutes'] } 'LogonTime' = [DateTime]::ParseExact($matches['logon'], 'dd\.MM\.yyyy', [Globalization.CultureInfo]::InvariantCulture) } } | Where-Object { $_.IdleTime.TotalDays -gt 2 } | Select-Object Username, IdleTime