Now that we’ve gotten down the basics of how to connect to a device (see Part 2), let’s learn how to gather and store info for further processing, i.e. check or change current configurations on the WLC.
In Part 2, we connected to the WLC and printed out the WLANs configured. Now, let’s learn how to send commands to the WLC and validate the results against a “standard” set of configurations.
Let’s pretend we want to validate that only the single local admin username is configured on the WLC and no others. Doing this manually would be something like this:
- Connect to the device via SSH
- Authenticate with some sort of credentials, local or TACACS.
- Send the command ‘
show mgmtuser‘ to list the local admin accounts configured
We’ve already established connecting and authenticating to the device, so let’s look at how to send the ‘show mgmtuser‘ command, save and parse the output for a list of admin usernames on the device.
Using what we know from Part 2, let’s set up a basic script that will:
- Import the
getpassandnetmikomodules - Prompt the user for a username and password
- Connect to the device
- Send the
show mgmtusercommand to the device - Print the output
import getpass # Import the getpass module to securely input the password
from netmiko import ConnectHandler # Import the ConnectHandler factory function that will handle the SSH connection to the device
username = input('\nEnter your username: ')
password = getpass.getpass(prompt='Enter your password: ') # Prompt the user to enter their password securely
device = {
'device_type': 'cisco_wlc', # Set device type as 'cisco_wlc', which for Netmiko is AireOS-based WLCs (not IOS-XE)
'ip': '192.168.10.2', # Your device's IP address
'username': username, # Username variable that was set earlier
'password': password # Password variable that was set earlier
}
net_connect = ConnectHandler(**device) # Establish an SSH connection to the device using the device dictionary
# Send some commands to the device and print the output:
output = net_connect.send_command(command_string='show mgmtuser')
print(output)
net_connect.disconnect() # Disconnect from the device
Running this gives us the output:
taylor.thurston@UA-MAC wirelesstot % /usr/local/bin/python3 "/Users/taylor.thurston/Library/Python Automation for AireOS/part_3.py"
(LAB5508-WC01) >show mgmtuser
User Name Permissions Description Password Strength Telnet Capable
----------------------- ------------ --------------------- ------------------ ----------
admin read-write Strong Yes
taylor read-write Strong Yes
This is great, it gave us the exact output we’d see if we issues this command manually. I believe it’s easier to parse for the info I want if I have as little info to parse as possible. So I’m going to add a parameter while sending the command to clean up the output a bit:
output = net_connect.send_command(command_string='show mgmtuser', strip_prompt=True)
It’s probably not hard to guess what this will do…
taylor.thurston@UA-MAC wirelesstot % /usr/local/bin/python3 "/Users/taylor.thurston/Library/Python Automation for AireOS/part_3.py"
User Name Permissions Description Password Strength Telnet Capable
----------------------- ------------ --------------------- ------------------ ----------
admin read-write Strong Yes
taylor read-write Strong Yes
No more prompt! Now it’s a little easier for us to parse, so let’s create a function (with ‘def {function_name}’) that will take the ‘output’ results and return us a list of usernames configured on this WLC.
The following function, called ‘parse_mgmtuser_output’, takes one argument, named ‘output’ and defined as a string. NOTE: the ‘output’ argument is simply a variable placeholder, it can be any string, it doesn’t have to be a variable named ‘output’.
def parse_mgmtuser_output(output: str):
lines = output.strip().split('\n')
data_lines = lines[2:]
usernames = [line.split()[0] for line in data_lines]
return usernames
Let’s break it down line by line:
def parse_mgmtuser_output(output):
# - This line defines a function named parse_mgmtuser_output that takes one parameter output
lines = output.strip().split('\n')
# - 'output.strip()' removes any leading or trailing whitespace from the input string
# - '.split('\n')' splits the string into a list of strings, using newline characters as separators
# - The result is stored in the 'lines' variable, which is now a list where each element is a line from the original output
data_lines = lines[2:]
# - This creates a new list data_lines that contains all elements of lines starting from index 2
# - It effectively skips the first two lines (which are the command prompt and the header), keeping only the lines with actual user data
usernames = [line.split()[0] for line in data_lines]
# - This is a list comprehension that creates a new list usernames
# - For each line in data_lines:
# - 'line.split()' splits the line into words (by default, it splits on whitespace)
# - '[0]' takes the first word, which is the username
# - The result is a list containing only the usernames
return usernames
# - This line returns the usernames list, which is the final output of the function.
Let’s add this function to our script and test it out. The updated script now:
import getpass # Import the getpass module to securely input the password
from netmiko import ConnectHandler # Import the ConnectHandler factory function that will handle the SSH connection to the device
def parse_mgmtuser_output(output: str): # Define a function that will parse the output of the 'show mgmtuser' command
lines = output.strip().split('\n') # Split the output into lines at each newline character and remove leading/trailing whitespace
data_lines = lines[2:] # Skip the first two lines of the output, which are headers
usernames = [line.split()[0] for line in data_lines] # Split each line into words and take the first word, which is the username
return usernames # Return the list of usernames
username = input('\nEnter your username: ')
password = getpass.getpass(prompt='Enter your password: ') # Prompt the user to enter their password securely
device = {
'device_type': 'cisco_wlc', # Set device type as 'cisco_wlc', which for Netmiko is AireOS-based WLCs (not IOS-XE)
'ip': '192.168.10.2', # Your device's IP address
'username': username, # Username variable that was set earlier
'password': password # Password variable that was set earlier
}
net_connect = ConnectHandler(**device) # Establish an SSH connection to the device using the device dictionary
output = net_connect.send_command(command_string='show mgmtuser', strip_prompt=True) # Send the 'show mgmtuser' command to the device, remove the prompt, and store the output in a variable
usernames = parse_mgmtuser_output(output) # Parse the output of the 'show mgmtuser' command using the function defined earlier
print('Usernames found:')
print(usernames) # Print the list of usernames
net_connect.disconnect() # Disconnect from the device
Running the updated script now returns:
taylor.thurston@UA-MAC wirelesstot % /usr/local/bin/python3 "/Users/taylor.thurston/Library/Python Automation for AireOS/part_3.py"
Usernames found:
['admin', 'taylor']
Victory! Exactly what we wanted! But I’m going to make the output a little prettier by using f-strings:
print(f'\nUsernames found:\n{usernames}\n') # Print the list of usernames
Now the output has a blank line before and after printing the list of userrnames and is easier for us humans to process:
taylor.thurston@UA-MAC wirelesstot % /usr/local/bin/python3 "/Users/taylor.thurston/Library/Python Automation for AireOS/part_3.py"
Usernames found:
['admin', 'taylor']
And if we wanted to get REALLY fancy with it, we could iterate through the list:
print(f'\nUsernames found on {device["ip"]}:')
for username in usernames: # Iterate over the list of usernames
print(f' - {username}') # Print each username
Which now gives us on output that looks like this:
taylor.thurston@UA-MAC wirelesstot % /usr/local/bin/python3 "/Users/taylor.thurston/Library/Python Automation for AireOS/part_3.py"
Usernames found on 192.168.10.2:
- admin
- taylor
With this list of usernames, we can now decide which usernames to keep or not. In my example, we’re going to use a loop to delete any usernames that aren’t ‘taylor’:
for username in usernames: # Iterate over the list of usernames
if username != 'taylor': # If the username is not equal to 'taylor' ('!=' means not equal to, '==' means equal to)
print(f'\nDeleting user: {username}') # Print a message indicating that the user is being deleted
net_connect.send_command_timing(command_string=f'config mgmtuser delete {username}') # Send the 'delete mgmtuser' command to delete the user
net_connect.send_command_timing(command_string='y', delay_factor=2) # Send 'y' to confirm the deletion
else:
print(f'Skipping user: {username}') # Print a message indicating that the user is being skipped
NOTE: Deleting usernames requires confirmation in AireOS, which is why I have the second line that sends a ‘y’ to the device with a slight delay factor to make sure the full output is present before I send my answer.
Running this yields:
Running this yields:
taylor.thurston@UA-MAC wirelesstot % /usr/local/bin/python3 "/Users/taylor.thurston/Library/Python Automation for AireOS/part_3.py"
Usernames found on 192.168.10.2:
- admin
- taylor
Deleting user: admin
Skipping user: taylor
Checking configured management users...
Usernames found on 192.168.10.2:
- taylor
We’ve now written a script that can check for the local management users we want and delete the rest! We’re done! …?
The full script is now:
import getpass # Import the getpass module to securely input the password
from netmiko import ConnectHandler # Import the ConnectHandler factory function that will handle the SSH connection to the device
def parse_mgmtuser_output(output: str): # Define a function that will parse the output of the 'show mgmtuser' command
lines = output.strip().split('\n') # Split the output into lines at each newline character and remove leading/trailing whitespace
data_lines = lines[2:] # Skip the first two lines of the output, which are headers
usernames = [line.split()[0] for line in data_lines] # Split each line into words and take the first word, which is the username
return usernames # Return the list of usernames
username = input('\nEnter your username: ') ## Prompt the user for a username
password = getpass.getpass(prompt='Enter your password: ') # Prompt the user to enter their password securely
device = {
'device_type': 'cisco_wlc', # Set device type as 'cisco_wlc', which for Netmiko is AireOS-based WLCs (not IOS-XE)
'ip': '192.168.10.2', # Your device's IP address
'username': username, # Username variable that was set earlier
'password': password # Password variable that was set earlier
}
net_connect = ConnectHandler(**device) # Establish an SSH connection to the device using the device dictionary
output = net_connect.send_command(command_string='show mgmtuser', strip_prompt=True) # Send the 'show mgmtuser' command to the device, remove the prompt, and store the output in a variable
usernames = parse_mgmtuser_output(output) # Parse the output of the 'show mgmtuser' command using the function defined earlier
print(f'\nUsernames found on {device["ip"]}:')
for username in usernames: # Iterate over the list of usernames
print(f' - {username}') # Print each username
for username in usernames: # Iterate over the list of usernames
if username != 'taylor': # If the username is not equal to 'taylor' ('!=' means not equal to, '==' means equal to)
print(f'\nDeleting user: {username}') # Print a message indicating that the user is being deleted
net_connect.send_command_timing(command_string=f'config mgmtuser delete {username}') # Send the 'delete mgmtuser' command to delete the user
net_connect.send_command_timing(command_string='y', delay_factor=2) # Send 'y' to confirm the deletion
else:
print(f'Skipping user: {username}') # Print a message indicating that the user is being skipped
# Check the configured management users again for verification:
print('\nChecking configured management users...')
output = net_connect.send_command(command_string='show mgmtuser', strip_prompt=True) # Send the 'show mgmtuser' command to the device, remove the prompt, and store the output in a variable
usernames = parse_mgmtuser_output(output) # Parse the output of the 'show mgmtuser' command using the function defined earlier
print(f'\nUsernames found on {device["ip"]}:')
for username in usernames: # Iterate over the list of usernames
print(f' - {username}') # Print each username
net_connect.disconnect() # Disconnect from the device
Advanced updates
This script works just fine, but to put this in-line with Python best practices, we’ll updated it to look like this (I’ll go over this in more detail in another post in the future…I’ve also made a few changes, list the usernames we want to keep are a list, so add more if you’d like):
import getpass
from netmiko import ConnectHandler, NetmikoTimeoutException, NetmikoAuthenticationException
def parse_mgmtuser_output(output: str): # Define a function that will parse the output of the 'show mgmtuser' command
lines = output.strip().split('\n') # Split the output into lines at each newline character and remove leading/trailing whitespace
data_lines = lines[2:] # Skip the first two lines of the output, which are headers
usernames = [line.split()[0] for line in data_lines] # Split each line into words and take the first word, which is the username
return usernames # Return the list of usernames
EXCLUDED_USERNAMES = ['taylor', 'superuser'] # List of usernames to exclude from deletion
def main():
username = input('\nEnter your username: ')
password = getpass.getpass(prompt='Enter your password: ')
device = {
'device_type': 'cisco_wlc', # Set device type as 'cisco_wlc', which for Netmiko is AireOS-based WLCs (not IOS-XE)
'ip': '10.108.54.70', # Your device's IP address
'username': username, # Username variable that was set earlier
'password': password # Password variable that was set earlier
}
try: # Try to connect to the device
net_connect = ConnectHandler(**device) # Establish an SSH connection to the device using the device dictionary:
output = net_connect.send_command(command_string='show mgmtuser', strip_prompt=True) # Send the 'show mgmtuser' command to the device, remove the prompt, and store the output in a variable
usernames = parse_mgmtuser_output(output) # Parse the output of the 'show mgmtuser' command using the function defined earlier
print(f'\nUsernames found on {device["ip"]}:')
for username in usernames: # Iterate over the list of usernames
print(f' - {username}') # Print each username
for username in usernames: # Iterate over the list of usernames
if username not in EXCLUDED_USERNAMES: # If the username is not in the list of excluded usernames
print(f'\nDeleting user: {username}') # Print a message indicating that the user is being deleted
net_connect.send_command_timing(command_string=f'config mgmtuser delete {username}') # Send the 'delete mgmtuser' command to delete the user
net_connect.send_command_timing(command_string='y', delay_factor=2) # Send 'y' to confirm the deletion
else:
print(f'Skipping user: {username}') # Print a message indicating that the user is being skipped
# Check the configured management users again for verification:
print('\nChecking configured management users...')
output = net_connect.send_command(command_string='show mgmtuser', strip_prompt=True) # Send the 'show mgmtuser' command to the device, remove the prompt, and store the output in a variable
usernames = parse_mgmtuser_output(output) # Parse the output of the 'show mgmtuser' command using the function defined earlier
print(f'\nUsernames found on {device["ip"]}:')
for username in usernames: # Iterate over the list of usernames
print(f' - {username}') # Print each username
net_connect.disconnect() # Disconnect from the device
except (NetmikoTimeoutException, NetmikoAuthenticationException) as e: # Handle exceptions for connection timeouts and authentication failures
print(f'Error connecting to the device: {e}')
if __name__ == '__main__': # If the script is run directly (not imported as a module)
main() # Call the main function
Output:
taylor.thurston@UA-MAC wirelesstot % /usr/local/bin/python3 "/Users/taylor.thurston/Library/Python Automation for AireOS/part_3.py"
Usernames found on 192.168.10.2:
- admin
- dontmindme
- superuser
- taylor
Deleting user: admin
Deleting user: dontmindme
Skipping user: superuser
Skipping user: taylor
Checking configured management users...
Usernames found on 192.168.10.2:
- superuser
- taylor

Leave a comment