How can I output all the output of a called command in Powershell script

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.

powershell
csv
output

1 Answer

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
answered on Stack Overflow Apr 9, 2015 by Matt • edited May 23, 2017 by Community

User contributions licensed under CC BY-SA 3.0