I have adapted an existing Powershell script to query a list of servers for logged in (or disconnected) user sessions using quser /server:{servername}
and output the results into a CSV file. It does output any logged in users but what it doesn't capture is servers that had 0 users or weren't accessible (server offline, rpc not available, etc.). I'm assuming that is because these other conditions are "errors" rather than command output.
So if it hits a server with no users it outputs "No User exists for *" in the console running the script. If it hits a server that it can't reach it outputs "Error 0x000006BA enumerating sessionnames" and on a second line "Error [1722]:The RPC server is unavailable." in the console running the script. So neither of these conditions show in the output.csv file.
I wanted to know if someone could suggest how I could also capture these conditions in the CSV as "$Computer has no users" and "$Computer Unreachable"
Here is the script
$ServerList = Read-Host 'Path to Server List?'
$ComputerName = Get-Content -Path $ServerList
foreach ($Computer in $ComputerName) {
quser /server:$Computer | Select-Object -Skip 1 | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
$HashProps = @{
UserName = $CurrentLine[0]
ServerName = $Computer
}
# If session is disconnected different fields will be selected
if ($CurrentLine[2] -eq 'Disc') {
$HashProps.SessionName = $null
$HashProps.Id = $CurrentLine[1]
$HashProps.State = $CurrentLine[2]
$HashProps.IdleTime = $CurrentLine[3]
$HashProps.LogonTime = $CurrentLine[4..6] -join ' '
} else {
$HashProps.SessionName = $CurrentLine[1]
$HashProps.Id = $CurrentLine[2]
$HashProps.State = $CurrentLine[3]
$HashProps.IdleTime = $CurrentLine[4]
$HashProps.LogonTime = $CurrentLine[5..7] -join ' '
}
New-Object -TypeName PSCustomObject -Property $HashProps |
Select-Object -Property ServerName,UserName,State,LogonTime |
ConvertTo-Csv -NoTypeInformation | select -Skip 1 | Out-File -Append .\output.csv
}
}
Anyone curious why I'm using ConvertTo-CSV
rather than Export-CSV
which would be cleaner, is because the servers I'm running this from are running Powershell 2.0 in which Export-CSV doesn't support -Append. I'm not concerned as the output works for what I need, but if someone has a better suggestion for this feel free to comment.
So we have some updates to the script. If there is any error using quser
we capture that as a special entry where the server name will read "Error contacting $computer"
and other text that will give context to the error..
$ServerList = Read-Host 'Path to Server List?'
$ComputerNames = Get-Content -Path $ServerList
$ComputerNames | ForEach-Object{
$computer = $_
$results = quser /server:$Computer 2>&1 | Write-Output
If($LASTEXITCODE -ne 0){
$HashProps = @{
UserName = ""
ServerName = "Error contacting $computer"
SessionName = ""
Id = ""
State = $results | Select-String -Pattern '\[.+?\]' | Select -ExpandProperty Matches | Select -ExpandProperty Value
IdleTime = ""
LogonTime = ""
}
switch -Wildcard ($results){
'*[1722]*'{$HashProps.UserName = "RPC server is unavailable"}
'*[5]*'{$HashProps.UserName = "Access is denied"}
default{$HashProps.UserName = "Something else"}
}
} Else {
$results | Select-Object -Skip 1 | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
$HashProps = @{
UserName = $CurrentLine[0]
ServerName = $Computer
}
# If session is disconnected different fields will be selected
if ($CurrentLine[2] -eq 'Disc') {
$HashProps.SessionName = $null
$HashProps.Id = $CurrentLine[1]
$HashProps.State = $CurrentLine[2]
$HashProps.IdleTime = $CurrentLine[3]
$HashProps.LogonTime = $CurrentLine[4..6] -join ' '
} else {
$HashProps.SessionName = $CurrentLine[1]
$HashProps.Id = $CurrentLine[2]
$HashProps.State = $CurrentLine[3]
$HashProps.IdleTime = $CurrentLine[4]
$HashProps.LogonTime = $CurrentLine[5..7] -join ' '
}
}
}
New-Object -TypeName PSCustomObject -Property $HashProps
} | Select-Object -Property ServerName,UserName,State,LogonTime |
Export-Csv -NoTypeInformation .\output.csv
Part of the issue is that since this is not a PowerShell cmdlet capturing stderr in order to parse it needs to work differnetly. Playing with $erroractionpreference
is an option as well but this is a first draft. We use 2>&1
to capture the error into $results
to hide the message from the screen. Then we use an If
to see if the last command succeeded.
In the event of an error
I used a switch statement with the error text. So you can tailor the output based on the the text returned in $results
Some minor changes
Most of the rest of your code is the same. I moved the object creation outside the If
statement so that errors could be logged and changed to a Export-CSV
as PowerShell will work out the details of that for you.
Unless you intend to have multiple passes of this function over time that you want to capture into the same file.
Console Output before export
ServerName UserName State LogonTime
---------- -------- ----- ---------
serverthing01 bjoe Active 4/9/2015 5:42 PM
Error contacting c4093 RPC server is unavailable [1722]
Error contacting c4094 Access is denied [5]
You can see there is output for each server even though the last two had separate reasons for not having proper output. If you ever see "Something else"
the there was an error that did not have a specific message attached to the error.
When that happens look under State
and the error number is displayed. Then you just need to update the Switch
accordingly.
I was not sure what was the issue where is was dropping the extra lines for multiple users but I already have a dynamic parsing code for positionally delimited text so I am bringing that in here.
Function ConvertFrom-PositionalText{
param(
[Parameter(Mandatory=$true)]
[string[]]$data
)
$headerString = $data[0]
$headerElements = $headerString -split "\s+" | Where-Object{$_}
$headerIndexes = $headerElements | ForEach-Object{$headerString.IndexOf($_)}
$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
}
New-Object -TypeName PSCustomObject -Property $props
}
}
$ServerList = Read-Host 'Path to Server List?'
$ComputerNames = Get-Content -Path $ServerList
$HashProps = @{}
$exportedprops = "ServerName","UserName","State",@{Label="LogonTime";Expression={$_.Logon}}
$ComputerNames | ForEach-Object{
$computer = $_
$results = quser /server:$Computer 2>&1 | Write-Output
If($LASTEXITCODE -ne 0){
$HashProps = @{
UserName = ""
ServerName = "Error contacting $computer"
SessionName = ""
Id = ""
State = $results | Select-String -Pattern '\[.+?\]' | Select -ExpandProperty Matches | Select -ExpandProperty Value
Idle = ""
Time = ""
Logon = ""
}
switch -Wildcard ($results){
'*[1722]*'{$HashProps.UserName = "RPC server is unavailable"}
'*[5]*'{$HashProps.UserName = "Access is denied"}
default{$HashProps.UserName = "Something else"}
}
New-Object -TypeName PSCustomObject -Property $HashProps
} Else {
ConvertFrom-PositionalText -data $results | Add-Member -MemberType NoteProperty -Name "ServerName" -Value $computer -PassThru
}
} | Select-Object -Property $exportedprops |
Export-Csv -NoTypeInformation .\output.csv
Biggest difference here is we use ConvertFrom-PositionalText
to parse the details from quser
. Needed to zero out $HashProps = @{}
which was causing conflicting results across mutliple runs. For good measure got the output of the function and the dummy error data to have the same parameter sets. Used $exportedprops
which has a calculated expression so that you could have the headers you wanted.
New Output
ServerName USERNAME STATE LogonTime
---------- -------- ----- ---------
server01 user1 Disc 3/12/2015 9:38 AM
server01 user2 Active 4/9/2015 5:42 PM
Error contacting 12345 Access is denied [5]
Error contacting 12345 RPC server is unavailable [1722]
svrThg1 user3 Active 4/9/2015 5:28 PM
svrThg1 user4 Active 4/9/2015 5:58 PM
svrThg1 user-1 Active 4/9/2015 9:50 PM
svrThg1 bjoe Active 4/9/2015 10:01 PM
User contributions licensed under CC BY-SA 3.0