Calling the value of a variable from a variable value - powershell

I run a script in powershell
./name -i tom
then i want to call the value of a variable called $Tom from the input reference value of $i
$tom = 29
$andrew = 99
$bill = 5
Echo $i's age is $i
This would print:
toms age is 29

In powershell this would look like this:
Contents of name.ps1:
$person = $args
$ages = #{"Tom" = 23;
"Andrew" = 99;
"Bill" = 5}
$age = $ages[$person]
Write-Host "$person's age is $age"
And you would call it like this
.\name.ps1 "tom"
$argscontains all the arguments that you send to the script. So if you call the script like this: .\name.ps1 "tom" "bill", your result would be: tom bill's age is 23 5

I would use hashtable, but if you have global variables, you can use following:
#variables
$tom = 29
$andrew = 99
$bill = 5
#your parameter
$i = "tom"
#echo
Echo "$i's age is $((Get-Variable | ? {$_.Name -eq $i}).Value)"

An alternative approach to those provided. More code, arguably overkill, but I think it's good to get a handle on PowerShell's param feature.
# PowerShell's native argument/parameter support
param(
[string]$name
)
# Create an array with Name and Age properties as hashtable.
$people = #(
#{ Name = "Tom" ; Age = 29},
#{ Name = "Andrew" ; Age = 99},
#{ Name = "Bill" ; Age = 5}
)
# Find the person by comparing the argument to what is in your array
$person = $people | Where-Object {$_.Name -eq $name}
# Single name version: If the person is found, print what you would like. Otherwise let the user know name not found
if($person -ne $null){
Write-Host "$($person.Name) is $($person.Age) years old"
}else{
Write-Host "$name not found in list."
}
<# Multiple name version : get rid of the param section
foreach ($name in $args){
$person = $people | Where-Object {$_.Name -eq $name}
if($person -ne $null){
Write-Host "$($person.Name) is $($person.Age) years old"
}else{
Write-Host "$name not found in list."
}
}
#>

Related

Comparing all rows of a table Powershell

I have a DataTable ($dt = New-Object system.Data.datatable) which contains entries as below:
My objective is :
Find servers which same names ,ie , from ServerID column trim the part after underscore (_) (which I achieved via Split()) and then compare with rest of the rows.
If the Server Name is same, check the value of all respective "Status" column
If none of the columns have "IN PROCESS" in them for the respective server, then print the ServerID.
This is what I came up with but got stuck since values are not returned correctly:
foreach($backupid in ($dt.'ServerID' | %{foreach ($y in $_){$y.Split('_')[0]}} | sort -Unique)){
foreach ($row in $dt){
if ($row.'ServerID ' -match "^$backupid" -and $row.Status -ne "IN PROCESS" ){
$row.'ServerID '
}
}
}
Just use a hash table to check whether a server id is (not) IN PROCESS, like:
$dt = ConvertFrom-Csv #'
Server,Status
abc_123,"IN PROCESS"
abc_345,"INACTIVE"
abc_546,"INACTIVE"
xyz_123,"INACTIVE"
xyz_457,"INACTIVE"
xyz_230,"INACTIVE"
'#
$InProcess = #{}
$dt | Foreach-Object {
$Id = $_.Server.Split('_')[0]
if (!$InProcess.Contains($Id)) { $InProcess[$Id] = $False }
if ($_.Status -eq 'IN PROCESS') { $InProcess[$Id] = $True }
}
$dt | Foreach-Object {
$Id = $_.Server.Split('_')[0]
if ($InProcess[$Id] -eq $False) { $_ }
}
Server Status
------ ------
xyz_123 INACTIVE
xyz_457 INACTIVE
xyz_230 INACTIVE
Instead of nested loops, try Group-Object!
# Group objects by the first part of the Server ID
$dt |Group { $_.ServerID.Split('_')[0] } |Where-Object {
# Then find only the groups with no objects where Status is IN_PROGRESS
$_.Group.Status -notcontains 'IN_PROGRESS'
} |ForEach-Oject -MemberName ServerID # Output just the Server value

PowerShell wait for output from function or invoke-command

I have a script block/function that returns PSCustomObject followed by Write-Host.
I want to get the output first then print the write-host but I can't seem to figure it out.
function ReturnArrayList {
param (
[int] $number
)
[System.Collections.ArrayList]$folderList = #()
$folderObject = [PSCustomObject]#{
Name = 'John'
number = $number
}
#Add the object to the array
$folderList.Add($folderObject) | Out-Null
return $folderList
}
$sb = {
param (
[int] $number
)
[System.Collections.ArrayList]$folderList = #()
$folderObject = [PSCustomObject]#{
Name = 'John'
number = $number
}
#Add the object to the array
$folderList.Add($folderObject) | Out-Null
return $folderList
}
ReturnArrayList -number 5
#Invoke-Command -ScriptBlock $sb -ArgumentList 5
Write-Host "This write host should come later"
Result:
This write host should come after
Name number
---- ------
John 5
Desired result:
Name number
---- ------
John 5
This write host should come after
How can I get the return result first and print the write-host message?
Thank you for your help in advance!
You can force PowerShell to write the output from ReturnArrayList to the screen before reaching Write-Host by piping it to either one of the Format-* cmdlets or Out-Default:
$object = ReturnArrayList -number 5
$object |Out-Default
Write-Host "This write host should come later"
Result:
Name number
---- ------
John 5
This write host should come later
Beware that your ReturnArrayList function does not actually return an ArrayList - PowerShell will automatically enumerate the item(s) in $folderlist, and since it only contains one item, the result is just a single PSCustomObject, "unwrapped" from the ArrayList so to speak:
PS ~> $object = ReturnArrayList -number 5
PS ~> $object.GetType().Name
PSCustomObject
To preserve enumerable types as output from functions, you'll need to either use Write-Output -NoEnumerate, or wrap the it in an array using the , operator:
function ReturnArrayList {
param (
[int] $number
)
[System.Collections.ArrayList]$folderList = #()
$folderObject = [PSCustomObject]#{
Name = 'John'
number = $number
}
#Add the object to the array
$folderList.Add($folderObject) | Out-Null
return ,$folderList
# or
Write-Output $folderList -NoEnumerate
}
Data is usually output to the pipeline, while Write-Host bypasses the pipeline and writes to the console directly.
Using Write-Output instead of Write-Host will fix this issue. You can easily find more in-depth information on this topic, and when not to Write-Host.

Creating and passing an array from one function to another [duplicate]

This question already has answers here:
Boolean variable gets returned as an Object[]
(2 answers)
Closed 3 years ago.
What I'm trying to do is make it so I can create the array and check it in a single a single function as I call it in other functions so it'd be easier to just add $list = GetUserList instead of verifying the $list each time I plan on calling the GetUserList function.
https://pastebin.com/6h4MJH9n
What works:
function GetUserList {
$name = Read-Host "Please enter the users name (multiple users can be separated by commas)"
$names = $name.Split(",")
for ( $i = 0; $i -lt $names.Count; $i++ ) {
$firstn = $names[$i].Trim().Split(" ")[0]
$lastn = $names[$i].Trim().Split(" ")[-1]
$ulist += #([PSCustomObject]#{
First = "$firstn";
Last = "$lastn"
})
}
return $ulist
}
function UserList {
do {
$userlist = GetUserList
$userlist | Format-Table -AutoSize -Wrap
$again = Read-Host "Is this correct? (y/n)"
} until ( $again -eq "y" )
$userlist | ForEach-Object {
"First: $($_.First)"
"Last: $($_.Last)"
}
}
UserList
What doesn't work:
function GetUserList {
do {
$ulist = #()
$name = Read-Host "Please enter the users name (multiple users can be separated by commas)"
$names = $name.Split(",")
for ( $i = 0; $i -lt $names.Count; $i++ ) {
$firstn = $names[$i].Trim().Split(" ")[0]
$lastn = $names[$i].Trim().Split(" ")[-1]
$ulist += #([PSCustomObject]#{
First = "$firstn";
Last = "$lastn"
})
}
$ulist | Format-Table -AutoSize -Wrap
$again = Read-Host "Is this correct? (y/n)"
} until ( $again -eq "y" )
return $ulist
}
function UserList {
$userlist = GetUserList
$userlist | ForEach-Object {
"First: $($_.First)"
"Last: $($_.Last)"
}
}
UserList
I don't get an errors, it's just the code that doesn't work completely skips the for loop and I have no idea why.
the problem is situated in $ulist | Format-Table -AutoSize -wrap since you're not either
storing the formatted content in a variable,
nore sending the formatted content to the PowerShell host for printing (as stated in #Lee_Daily's comment in the OP)
PowerShell will return the formatted content to the output stream. Additionally to the formatted content you're also sending the content of $ulist to the output stream (via the Return $ulist statement). Based on that $userlist (via $userlist = GetUserList) contains the $ulist content PLUS the formatted $ulist content.
These can also be seen when debugging your code (see Set-PsBreakPoint):
[DBG]:> $userlist
First Last
----- ----
user 1
user 2
user 3
First Last
----- ----
user 1
user 2
user 3
As #Lee_Daily suggests change the line $ulist | Format-Table -AutoSize -wrap to $ulist | Format-Table -AutoSize -wrap | Out-Host. Piping to Out-Host will prevent that the output of Format-Table is written to the output-stream.
Further suggested reading:
about_Redirection
PowerShell streams devblog

Powershell Type Conversion Format

I have written/nabbed code that is designed to iterate through a list of hostnames, grab the IP's, and then log users off if they are not 'Active' on the machine. Iterating through the hostnames and putting a list of IP's into an array list works perfectly:
$HostNames = "google.com","facebook.com","test.com" #Create an array with the hostnames.
$IPList = [System.Collections.ArrayList]#() #Create an empty array list so we can add/remove objects.
for ($i=0;$i -lt $HostNames.Length; $i++){ #Loop through the hostnames.
[System.Net.Dns]::GetHostAddresses($HostNames[$i]) | foreach {$IPList.Add($_.IPAddressToString) } #Get all the IPs for the host and add them to the array.
}
When I call:
echo $IPList
I get the expected result:
216.58.198.78
31.13.73.35
69.172.200.235
The piece of code I stole from https://stackoverflow.com/a/35848515/3718225 also works perfectly when including the hostname or IP directly, such as:
$sessions = qwinsta /server "localhost" | ?{ $_ -notmatch '^ SESSIONNAME' } | %{#Return information about the selected server | Using the previous command, do not return a row where anything matches SESSIONNAME in caps
$item = "" | Select "Active", "SessionName", "Username", "Id", "State", "Type", "Device" #Select what information to return
#All the items below this line trim up the session info.
$item.Active = $_.Substring(0,1) -match '>'
$item.SessionName = $_.Substring(1,18).Trim()
$item.Username = $_.Substring(19,20).Trim()
$item.Id = $_.Substring(39,9).Trim()
$item.State = $_.Substring(48,8).Trim()
$item.Type = $_.Substring(56,12).Trim()
$item.Device = $_.Substring(68).Trim()
$item
}
But when I do something like:
$HostNames = "google.com","facebook.com","test.com" #Create an array with the hostnames.
$IPList = [System.Collections.ArrayList]#() #Create an empty array list so we can add/remove objects.
for ($i=0;$i -lt $HostNames.Length; $i++){ #Loop through the hostnames.
[System.Net.Dns]::GetHostAddresses($HostNames[$i]) | foreach {$IPList.Add($_.IPAddressToString) } #Get all the IPs for the host and add them to the array.
}
for ($i=0;$i -lt $IPList.Length; $i++){ #For all of the IP's in the array.
$sessions = qwinsta /server $IPList[$i]| ?{ $_ -notmatch '^ SESSIONNAME' } | %{#Return information about the selected server | Using the previous command, do not return a row where anything matches SESSIONNAME in caps
$item = "" | Select "Active", "SessionName", "Username", "Id", "State", "Type", "Device" #Select what information to return
#All the items below this line trim up the session info.
$item.Active = $_.Substring(0,1) -match '>'
$item.SessionName = $_.Substring(1,18).Trim()
$item.Username = $_.Substring(19,20).Trim()
$item.Id = $_.Substring(39,9).Trim()
$item.State = $_.Substring(48,8).Trim()
$item.Type = $_.Substring(56,12).Trim()
$item.Device = $_.Substring(68).Trim()
$item
}
foreach ($session in $sessions){ #For all the sessions
if (($session.Username -ne "" -or $session.Username.Length -gt 1) -and ($session.State -eq 'Active')){ #So long as the session name is longer then 1 and not blank AND the session is not 'active'
#logoff /server $IPList $session.Id
Write-Host $IPList[$i] $session.Username $session.State #Disconnect the user.
}
}
}
I get an error:
Could not compare "0" to "13 11 14". Error: "Cannot convert the "System.Object[]" value of type "System.Object[]" to
type "System.Int32"."
At line:1 char:11
+ for ($i=0;$i -lt $IPList.Length; $i++){ #For all of the IP's in the a ...
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : ComparisonFailure
I have figured that I need to find a way to convert the ArrayList from a System.Object to a string. However, something like:
$IPList | out-string
Doesn't seem to work/be the solution. Why? What is the best way to convert this? I am not sure I understand why the $ArrayList stores strings as objects, and not strings.
Your loop is iterating the wrong number of items, to get the items count for the ArrayList or any other Array, use the Count Method, see ArrayList.Count
$IPList.Length will show the total character length of each item in the array while $IPList.Count will give you the total items in the Array.
So your Could not compare "0" to "13 11 14". error is:
google.com -> IP -> 216.58.206.78 Length -> 13
facebook.com -> IP -> 31.13.73.35 Length -> 11
test.com -> IP -> 69.172.200.235 Length -> 14
Replace the length in Count in this line:
for ($i=0;$i -lt $IPList.Length; $i++)
To this:
for ($i=0;$i -lt $IPList.Count; $i++)

Easier way to parse 'query user' in PowerShell (or quser)

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(">")