Reading/Parsing Console Output

This topic contains 8 replies, has 5 voices, and was last updated by  Scotty 3 weeks, 4 days ago.

  • Author
    Posts
  • #84671

    Scotty
    Participant

    Know first, I am pretty new at Powershell but I am here to hopefully learn and get better.

    I am working on a script that will log into Cisco devices (routers for the moment) via SSH. The script will then run a command and take the output and save it to a text file. All of this works running just a single command (it's a work in progress, working towards running more commands). This is not for device configuration, simply to run some show commands (and eventually a ping command). For the moment I am just running a 'show BGP summary' command. However, I want to run a show BGP neighbor command and be able to parse the output to read the BGP state (Idle, Active, Established, etc.).

    And that is where I am hitting a wall and not sure where to start. When the command 'show bgp neighbor 1.2.3.4 is run, I want the entire output saved to the text file, which I can do, but where do I start in trying to parse that output?

    I can share what I have at the moment (if needed) just be warned, its probably going to make some PowerShell expert cringe, but I did not think it would be needed in order to get started on parsing the output.

  • #84688

    nohwnd
    Participant

    Parsing output highly depends on how the output is formatted. Usually the output is in columns, and there you often just split the lines on spaces and translate that to psobjects.

    Here is an example of how you can parse a simple output. I am saving output of Get-Process (ps) as a string, and then parsing it back to powershell objects.

    $output = ps | select -first 10 | select id, si, processname | format-table | out-string
    "Original output:"
    $output
    
    "Parsed output:"
    $lines  = $output -split "`n"
    foreach ($line in $lines | Select -Skip 3) {
        $id, $si, $processname = $line.Trim() -split '\s+'
        [pscustomobject] @{
            My_Id = $id
            My_SI = $si
            My_ProcessName = $processname
        }
    }
    
    
    
    Original output:
    
       Id SI ProcessName          
       -- -- -----------          
     3360  0 aaHMSvc              
    12936  1 AiChargerPlus        
     9488  1 ApplicationFrameHost 
     3320  0 armsvc               
     3416  0 AsusFanControlService
     3340  0 atkexComSvc          
     1212  1 Code                 
     1776  1 Code                 
     2708  1 Code                 
     3144  1 Code                 
    
    
    
    Parsed output:
    
    My_Id My_SI My_ProcessName       
    ----- ----- --------------       
    3360  0     aaHMSvc              
    12936 1     AiChargerPlus        
    9488  1     ApplicationFrameHost 
    3320  0     armsvc               
    3416  0     AsusFanControlService
    3340  0     atkexComSvc          
    1212  1     Code                 
    1776  1     Code                 
    2708  1     Code                 
    3144  1     Code                 
                                     
                                     
    
  • #84703

    Sam Boutros
    Participant

    Can you post sample output of both commands?

    • #84707

      Scotty
      Participant

      I could post a sample output, but it will have to wait until I get to work. However, it's simple Cisco router console output.

  • #84706

    Richard Siddaway
    Moderator

    Also look at ConvertFrom-String

  • #84712

    Sam Boutros
    Participant

    When parsing you want to use a sample file that captures are many output scenarios as possible. I used this sample input file based on https://www.cisco.com/c/en/us/td/docs/ios/iproute_bgp/command/reference/irg_book/irg_bgp5.html

    Router# show ip bgp summary 
    
     BGP router identifier 172.16.1.1, local AS number 100 
     BGP table version is 199, main routing table version 199 
     37 network entries using 2850 bytes of memory 
     59 path entries using 5713 bytes of memory 
     18 BGP path attribute entries using 936 bytes of memory 
     2 multipath network entries and 4 multipath paths 
     10 BGP AS-PATH entries using 240 bytes of memory 
     7 BGP community entries using 168 bytes of memory 
     0 BGP route-map cache entries using 0 bytes of memory 
     0 BGP filter-list cache entries using 0 bytes of memory
     90 BGP advertise-bit cache entries using 1784 bytes of memory 
     36 received paths for inbound soft reconfiguration 
     BGP using 34249 total bytes of memory 
     Dampening enabled. 4 history paths, 0 dampened paths 
     BGP activity 37/2849 prefixes, 60/1 paths, scan interval 15 secs 
      
     Neighbor        V    AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down State/PfxRcd
     10.100.1.1      4   200      26      22      199    0    0 00:14:23 23
     10.200.1.1      4   300      21      51      199    0    0 00:13:40 0
    
    Router# show ip bgp summary
    
     BGP router identifier 192.168.3.1, local AS number 45000
     BGP table version is 1, main routing table version 1
    
     Neighbor        V    AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
     *192.168.3.2    4 50000       2       2        0    0    0 00:00:37        0
     * Dynamically created based on a listen range command
     Dynamically created neighbors: 1/(200 max), Subnet ranges: 1
    
     BGP peergroup group192 listen range group members: 
       192.168.0.0/16
    
    Router# show ip bgp summary
    
     BGP router identifier 172.17.1.99, local AS number 65538
     BGP table version is 1, main routing table version 1
    
     Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  Statd
     192.168.1.2     4       65536       7       7        1    0    0 00:03:04      0
     192.168.3.2     4       65550       4       4        1    0    0 00:00:15      0
    
    Router# show ip bgp summary
    
     BGP router identifier 172.17.1.99, local AS number 1.2
     BGP table version is 1, main routing table version 1
    
     Neighbor        V           AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  Statd
     192.168.1.2     4         1.0       9       9        1    0    0 00:04:13      0
     192.168.3.2     4        1.14       6       6        1    0    0 00:01:24      0
    
    

    and this script would parse a file like that:

    #Requires -Version 5
    #Requires -RunAsAdministrator
    # Script to parse 'show ip bgp summary' output - Sam Boutros - 21 November 2017
    
    #region input
    $SourceFile = '.\SampleCisco1.txt'
    $NewRecordMarker = 'show ip bgp summary'
    #endregion
    
    #region Process
    # Read input file
    $Lines = Get-Content $SourceFile
    
    # Breakdown input file into records based on $NewRecordMarker
    $i=0; $RecordStartLines = $Lines | % { if ($_ -match $NewRecordMarker) { $i }; $i++ }
    "Identified $($RecordStartLines.Count) records at lines: $($RecordStartLines -join ', ' )"
    
    # Parse each record to create the output PS object
    $myOutput = 0..($RecordStartLines.Count-1) | % {
        $RecordStartLine = $RecordStartLines[$_]
        $RecordEndLine = $(
            if ($_ -eq $RecordStartLines.Count-1) {
                $Lines.Count-1
            } else {
                $RecordStartLines[$_+1]-1
            }        
        )
    
        Remove-Variable PastNeighborline -EA 0
        foreach ($Line in $Lines[$RecordStartLine..$RecordEndLine]) {
            # Get RouterID from 'BGP router identifier' line
            if ($Line -match 'BGP router identifier') { $RouterID = $Line.Split(' ')[4].Replace(',','').Trim() }
            
            # We're only interested in the lines after 'Neighbor' line that has a '.'
            if ($Line -match 'Neighbor') { $PastNeighborline = $true }
            if ($Line -match 'Dynamically created') { $PastNeighborline = $false }
            if ($PastNeighborline -and $Line -match '\.') {  
                $Neighbor = ($Line.Split(' ') | ? { $_ })[0]
                if ($Neighbor -match '\*') { $Neighbor = $Neighbor.Replace('*',''); $Dynamic = $true } else { $Dynamic = $false }
                [PSCustomObject][Ordered]@{
                    RouterID = $RouterID
                    Neighbor = $Neighbor
                    Dynamic  = $Dynamic
                    State    = ($Line.Split(' ') | ? { $_ })[9] 
                } # PSCustomObject
            } # if $PastNeighborline 
        } # foreach $Line
    } # foreach 0..($RecordStartLines.Count-1) 
    #endregion
    
    #region output
    # Dedup the output
    $myOutput | group RouterID,Neighbor,Dynamic,State | % {
        $_.Group | select RouterID,Neighbor,Dynamic,State -First 1
    }
    #endregion
    

    giving output like:

    RouterID    Neighbor    Dynamic State
    --------    --------    ------- -----
    172.16.1.1  10.100.1.1    False 23   
    172.16.1.1  10.200.1.1    False 0    
    192.168.3.1 192.168.3.2    True 0    
    172.17.1.99 192.168.1.2   False 0    
    172.17.1.99 192.168.3.2   False 0   
    

    Obviously you can output more properties depending on what matters to you..

  • #84739

    Scotty
    Participant

    Thank you all for the great replies thus far. Sorry, it has taken so long to respond. 10-hour work days in a NOC on minimal holiday staffing doesn't leave a lot of time to freeload on the web 🙂

    I did realize something today in my approach to this (I told you I was new). My thinking was that I could parse the console output in real-time. Apparently, I was a bit off base in my thinking. So in that regard, I learned something today. It appears I will have to read the output into a file and/or a variable and then pick it apart there, most likely using the suggestion of ConvertFrom-String.

    Sam, I can't thank you enough for the example you gave, but you went so far over my head I will probably spend the next week just trying to pick through and understand your code. However, it looks like you doing pretty much exactly what I am wanting to do.

You must be logged in to reply to this topic.