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.
Significant Update
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