コード例 #1
0
 def collect_it(in_list_of_dict, in_send_cmd, in_send_cmd_key, in_template_filename, in_key_suffix=''):
     # start with an empty output
     this_output_raw = ''
     # step across each dict entry in the list
     for this_dict_entry in in_list_of_dict:
         # send the in_send_cmd and add to it per dict entry aspect defined by the in_send_cmd_key
         this_send_cmd = in_send_cmd + format(this_dict_entry[in_send_cmd_key])
         # add this to the overall output to be processed
         this_output_raw += session.get_command_output(this_send_cmd)
     # use the template file provided & process the overall output
     processing_template_file = session.script.get_template(in_template_filename)
     processing_list_of_dict = utilities.textfsm_parse_to_dict(this_output_raw, processing_template_file)
     # start with an empty out_list_of_dict
     out_list_of_dict = []
     for this_dict_entry in processing_list_of_dict:
         # start with an empty change_dict_entry
         change_dict_entry = {}
         # step across each key
         for this_key in this_dict_entry.keys():
             # don't change the primary key name .. for the key:value entry
             if this_key == in_send_cmd_key: change_dict_entry[this_key] = this_dict_entry[this_key]
             # for the rest of the keys, add the suffix .. for the key:value entry
             else: change_dict_entry[this_key+in_key_suffix] = this_dict_entry[this_key]
         # now with key names changed, append the change_dict_entry
         out_list_of_dict.append(change_dict_entry)
     return out_list_of_dict
コード例 #2
0
def script_main(session, ask_vrf=True, vrf=None):
    """
    | SINGLE device script
    | Author: Jamie Caesar
    | Email: [email protected]

    This script will grab the route table information from a Cisco IOS or NXOS device and export details about each
    next-hop address (how many routes and from which protocol) into a CSV file.  It will also list all connected
    networks and give a detailed breakdown of every route that goes to each next-hop.

    :param session: A subclass of the sessions.Session object that represents this particular script session (either
                SecureCRTSession or DirectSession)
    :type session: sessions.Session
    :param ask_vrf: A boolean that specifies if we should prompt for which VRF.  The default is true, but when this
        module is called from other scripts, we may want avoid prompting and supply the VRF with the "vrf" input.
    :type ask_vrf: bool
    :param vrf: The VRF that we should get the route table from.  This is used only when ask_vrf is False.
    :type vrf: str
    """
    # Get script object that owns this session, so we can check settings, get textfsm templates, etc
    script = session.script

    # Start session with device, i.e. modify term parameters for better interaction (assuming already connected)
    session.start_cisco_session()

    # Validate device is running a supported OS
    session.validate_os(["IOS", "NXOS"])

    # If we should prompt for a VRF, then do so.  Otherwise use the VRF passed into the function (if any)
    if ask_vrf:
        selected_vrf = script.prompt_window("Enter the VRF name. (Leave blank for default VRF)")
    else:
        selected_vrf = vrf

    # If we have a VRF, modify our commands and hostname to reflect it.  If not, pull the default route table.
    if selected_vrf:
        send_cmd = "show ip route vrf {0}".format(selected_vrf)
        session.hostname = session.hostname + "-VRF-{0}".format(selected_vrf)
        logger.debug("Received VRF: {0}".format(selected_vrf))
    else:
        send_cmd = "show ip route"

    raw_routes = session.get_command_output(send_cmd)

    if session.os == "IOS":
        template_file = script.get_template("cisco_ios_show_ip_route.template")
    else:
        template_file = script.get_template("cisco_nxos_show_ip_route.template")

    fsm_results = utilities.textfsm_parse_to_dict(raw_routes, template_file)

    route_list = parse_routes(fsm_results)

    output_filename = session.create_output_filename("nexthop-summary", ext=".csv")
    output = nexthop_summary(route_list)
    utilities.list_of_lists_to_csv(output, output_filename)

    # Return terminal parameters back to the original state.
    session.end_cisco_session()
コード例 #3
0
def get_interface_detail(session, to_cvs=False):
    """
    A function that captures the WLC AireOS interface detail table and returns an output list

    :param session: The script object that represents this script being executed
    :type session: session.Session

    :return: A list of interface details
    :rtype: list of lists
    """
    send_cmd = "show interface summary"
    output_raw = session.get_command_output(send_cmd)

    # TextFSM template for parsing "show interface summary" output
    template_file = session.script.get_template(
        "cisco_aireos_show_interface_summary.template")
    interface_summ_dict = utilities.textfsm_parse_to_dict(
        output_raw, template_file)

    output_raw = ''
    for interface_entry in interface_summ_dict:
        send_cmd = "show interface detailed " + format(
            interface_entry["INT_Name"])
        output_raw += session.get_command_output(send_cmd)

    # TextFSM template for parsing "show interface detailed <interface-name>" output
    template_file = session.script.get_template(
        "cisco_aireos_show_interface_detailed.template")
    output = utilities.textfsm_parse_to_list(output_raw,
                                             template_file,
                                             add_header=True)

    if to_cvs:
        output_filename = session.create_output_filename("interface-detail",
                                                         ext=".csv")
        utilities.list_of_lists_to_csv(output, output_filename)

    return output
コード例 #4
0
def script_main(session):
    supported_os = ["IOS", "NXOS"]
    if session.os not in supported_os:
        logger.debug("Unsupported OS: {0}.  Exiting program.".format(
            session.os))
        session.message_box(
            "{0} is not a supported OS for this script.".format(session.os),
            "Unsupported OS",
            options=sessions.ICON_STOP)
        return
    else:
        send_cmd = "show ip route"

    selected_vrf = session.prompt_window(
        "Enter the VRF name. (Leave blank for default VRF)")
    if selected_vrf != "":
        send_cmd = send_cmd + " vrf {0}".format(selected_vrf)
        session.hostname = session.hostname + "-VRF-{0}".format(selected_vrf)
        logger.debug("Received VRF: {0}".format(selected_vrf))

    raw_routes = session.get_command_output(send_cmd)

    if session.os == "IOS":
        template_file = "textfsm-templates/cisco_ios_show_ip_route.template"
    else:
        template_file = "textfsm-templates/cisco_nxos_show_ip_route.template"

    fsm_results = utils.textfsm_parse_to_dict(raw_routes, template_file)

    route_list = parse_routes(fsm_results)

    output_filename = session.create_output_filename("nexthop-summary",
                                                     ext=".csv")
    output = nexthop_summary(route_list)
    utils.list_of_lists_to_csv(output, output_filename)

    # Clean up before closing session
    session.end()
コード例 #5
0
def per_device_work(session, enable_pass):
    """
    This function contains the code that should be executed on each device that this script connects to.  It is called
    after establishing a connection to each device in the loop above.

    You can either put your own code here, or if there is a single-device version of a script that performs the correct
    task, it can be imported and called here, essentially making this script connect to all the devices in the chosen
    CSV file and then running a single-device script on each of them.
    """
    script = session.script
    interesting_keys = ['HARDWARE', 'HOSTNAME', 'MODEL', 'VERSION', 'SERIAL', 'UPTIME', 'LAST_REBOOT_REASON', 'IMAGE']

    # Validate device is of a proper OS
    supported_os = ['IOS', 'NXOS', 'ASA']
    session.start_cisco_session(enable_pass=enable_pass)
    session.validate_os(supported_os)

    # Select the appropriate template to process show version data
    if session.os == 'IOS':
        ver_template_file = script.get_template('cisco_ios_show_version.template')
    elif session.os == 'NXOS':
        ver_template_file = script.get_template('cisco_nxos_show_version.template')
    elif session.os == 'ASA':
        ver_template_file = script.get_template('cisco_asa_show_version.template')
    else:
        raise sessions.UnsupportedOSError("{0} isn't a supported OS.".format(session.os))

    # Process Show Version data
    raw_version = session.get_command_output('show version')
    fsm_output = utilities.textfsm_parse_to_dict(raw_version, ver_template_file)

    if len(fsm_output) > 1:
        raise sessions.InteractionError("Received multiple entries from a single device, which should not happen.")
    else:
        ver_info = fsm_output[0]

    # For NXOS get parse 'show inventory' for model and serial number
    if session.os == 'NXOS':
        ver_info['HOSTNAME'] = session.hostname
        logger.debug("<M_SCRIPT> NXOS device, getting 'show inventory'.")
        raw_inv = session.get_command_output('show inventory')
        inv_template_file = script.get_template('cisco_nxos_show_inventory.template')
        inv_info = utilities.textfsm_parse_to_dict(raw_inv, inv_template_file)
        for entry in inv_info:
            if entry['NAME'] == "Chassis":
                logger.debug("<M_SCRIPT> Adding {0} as model number".format(entry['PID']))
                ver_info['MODEL'] = entry['PID']
                logger.debug("<M_SCRIPT> Adding {0} as serial number".format(entry['SN']))
                ver_info['SERIAL'] = entry['SN']
                break
    elif session.os == 'ASA':
        logger.debug("<M_SCRIPT> ASA device, writing 'N/A' for last reboot reason.")
        # For ASA put a N/A reload reason since ASA doesn't have this output
        ver_info['LAST_REBOOT_REASON'] = "N/A"
        # If we don't have a model number in older 'show ver' extract it from the hardware column.
        if not ver_info['MODEL']:
            model = ver_info['HARDWARE'].split(',')[0]
            logger.debug("<M_SCRIPT> ASA device without model, using {0}".format(model))
            ver_info['MODEL'] = model
    elif session.os == 'IOS':
        # Expand multiple serial numbers found in stacks, or just remove lists for serial and model if only 1 device
        logger.debug("<M_SCRIPT> IOS device, writing list of serials/models to separate entries")
        num_in_stack = len(ver_info['SERIAL'])
        if len(ver_info['MODEL']) != num_in_stack:
            # For older IOS, we may not get a model number, but we'll pick up the hardware.  As long as this happens
            # when only 1 serial is detected (not a switch stack), then just use the HARDWARE for the model number.
            if len(ver_info['MODEL']) == 0 and num_in_stack == 1 and ver_info['HARDWARE']:
                ver_info['MODEL'] = [ver_info['HARDWARE']]
            else:
                logger.debug("<M_SCRIPT> List of Serials & Models aren't the same length. Likely TextFSM parsing problem.")
                raise sessions.InteractionError("Received {0} serial nums and only {1} model nums in output."
                                                .format(num_in_stack, len(ver_info['MODEL'])))
        new_output = []
        for x in range(num_in_stack):
            stack_subset = dict((key, ver_info[key]) for key in interesting_keys)
            stack_subset['HOSTNAME'] = "{0}-{1}".format(ver_info['HOSTNAME'], x+1)
            stack_subset['SERIAL'] = ver_info['SERIAL'][x]
            stack_subset['MODEL'] = ver_info['MODEL'][x]
            new_output.append(stack_subset)
            logger.debug("Created an entry for {0}/{1}".format(stack_subset['MODEL'], stack_subset['SERIAL']))
        fsm_output = new_output

    # Create output data structure with only the keys that we need.
    inv_data = []
    logger.debug("Creating list of dictionaries to return, and adding manufacture dates.")
    for entry in fsm_output:
        subset = dict((key, entry[key]) for key in interesting_keys)
        subset['MANUFACTURE_DATE'] = get_manufacture_date(subset['SERIAL'])
        inv_data.append(subset)

    # End session on the Cisco device
    session.end_cisco_session()

    return inv_data
コード例 #6
0
def script_main(session, ask_vrf=True, vrf=None):
    """
    | SINGLE device script
    | Author: Jamie Caesar
    | Email: [email protected]

    This script will grab the route table information from a Cisco IOS or NXOS device and export details about each
    next-hop address (how many routes and from which protocol) into a CSV file.  It will also list all connected
    networks and give a detailed breakdown of every route that goes to each next-hop.

    :param session: A subclass of the sessions.Session object that represents this particular script session (either
                SecureCRTSession or DirectSession)
    :type session: sessions.Session
    :param ask_vrf: A boolean that specifies if we should prompt for which VRF.  The default is true, but when this
        module is called from other scripts, we may want avoid prompting and supply the VRF with the "vrf" input.
    :type ask_vrf: bool
    :param vrf: The VRF that we should get the route table from.  This is used only when ask_vrf is False.
    :type vrf: str
    """
    # Get script object that owns this session, so we can check settings, get textfsm templates, etc
    script = session.script

    # Start session with device, i.e. modify term parameters for better interaction (assuming already connected)
    session.start_cisco_session()

    # Validate device is running a supported OS
    session.validate_os(["IOS", "NXOS"])

    # If we should prompt for a VRF, then do so.  Otherwise use the VRF passed into the function (if any)
    if ask_vrf:
        selected_vrf = script.prompt_window(
            "Enter the VRF name. (Leave blank for default VRF)")
    else:
        selected_vrf = vrf

    # If we have a VRF, modify our commands and hostname to reflect it.  If not, pull the default route table.
    if selected_vrf:
        send_cmd = "show ip route vrf {0}".format(selected_vrf)
        session.hostname = session.hostname + "-VRF-{0}".format(selected_vrf)
        logger.debug("Received VRF: {0}".format(selected_vrf))
    else:
        send_cmd = "show ip route"

    raw_routes = session.get_command_output(send_cmd)

    if session.os == "IOS":
        template_file = script.get_template("cisco_ios_show_ip_route.template")
    else:
        template_file = script.get_template(
            "cisco_nxos_show_ip_route.template")

    fsm_results = utilities.textfsm_parse_to_dict(raw_routes, template_file)

    route_list = parse_routes(fsm_results)

    output_filename = session.create_output_filename("nexthop-summary",
                                                     ext=".csv")
    output = nexthop_summary(route_list)
    utilities.list_of_lists_to_csv(output, output_filename)

    # Return terminal parameters back to the original state.
    session.end_cisco_session()
コード例 #7
0
def script_main(session, ask_vrf=True, vrf=None):
    """
    | SINGLE device script
    | Author: Jamie Caesar
    | Email: [email protected]

    This script will grab the EIGRP topology from a Cisco IOS or NXOS device and export a summary of how many networks
    are learned from each successor/feasible successor and output it into a CSV file.  It will also give a detailed
    breakdown of every network that was learned from a particular successor

    :param session: A subclass of the sessions.Session object that represents this particular script session (either
                SecureCRTSession or DirectSession)
    :type session: sessions.Session
    :param ask_vrf: A boolean that specifies if we should prompt for which VRF.  The default is true, but when this
        module is called from other scripts, we may want avoid prompting and supply the VRF with the "vrf" input.
    :type ask_vrf: bool
    :param vrf: The VRF that we should get the route table from.  This is used only when ask_vrf is False.
    :type vrf: str
    """
    # Get script object that owns this session, so we can check settings, get textfsm templates, etc
    script = session.script

    # Start session with device, i.e. modify term parameters for better interaction (assuming already connected)
    session.start_cisco_session()

    # Validate device is running a supported OS
    session.validate_os(["IOS", "NXOS"])

    # If we should prompt for a VRF, then do so.  Otherwise use the VRF passed into the function (if any)
    if ask_vrf:
        selected_vrf = script.prompt_window("Enter the VRF name. (Leave blank for default VRF, 'all' for all VRFs)")
        selected_vrf = selected_vrf.strip()
        logger.debug("Input VRF: {0}".format(selected_vrf))
    else:
        selected_vrf = vrf
        logger.debug("Received VRF: {0}".format(selected_vrf))

    # If we have a VRF, modify our commands and hostname to reflect it.  If not, pull the default route table.
    if selected_vrf:
        if session.os == "IOS":
            if selected_vrf == "all":
                send_cmd = "show ip eigrp vrf * topology"
            else:
                send_cmd = "show ip eigrp vrf {0} topology".format(selected_vrf)
        else:
            if selected_vrf == "*":
                send_cmd = "show ip eigrp topology vrf all"
            else:
                send_cmd = "show ip eigrp topology vrf {0}".format(selected_vrf)
    else:
        send_cmd = "show ip eigrp topology"

    logger.debug("Generated Command: {0}".format(send_cmd))

    raw_topo = session.get_command_output(send_cmd)

    if session.os == "IOS":
        template_file = script.get_template("cisco_ios_show_ip_eigrp_topology.template")
    else:
        template_file = script.get_template("cisco_nxos_show_ip_eigrp_topology.template")

    fsm_results = utilities.textfsm_parse_to_dict(raw_topo, template_file)

    detailed_results = process_topology(fsm_results)

    for process, nexthops in detailed_results.iteritems():
        nexthop_list = sorted(nexthops.keys(), key=utilities.human_sort_key)
        vrf = process[0]
        as_num = process[1]
        rid = process[2]

        output = [["Hostname:", session.hostname], ["VRF:", vrf], ["AS:", as_num], ["Router ID:", rid], ["", ""],
                  ["Nexthop", "Routes Learned"]]

        # output.append(["EIGRP TOPOLOGY SUMMARY", ""])
        for nexthop in nexthop_list:
            output.append([nexthop, len(nexthops[nexthop])])
        output.append(["", ""])

        # output.append(["DETAILED ROUTE LIST",""])
        for nexthop in nexthop_list:
            output.append(["Nexthop", "Routes"])
            sorted_networks = sorted(nexthops[nexthop], key=utilities.human_sort_key)
            for network in sorted_networks:
                output.append([nexthop, network])
            output.append(["",""])

        if vrf:
            output_filename = session.create_output_filename("{0}-eigrp-{1}-summary".format(vrf, as_num), ext=".csv")
        else:
            output_filename = session.create_output_filename("-eigrp-{0}-summary".format(as_num), ext=".csv")
        utilities.list_of_lists_to_csv(output, output_filename)

    # Return terminal parameters back to the original state.
    session.end_cisco_session()
コード例 #8
0
def script_main(session, ask_vrf=True, vrf=None):
    """
    | SINGLE device script
    | Author: Jamie Caesar
    | Email: [email protected]

    This script will grab the EIGRP topology from a Cisco IOS or NXOS device and export a summary of how many networks
    are learned from each successor/feasible successor and output it into a CSV file.  It will also give a detailed
    breakdown of every network that was learned from a particular successor

    :param session: A subclass of the sessions.Session object that represents this particular script session (either
                SecureCRTSession or DirectSession)
    :type session: sessions.Session
    :param ask_vrf: A boolean that specifies if we should prompt for which VRF.  The default is true, but when this
        module is called from other scripts, we may want avoid prompting and supply the VRF with the "vrf" input.
    :type ask_vrf: bool
    :param vrf: The VRF that we should get the route table from.  This is used only when ask_vrf is False.
    :type vrf: str
    """
    # Get script object that owns this session, so we can check settings, get textfsm templates, etc
    script = session.script

    # Start session with device, i.e. modify term parameters for better interaction (assuming already connected)
    session.start_cisco_session()

    # Validate device is running a supported OS
    session.validate_os(["IOS", "NXOS"])

    # If we should prompt for a VRF, then do so.  Otherwise use the VRF passed into the function (if any)
    if ask_vrf:
        selected_vrf = script.prompt_window(
            "Enter the VRF name. (Leave blank for default VRF, 'all' for all VRFs)"
        )
        selected_vrf = selected_vrf.strip()
        logger.debug("Input VRF: {0}".format(selected_vrf))
    else:
        selected_vrf = vrf
        logger.debug("Received VRF: {0}".format(selected_vrf))

    # If we have a VRF, modify our commands and hostname to reflect it.  If not, pull the default route table.
    if selected_vrf:
        if session.os == "IOS":
            if selected_vrf == "all":
                send_cmd = "show ip eigrp vrf * topology"
            else:
                send_cmd = "show ip eigrp vrf {0} topology".format(
                    selected_vrf)
        else:
            if selected_vrf == "*":
                send_cmd = "show ip eigrp topology vrf all"
            else:
                send_cmd = "show ip eigrp topology vrf {0}".format(
                    selected_vrf)
    else:
        send_cmd = "show ip eigrp topology"

    logger.debug("Generated Command: {0}".format(send_cmd))

    raw_topo = session.get_command_output(send_cmd)

    if session.os == "IOS":
        template_file = script.get_template(
            "cisco_ios_show_ip_eigrp_topology.template")
    else:
        template_file = script.get_template(
            "cisco_nxos_show_ip_eigrp_topology.template")

    fsm_results = utilities.textfsm_parse_to_dict(raw_topo, template_file)

    detailed_results = process_topology(fsm_results)

    for process, nexthops in detailed_results.iteritems():
        nexthop_list = sorted(nexthops.keys(), key=utilities.human_sort_key)
        vrf = process[0]
        as_num = process[1]
        rid = process[2]

        output = [["Hostname:", session.hostname], ["VRF:", vrf],
                  ["AS:", as_num], ["Router ID:", rid], ["", ""],
                  ["Nexthop", "Routes Learned"]]

        # output.append(["EIGRP TOPOLOGY SUMMARY", ""])
        for nexthop in nexthop_list:
            output.append([nexthop, len(nexthops[nexthop])])
        output.append(["", ""])

        # output.append(["DETAILED ROUTE LIST",""])
        for nexthop in nexthop_list:
            output.append(["Nexthop", "Routes"])
            sorted_networks = sorted(nexthops[nexthop],
                                     key=utilities.human_sort_key)
            for network in sorted_networks:
                output.append([nexthop, network])
            output.append(["", ""])

        if vrf:
            output_filename = session.create_output_filename(
                "{0}-eigrp-{1}-summary".format(vrf, as_num), ext=".csv")
        else:
            output_filename = session.create_output_filename(
                "-eigrp-{0}-summary".format(as_num), ext=".csv")
        utilities.list_of_lists_to_csv(output, output_filename)

    # Return terminal parameters back to the original state.
    session.end_cisco_session()
コード例 #9
0
def get_wlan_detail(session, to_cvs=False):
    """
    A function that captures the WLC AireOS wlan & remote-lan & guest-lan details and returns an output list

    :param session: The script object that represents this script being executed
    :type session: session.Session

    :return: A list of wlan details
    :rtype: list of lists
    """

    # Get the show wlan summary
    send_cmd = "show wlan summary"
    raw_wlan_summary = session.get_command_output(send_cmd)
    # Get the show remote-lan summary
    send_cmd = "show remote-lan summary"
    raw_rlan_summary = session.get_command_output(send_cmd)
    # Get the show guest-lan summary
    send_cmd = "show guest-lan summary"
    raw_glan_summary = session.get_command_output(send_cmd)

    template_file = session.script.get_template(
        "cisco_aireos_show_wlan_summary.template")
    wlan_summary_dict = utilities.textfsm_parse_to_dict(
        raw_wlan_summary, template_file)
    rlan_summary_dict = utilities.textfsm_parse_to_dict(
        raw_rlan_summary, template_file)
    glan_summary_dict = utilities.textfsm_parse_to_dict(
        raw_glan_summary, template_file)

    output_raw = ''
    output_list = []
    for wlan_entry in wlan_summary_dict:
        send_cmd = "show wlan " + format(wlan_entry["WLAN_Identifier"])
        output_list.append(session.get_command_output(send_cmd))

    raw_rlan_detail = ''
    for wlan_entry in rlan_summary_dict:
        send_cmd = "show remote-lan " + format(wlan_entry["WLAN_Identifier"])
        output_list.append(session.get_command_output(send_cmd))

    raw_glan_detail = ''
    for wlan_entry in glan_summary_dict:
        send_cmd = "show guest-lan " + format(wlan_entry["WLAN_Identifier"])
        output_list.append(session.get_command_output(send_cmd))

    output = []
    first = True
    for output_raw in output_list:
        # TextFSM template for parsing "show wlan <WLAN-ID>" output
        template_file = session.script.get_template(
            "cisco_aireos_show_wlan_detail.template")
        if first:
            output = utilities.textfsm_parse_to_list(output_raw,
                                                     template_file,
                                                     add_header=True)
            first = False
        else:
            output.append(
                utilities.textfsm_parse_to_list(output_raw,
                                                template_file,
                                                add_header=False)[0])

    if to_cvs:
        output_filename = session.create_output_filename("wlan-detail",
                                                         ext=".csv")
        utilities.list_of_lists_to_csv(output, output_filename)

    return output
コード例 #10
0
def get_ap_detail(session, to_cvs=False):
    """
    A function that captures the WLC AireOS ap details and returns an output list

    :param session: The script object that represents this script being executed
    :type session: session.Session

    :return: A list AP details
    :rtype: list of dicts
    """

    AP_Name_Key = "AP_Name"
    AP_80211A_suffix = "_80211_A"
    AP_80211B_suffix = "_80211_B"
    AP_Slot1_suffix = "_Slot_1"
    AP_Slot0_suffix = "_Slot_0"
    AP_CDP_mid = "_CDP_"

    output_raw = ''
    send_cmd = "show ap summary"
    output_raw += session.get_command_output(send_cmd)
    template_file = session.script.get_template("cisco_aireos_show_ap_summary.template")
    ap_summ_list_of_dict = utilities.textfsm_parse_to_dict(output_raw, template_file)

    def collect_it(in_list_of_dict, in_send_cmd, in_send_cmd_key, in_template_filename, in_key_suffix=''):
        # start with an empty output
        this_output_raw = ''
        # step across each dict entry in the list
        for this_dict_entry in in_list_of_dict:
            # send the in_send_cmd and add to it per dict entry aspect defined by the in_send_cmd_key
            this_send_cmd = in_send_cmd + format(this_dict_entry[in_send_cmd_key])
            # add this to the overall output to be processed
            this_output_raw += session.get_command_output(this_send_cmd)
        # use the template file provided & process the overall output
        processing_template_file = session.script.get_template(in_template_filename)
        processing_list_of_dict = utilities.textfsm_parse_to_dict(this_output_raw, processing_template_file)
        # start with an empty out_list_of_dict
        out_list_of_dict = []
        for this_dict_entry in processing_list_of_dict:
            # start with an empty change_dict_entry
            change_dict_entry = {}
            # step across each key
            for this_key in this_dict_entry.keys():
                # don't change the primary key name .. for the key:value entry
                if this_key == in_send_cmd_key: change_dict_entry[this_key] = this_dict_entry[this_key]
                # for the rest of the keys, add the suffix .. for the key:value entry
                else: change_dict_entry[this_key+in_key_suffix] = this_dict_entry[this_key]
            # now with key names changed, append the change_dict_entry
            out_list_of_dict.append(change_dict_entry)
        return out_list_of_dict

    def merge_it(in_base_list_of_dict, in_add_list_of_dict):
        # Now merge the various collected data
        out_list_of_dict = []
        # step across the original in_base_list_of_dict
        for base_dict_entry in in_base_list_of_dict:
            # start with what came from the base dict entry
            process_dict_entry = base_dict_entry
            # now for each of the sub command lists, find the matching dict entry and update merge it
            for in_add_entry in in_add_list_of_dict:
                if in_add_entry[AP_Name_Key] == base_dict_entry[AP_Name_Key]:
                    process_dict_entry.update(in_add_entry)
            out_list_of_dict.append(process_dict_entry)
        return(out_list_of_dict)

    dummy_dict_entry = {}
    dummy_dict_entry[AP_Name_Key] = ""
    dummy_list_of_dict = [dummy_dict_entry]

    ap_config_general_list_of_dict = collect_it(ap_summ_list_of_dict, "show ap config general ", AP_Name_Key, \
                                                "cisco_aireos_show_ap_config_general.template" )
    ap_list_of_dict = merge_it(ap_summ_list_of_dict, ap_config_general_list_of_dict)

    ap_config_slot_1_list_of_dict = collect_it(ap_summ_list_of_dict, "show ap config slot 1 ", AP_Name_Key, \
                                                "cisco_aireos_show_ap_config_slot.template", AP_Slot1_suffix)
    ap_list_of_dict = merge_it(ap_summ_list_of_dict, ap_config_slot_1_list_of_dict)

    ap_config_slot_0_list_of_dict = collect_it(ap_summ_list_of_dict, "show ap config slot 0 ", AP_Name_Key, \
                                                "cisco_aireos_show_ap_config_slot.template", AP_Slot0_suffix)
    ap_list_of_dict = merge_it(ap_summ_list_of_dict, ap_config_slot_0_list_of_dict)

    ap_TxPower_80211_A_list_of_dict = collect_it(dummy_list_of_dict, "show advanced 802.11a txpower", AP_Name_Key, \
                                                "cisco_aireos_show_advanced_txpower.template", AP_80211A_suffix)
    ap_list_of_dict = merge_it(ap_summ_list_of_dict, ap_TxPower_80211_A_list_of_dict)

    ap_TxPower_80211_B_list_of_dict = collect_it(dummy_list_of_dict, "show advanced 802.11b txpower", AP_Name_Key, \
                                                "cisco_aireos_show_advanced_txpower.template", AP_80211B_suffix)
    ap_list_of_dict = merge_it(ap_summ_list_of_dict, ap_TxPower_80211_B_list_of_dict)

    ap_cdp_list_of_dict = collect_it( dummy_list_of_dict, "show ap cdp neighbors detail all", AP_Name_Key, \
                                                "cisco_aireos_show_ap_cdp_neighbors_detail_all.template")
    ap_list_of_dict = merge_it(ap_summ_list_of_dict, ap_cdp_list_of_dict)

    output = ap_list_of_dict

    if to_cvs:
        ap_list_of_dict.sort(key=itemgetter(AP_Name_Key))

        # build an overall list of keys for using as header
        key_list = []
        # first collect up the unique keys by stepping across each dict
        for ap_dict_entry in ap_list_of_dict:
            for this_key in ap_dict_entry.keys():
                if this_key not in key_list: key_list.append(this_key)
        # next sort the resulting key list
        key_list.sort()
        # now resequence to group up the sorted sections
        key_list_new = [AP_Name_Key, "AP_Model", "AP_MAC_Enet", "AP_IP_Conf", "AP_IP_Addr"]
        key_list_slot_1 = []
        key_list_slot_A = []
        key_list_slot_0 = []
        key_list_slot_B = []
        key_list_cdp = []
        key_list_misc = []
        for this_key in key_list:
            if (this_key not in key_list_slot_1) and (AP_Slot1_suffix in this_key):
                key_list_slot_1.append(this_key)
            elif (this_key not in key_list_slot_A) and (AP_80211A_suffix in this_key):
                key_list_slot_A.append(this_key)
            elif (this_key not in key_list_slot_0) and (AP_Slot0_suffix in this_key):
                key_list_slot_0.append(this_key)
            elif (this_key not in key_list_slot_B) and (AP_80211B_suffix in this_key):
                key_list_slot_B.append(this_key)
            elif (this_key not in key_list_cdp) and (AP_CDP_mid in this_key):
                key_list_cdp.append(this_key)
            elif (this_key not in key_list_misc) and  (this_key not in key_list_new):
                key_list_misc.append(this_key)
        key_list = key_list_new + key_list_misc \
                    + key_list_slot_1 + key_list_slot_A \
                    + key_list_slot_0 + key_list_slot_B \
                    + key_list_cdp

        output_filename = session.create_output_filename("ap-summ", ext=".csv")
        utilities.list_of_dicts_to_csv(output, output_filename, key_list, add_header=True)

    return output