2

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.

1 Answers1

3

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
Community
  • 1
  • 1
Matt
  • 45,022
  • 8
  • 78
  • 119
  • You gave me what I needed with one minor issue that I believe I can easily work around. If a server has multiple people logged in (these are Citrix servers) its only recording one of the users (the last one that it enumerates) in the CSV. This is exactly the the problem I was having and needed to fix with the work around for -Append in Powershell 2.0. – William Jenkins Apr 10 '15 at 01:00
  • Hmm... I noticed that I was getting multiple results earlier. I will look back at this and see what might have been lost. – Matt Apr 10 '15 at 01:11
  • This is the result I get when I run the command against one server that I know has two users logged in. PS C:\temp> quser /server:myserversname USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME administrator 168 Disc none 2/12/2015 6:10 PM ausername 170 Disc none 2/17/2015 2:37 PM But its only recording "ausername" in the CSV. – William Jenkins Apr 10 '15 at 01:16
  • If Powershell 2.0 is the root cause, I could probably just upgrade the one machine I'm running it from to at least 3.0. Maybe that would fix my issue. I just can't upgrade all machines to Powershell 3.0 for other organizational issues. – William Jenkins Apr 10 '15 at 01:20
  • @WilliamJenkins Check my update. Pretty sure it works with 2.0. If not let me know and I can rig up a 2.0 system to check. After testing it worked with multiple users and still has error correction. – Matt Apr 10 '15 at 02:07
  • Your updated version still had the same issue when run from Powershell 2.0, but works perfectly in Powershell 3.0. I consider that extremely minor as I can (already have actually) upgrade the Powershell version on the machine I'm running the script from. So I consider your solution 100% for my scenario. – William Jenkins Apr 10 '15 at 17:00
  • @WilliamJenkins Cool beans. Glad I could help. – Matt Apr 10 '15 at 17:58