Skip to main content

 

Cisco Meraki Documentation

Scripts for Bulk Onboarding of Axis devices

Bulk Claiming with Automation Scripts

These scripts are for guidance only and will not be updated for different API versions and firmware. Please check compatibility and functionality in a test environment. 

For large deployments with multiple devices, manual onboarding can be time-consuming. The following automation scripts help you collect device information (Serial Number and OAK) that is required for bulk claiming in the Meraki Dashboard as well as virtually enable One-Click Connect to cloud required for devices to fetch configuration.  Please refer to this document for requirements and variables when using the scripts.

get_axis_device_info_interactive.py

#!/usr/bin/env python3
"""
Axis Device Information Retrieval Script - Interactive Mode


This script prompts for camera information interactively and queries each device to retrieve:
- OAK (Owner Authentication Key)
- Serial Number
- Model information
- Firmware Version


Requirements:
    pip install requests


Usage:
    python get_axis_device_info_interactive.py


The script will prompt you for:
- IP address(es)
- Username
- Password
- Optional CSV export
"""


import csv
import sys
import getpass
from datetime import datetime
from typing import Dict, Optional, List
import json


# Third-party library for HTTP requests
# Install with: pip install requests
try:
    import requests
    from requests.auth import HTTPDigestAuth
except ImportError:
    print("ERROR: 'requests' library not found.")
    print("Please install it using: pip install requests")
    sys.exit(1)



# ============================================================================
# CONFIGURATION
# ============================================================================


# Timeout for HTTP requests in seconds
REQUEST_TIMEOUT = 10


# Color codes for terminal output (ANSI escape codes)
class Colors:
    """ANSI color codes for colored terminal output"""
    CYAN = '\033[96m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    GRAY = '\033[90m'
    RESET = '\033[0m'
    BOLD = '\033[1m'



# ============================================================================
# API QUERY FUNCTIONS
# ============================================================================


def get_axis_oak(ip_address: str, username: str, password: str) -> Optional[str]:
    """
    Retrieve OAK (Owner Authentication Key) from Axis device.


    Args:
        ip_address: IP address of the Axis camera
        username: Username for authentication
        password: Password for authentication


    Returns:
        OAK string if successful, None if failed
    """
    try:
        # Construct the OAK API endpoint URL
        url = f"http://{ip_address}/axis-cgi/oak.cgi"


        # Prepare JSON-RPC request body
        # The Axis OAK API uses JSON-RPC format with method "getOak"
        payload = {
            "apiVersion": "1.0",
            "method": "getOak"
        }


        # Set HTTP headers for JSON content
        headers = {
            "Content-Type": "application/json"
        }


        # Make POST request with HTTP Digest Authentication
        # Axis cameras use Digest Auth (more secure than Basic Auth)
        response = requests.post(
            url,
            json=payload,
            headers=headers,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )


        # Check if request was successful (HTTP 200)
        response.raise_for_status()


        # Parse JSON response
        data = response.json()


        # Extract OAK from response (try multiple possible locations)
        if "data" in data and "oak" in data["data"]:
            return data["data"]["oak"]
        elif "oak" in data:
            return data["oak"]
        else:
            print(f"  Warning: OAK not found in response from {ip_address}")
            return None


    except requests.exceptions.Timeout:
        print(f"  Warning: Request timeout for OAK from {ip_address}")
        return None
    except requests.exceptions.RequestException as e:
        print(f"  Warning: Failed to retrieve OAK from {ip_address}: {e}")
        return None
    except json.JSONDecodeError:
        print(f"  Warning: Invalid JSON response for OAK from {ip_address}")
        return None



def get_axis_serial_number(ip_address: str, username: str, password: str) -> Optional[str]:
    """
    Retrieve Serial Number from Axis device.


    Args:
        ip_address: IP address of the Axis camera
        username: Username for authentication
        password: Password for authentication


    Returns:
        Serial number string if successful, None if failed
    """
    try:
        # Try the newer basicdeviceinfo.cgi API first
        url = f"http://{ip_address}/axis-cgi/basicdeviceinfo.cgi?apiversion=1.0"


        # Make GET request with HTTP Digest Authentication
        response = requests.get(
            url,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )


        response.raise_for_status()
        content = response.text


        # Try to parse as JSON first (newer firmware)
        try:
            data = json.loads(content)
            if "SerialNumber" in data:
                return data["SerialNumber"]
        except json.JSONDecodeError:
            pass


        # Try to find SerialNumber in text/XML format (older firmware)
        import re


        # Look for JSON format: "SerialNumber": "ABC123"
        match = re.search(r'"SerialNumber":\s*"([^"]+)"', content)
        if match:
            return match.group(1)


        # Look for XML format: <SerialNumber>ABC123</SerialNumber>
        match = re.search(r'<SerialNumber>([^<]+)</SerialNumber>', content)
        if match:
            return match.group(1)


        # Fallback: Try the older param.cgi API
        url = f"http://{ip_address}/axis-cgi/param.cgi?action=list&group=Properties.System.SerialNumber"
        response = requests.get(
            url,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )
        response.raise_for_status()


        # Parse param.cgi format: SerialNumber=ABC123
        match = re.search(r'SerialNumber=([^\s\r\n]+)', response.text)
        if match:
            return match.group(1)


        return None


    except requests.exceptions.RequestException as e:
        print(f"  Warning: Failed to retrieve Serial Number from {ip_address}: {e}")
        return None



def get_axis_model(ip_address: str, username: str, password: str) -> Optional[str]:
    """
    Retrieve Model/Product Number from Axis device.


    Args:
        ip_address: IP address of the Axis camera
        username: Username for authentication
        password: Password for authentication


    Returns:
        Model string if successful, None if failed
    """
    try:
        # Query product number from param.cgi
        url = f"http://{ip_address}/axis-cgi/param.cgi?action=list&group=Properties.System.ProdNbr"


        response = requests.get(
            url,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )


        response.raise_for_status()


        # Parse param.cgi format: ProdNbr=M3077-PLVE
        import re
        match = re.search(r'ProdNbr=([^\s\r\n]+)', response.text)
        if match:
            return match.group(1)


        return None


    except requests.exceptions.RequestException:
        # Model is optional, fail silently
        return None



def get_axis_firmware_version(ip_address: str, username: str, password: str) -> Optional[str]:
    """
    Retrieve Firmware Version from Axis device.


    Args:
        ip_address: IP address of the Axis camera
        username: Username for authentication
        password: Password for authentication


    Returns:
        Firmware version string if successful, None if failed
    """
    try:
        # Try the newer basicdeviceinfo.cgi API first
        url = f"http://{ip_address}/axis-cgi/basicdeviceinfo.cgi?apiversion=1.0"


        response = requests.get(
            url,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )


        response.raise_for_status()
        content = response.text


        # Try to parse as JSON first (newer firmware)
        try:
            data = json.loads(content)
            if "Version" in data:
                return data["Version"]
        except json.JSONDecodeError:
            pass


        # Try to find Version in text/XML format (older firmware)
        import re


        # Look for JSON format: "Version": "9.80.3"
        match = re.search(r'"Version":\s*"([^"]+)"', content)
        if match:
            return match.group(1)


        # Look for XML format: <Version>9.80.3</Version>
        match = re.search(r'<Version>([^<]+)</Version>', content)
        if match:
            return match.group(1)


        # Fallback: Try the older param.cgi API
        url = f"http://{ip_address}/axis-cgi/param.cgi?action=list&group=Properties.Firmware.Version"
        response = requests.get(
            url,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )
        response.raise_for_status()


        # Parse param.cgi format: Version=9.80.3
        match = re.search(r'Version=([^\s\r\n]+)', response.text)
        if match:
            return match.group(1)


        return None


    except requests.exceptions.RequestException:
        # Firmware version is optional, fail silently
        return None



# ============================================================================
# MAIN QUERY FUNCTION
# ============================================================================


def query_device(ip_address: str, username: str, password: str, index: int, total: int) -> Dict:
    """
    Query a single Axis device for all information.


    Args:
        ip_address: IP address of the device
        username: Username for authentication
        password: Password for authentication
        index: Current device number (for progress display)
        total: Total number of devices


    Returns:
        Dictionary containing all retrieved information
    """
    # Print progress header
    print(f"{Colors.YELLOW}[{index}/{total}] Querying: {ip_address}{Colors.RESET}")


    # Get OAK
    print(f"  Retrieving OAK...", end=" ", flush=True)
    oak = get_axis_oak(ip_address, username, password)
    if oak:
        print(f"{Colors.GREEN}OK{Colors.RESET}")
    else:
        print(f"{Colors.RED}FAILED{Colors.RESET}")


    # Get Serial Number
    print(f"  Retrieving Serial Number...", end=" ", flush=True)
    serial = get_axis_serial_number(ip_address, username, password)
    if serial:
        print(f"{Colors.GREEN}OK{Colors.RESET}")
    else:
        print(f"{Colors.RED}FAILED{Colors.RESET}")


    # Get Model
    print(f"  Retrieving Model...", end=" ", flush=True)
    model = get_axis_model(ip_address, username, password)
    if model:
        print(f"{Colors.GREEN}OK{Colors.RESET}")
    else:
        print(f"{Colors.GRAY}N/A{Colors.RESET}")


    # Get Firmware Version
    print(f"  Retrieving Firmware Version...", end=" ", flush=True)
    firmware = get_axis_firmware_version(ip_address, username, password)
    if firmware:
        print(f"{Colors.GREEN}OK{Colors.RESET}")
    else:
        print(f"{Colors.GRAY}N/A{Colors.RESET}")


    print()  # Empty line for spacing


    # Return result dictionary
    return {
        "IPAddress": ip_address,
        "SerialNumber": serial or "",
        "FirmwareVersion": firmware or "",
        "OAK": oak or "",
        "Model": model or "",
        "Status": "Success" if (oak or serial) else "Failed",
        "Timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }



# ============================================================================
# INPUT PARSING FUNCTIONS
# ============================================================================


def parse_ip_input(ip_input: str) -> List[str]:
    """
    Parse IP address input and return list of IP addresses.
    Supports:
    - Single IP: 192.168.1.100
    - Multiple IPs (comma-separated): 192.168.1.100, 192.168.1.101
    - IP range: 192.168.1.100-105


    Args:
        ip_input: User input string containing IP address(es)


    Returns:
        List of IP addresses
    """
    import re


    ip_addresses = []


    # Check for IP range notation (e.g., 192.168.1.100-105)
    range_match = re.match(r'(\d+\.\d+\.\d+\.)(\d+)-(\d+)', ip_input.strip())
    if range_match:
        # Extract base IP and start/end range
        base_ip = range_match.group(1)
        start_range = int(range_match.group(2))
        end_range = int(range_match.group(3))


        # Generate all IPs in the range
        for i in range(start_range, end_range + 1):
            ip_addresses.append(f"{base_ip}{i}")


        print(f"{Colors.GREEN}Expanded range to {len(ip_addresses)} addresses{Colors.RESET}")
    else:
        # Split by comma for multiple IPs
        ip_addresses = [ip.strip() for ip in ip_input.split(',')]


    return ip_addresses



# ============================================================================
# CSV OUTPUT FUNCTION
# ============================================================================


def write_output_csv(file_path: str, results: List[Dict]) -> None:
    """
    Write results to output CSV file.


    Args:
        file_path: Path to output CSV file
        results: List of result dictionaries
    """
    # Define output CSV columns in the order we want them
    fieldnames = [
        "IPAddress",
        "SerialNumber",
        "FirmwareVersion",
        "OAK",
        "Model",
        "Status",
        "Timestamp"
    ]


    # Write CSV file
    with open(file_path, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)


        # Write header row
        writer.writeheader()


        # Write all result rows
        writer.writerows(results)



# ============================================================================
# MAIN PROGRAM
# ============================================================================


def main():
    """Main program entry point."""


    # Print banner
    print()
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print(f"{Colors.CYAN}  Axis Device Info - Interactive Mode{Colors.RESET}")
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print()


    # Prompt for IP addresses with examples
    print(f"{Colors.YELLOW}Enter camera IP address(es):{Colors.RESET}")
    print(f"{Colors.GRAY}  - Single IP: 192.168.1.100{Colors.RESET}")
    print(f"{Colors.GRAY}  - Multiple IPs (comma separated): 192.168.1.100, 192.168.1.101{Colors.RESET}")
    print(f"{Colors.GRAY}  - IP range: 192.168.1.100-105{Colors.RESET}")
    print()
    ip_input = input("IP Address(es): ").strip()


    # Parse IP addresses
    ip_addresses = parse_ip_input(ip_input)


    if not ip_addresses:
        print(f"{Colors.RED}ERROR: No valid IP addresses provided{Colors.RESET}")
        sys.exit(1)


    print()


    # Prompt for credentials
    print(f"{Colors.YELLOW}Enter camera credentials:{Colors.RESET}")
    username = input("Username: ").strip()


    # Use getpass for secure password input (hidden)
    password = getpass.getpass("Password: ")


    print()


    # Ask about CSV export
    export_choice = input("Export results to CSV? (y/n): ").strip().lower()
    csv_path = None
    if export_choice == 'y':
        default_path = f"axis-devices-{datetime.now().strftime('%Y%m%d-%H%M%S')}.csv"
        csv_input = input(f"CSV filename (press Enter for '{default_path}'): ").strip()
        csv_path = csv_input if csv_input else default_path


    print()
    print(f"{Colors.CYAN}Starting device queries...{Colors.RESET}")
    print()


    # Query each device
    results = []
    for index, ip in enumerate(ip_addresses, start=1):
        result = query_device(
            ip_address=ip,
            username=username,
            password=password,
            index=index,
            total=len(ip_addresses)
        )
        results.append(result)


    # Display results summary table
    print()
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print(f"{Colors.CYAN}              RESULTS{Colors.RESET}")
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print()


    # Print results in a formatted table
    print(f"{'IP Address':<16} {'Serial':<16} {'Firmware':<12} {'Status':<10}")
    print("-" * 60)
    for result in results:
        status_color = Colors.GREEN if result['Status'] == 'Success' else Colors.RED
        print(f"{result['IPAddress']:<16} {result['SerialNumber']:<16} "
              f"{result['FirmwareVersion']:<12} {status_color}{result['Status']:<10}{Colors.RESET}")


    # Export to CSV if requested
    if csv_path:
        try:
            write_output_csv(csv_path, results)
            print()
            print(f"{Colors.GREEN}Results exported to: {csv_path}{Colors.RESET}")
        except Exception as e:
            print()
            print(f"{Colors.RED}ERROR: Failed to export CSV: {e}{Colors.RESET}")


    # Print summary statistics
    print()
    success_count = sum(1 for r in results if r['Status'] == 'Success')
    print(f"{Colors.CYAN}Summary: {success_count} of {len(results)} devices queried successfully{Colors.RESET}")
    print()



# Entry point - only run main() if script is executed directly (not imported)
if __name__ == "__main__":
    main()

get_axis_device_info_csv.py

#!/usr/bin/env python3
"""
Axis Device Information Retrieval Script - CSV Input Mode


This script reads camera information from a CSV file and queries each device to retrieve:
- OAK (Owner Authentication Key)
- Serial Number
- Model information
- Firmware Version


Requirements:
    pip install requests


CSV Input Format:
    IPAddress,Username,Password
    192.168.1.100,root,password123
    192.168.1.101,admin,admin456


Usage:
    python get_axis_device_info_csv.py
    python get_axis_device_info_csv.py --input cameras.csv --output results.csv
"""


import csv
import sys
import argparse
from datetime import datetime
from typing import Dict, Optional, List
import json


# Third-party library for HTTP requests
# Install with: pip install requests
try:
    import requests
    from requests.auth import HTTPDigestAuth
except ImportError:
    print("ERROR: 'requests' library not found.")
    print("Please install it using: pip install requests")
    sys.exit(1)



# ============================================================================
# CONFIGURATION
# ============================================================================


# Timeout for HTTP requests in seconds
REQUEST_TIMEOUT = 10


# Color codes for terminal output (ANSI escape codes)
class Colors:
    """ANSI color codes for colored terminal output"""
    CYAN = '\033[96m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    GRAY = '\033[90m'
    RESET = '\033[0m'
    BOLD = '\033[1m'



# ============================================================================
# API QUERY FUNCTIONS
# ============================================================================


def get_axis_oak(ip_address: str, username: str, password: str) -> Optional[str]:
    """
    Retrieve OAK (Owner Authentication Key) from Axis device.


    Args:
        ip_address: IP address of the Axis camera
        username: Username for authentication
        password: Password for authentication


    Returns:
        OAK string if successful, None if failed
    """
    try:
        # Construct the OAK API endpoint URL
        url = f"http://{ip_address}/axis-cgi/oak.cgi"


        # Prepare JSON-RPC request body
        # The Axis OAK API uses JSON-RPC format with method "getOak"
        payload = {
            "apiVersion": "1.0",
            "method": "getOak"
        }


        # Set HTTP headers for JSON content
        headers = {
            "Content-Type": "application/json"
        }


        # Make POST request with HTTP Digest Authentication
        # Axis cameras use Digest Auth (more secure than Basic Auth)
        response = requests.post(
            url,
            json=payload,
            headers=headers,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )


        # Check if request was successful (HTTP 200)
        response.raise_for_status()


        # Parse JSON response
        data = response.json()


        # Extract OAK from response (try multiple possible locations)
        if "data" in data and "oak" in data["data"]:
            return data["data"]["oak"]
        elif "oak" in data:
            return data["oak"]
        else:
            print(f"  Warning: OAK not found in response from {ip_address}")
            return None


    except requests.exceptions.Timeout:
        print(f"  Warning: Request timeout for OAK from {ip_address}")
        return None
    except requests.exceptions.RequestException as e:
        print(f"  Warning: Failed to retrieve OAK from {ip_address}: {e}")
        return None
    except json.JSONDecodeError:
        print(f"  Warning: Invalid JSON response for OAK from {ip_address}")
        return None



def get_axis_serial_number(ip_address: str, username: str, password: str) -> Optional[str]:
    """
    Retrieve Serial Number from Axis device.


    Args:
        ip_address: IP address of the Axis camera
        username: Username for authentication
        password: Password for authentication


    Returns:
        Serial number string if successful, None if failed
    """
    try:
        # Try the newer basicdeviceinfo.cgi API first
        url = f"http://{ip_address}/axis-cgi/basicdeviceinfo.cgi?apiversion=1.0"


        # Make GET request with HTTP Digest Authentication
        response = requests.get(
            url,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )


        response.raise_for_status()
        content = response.text


        # Try to parse as JSON first (newer firmware)
        try:
            data = json.loads(content)
            if "SerialNumber" in data:
                return data["SerialNumber"]
        except json.JSONDecodeError:
            pass


        # Try to find SerialNumber in text/XML format (older firmware)
        import re


        # Look for JSON format: "SerialNumber": "ABC123"
        match = re.search(r'"SerialNumber":\s*"([^"]+)"', content)
        if match:
            return match.group(1)


        # Look for XML format: <SerialNumber>ABC123</SerialNumber>
        match = re.search(r'<SerialNumber>([^<]+)</SerialNumber>', content)
        if match:
            return match.group(1)


        # Fallback: Try the older param.cgi API
        url = f"http://{ip_address}/axis-cgi/param.cgi?action=list&group=Properties.System.SerialNumber"
        response = requests.get(
            url,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )
        response.raise_for_status()


        # Parse param.cgi format: SerialNumber=ABC123
        match = re.search(r'SerialNumber=([^\s\r\n]+)', response.text)
        if match:
            return match.group(1)


        return None


    except requests.exceptions.RequestException as e:
        print(f"  Warning: Failed to retrieve Serial Number from {ip_address}: {e}")
        return None



def get_axis_model(ip_address: str, username: str, password: str) -> Optional[str]:
    """
    Retrieve Model/Product Number from Axis device.


    Args:
        ip_address: IP address of the Axis camera
        username: Username for authentication
        password: Password for authentication


    Returns:
        Model string if successful, None if failed
    """
    try:
        # Query product number from param.cgi
        url = f"http://{ip_address}/axis-cgi/param.cgi?action=list&group=Properties.System.ProdNbr"


        response = requests.get(
            url,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )


        response.raise_for_status()


        # Parse param.cgi format: ProdNbr=M3077-PLVE
        import re
        match = re.search(r'ProdNbr=([^\s\r\n]+)', response.text)
        if match:
            return match.group(1)


        return None


    except requests.exceptions.RequestException:
        # Model is optional, fail silently
        return None



def get_axis_firmware_version(ip_address: str, username: str, password: str) -> Optional[str]:
    """
    Retrieve Firmware Version from Axis device.


    Args:
        ip_address: IP address of the Axis camera
        username: Username for authentication
        password: Password for authentication


    Returns:
        Firmware version string if successful, None if failed
    """
    try:
        # Try the newer basicdeviceinfo.cgi API first
        url = f"http://{ip_address}/axis-cgi/basicdeviceinfo.cgi?apiversion=1.0"


        response = requests.get(
            url,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )


        response.raise_for_status()
        content = response.text


        # Try to parse as JSON first (newer firmware)
        try:
            data = json.loads(content)
            if "Version" in data:
                return data["Version"]
        except json.JSONDecodeError:
            pass


        # Try to find Version in text/XML format (older firmware)
        import re


        # Look for JSON format: "Version": "9.80.3"
        match = re.search(r'"Version":\s*"([^"]+)"', content)
        if match:
            return match.group(1)


        # Look for XML format: <Version>9.80.3</Version>
        match = re.search(r'<Version>([^<]+)</Version>', content)
        if match:
            return match.group(1)


        # Fallback: Try the older param.cgi API
        url = f"http://{ip_address}/axis-cgi/param.cgi?action=list&group=Properties.Firmware.Version"
        response = requests.get(
            url,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )
        response.raise_for_status()


        # Parse param.cgi format: Version=9.80.3
        match = re.search(r'Version=([^\s\r\n]+)', response.text)
        if match:
            return match.group(1)


        return None


    except requests.exceptions.RequestException:
        # Firmware version is optional, fail silently
        return None



# ============================================================================
# MAIN QUERY FUNCTION
# ============================================================================


def query_device(ip_address: str, username: str, password: str, index: int, total: int) -> Dict:
    """
    Query a single Axis device for all information.


    Args:
        ip_address: IP address of the device
        username: Username for authentication
        password: Password for authentication
        index: Current device number (for progress display)
        total: Total number of devices


    Returns:
        Dictionary containing all retrieved information
    """
    # Print progress header
    print(f"{Colors.YELLOW}[{index}/{total}] Querying: {ip_address} (User: {username}){Colors.RESET}")


    # Get OAK
    print(f"  Retrieving OAK...", end=" ", flush=True)
    oak = get_axis_oak(ip_address, username, password)
    if oak:
        print(f"{Colors.GREEN}OK{Colors.RESET}")
    else:
        print(f"{Colors.RED}FAILED{Colors.RESET}")


    # Get Serial Number
    print(f"  Retrieving Serial Number...", end=" ", flush=True)
    serial = get_axis_serial_number(ip_address, username, password)
    if serial:
        print(f"{Colors.GREEN}OK{Colors.RESET}")
    else:
        print(f"{Colors.RED}FAILED{Colors.RESET}")


    # Get Model
    print(f"  Retrieving Model...", end=" ", flush=True)
    model = get_axis_model(ip_address, username, password)
    if model:
        print(f"{Colors.GREEN}OK{Colors.RESET}")
    else:
        print(f"{Colors.GRAY}N/A{Colors.RESET}")


    # Get Firmware Version
    print(f"  Retrieving Firmware Version...", end=" ", flush=True)
    firmware = get_axis_firmware_version(ip_address, username, password)
    if firmware:
        print(f"{Colors.GREEN}OK{Colors.RESET}")
    else:
        print(f"{Colors.GRAY}N/A{Colors.RESET}")


    print()  # Empty line for spacing


    # Return result dictionary
    return {
        "IPAddress": ip_address,
        "Username": username,
        "SerialNumber": serial or "",
        "FirmwareVersion": firmware or "",
        "OAK": oak or "",
        "Model": model or "",
        "Status": "Success" if (oak or serial) else "Failed",
        "Timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }



# ============================================================================
# CSV I/O FUNCTIONS
# ============================================================================


def read_input_csv(file_path: str) -> List[Dict]:
    """
    Read device information from input CSV file.


    Args:
        file_path: Path to input CSV file


    Returns:
        List of dictionaries containing device information


    Raises:
        FileNotFoundError: If file doesn't exist
        ValueError: If CSV format is invalid
    """
    # Check if file exists
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            # Read CSV using DictReader (automatically handles headers)
            reader = csv.DictReader(f)
            devices = list(reader)


            # Validate required columns exist
            required_columns = ['IPAddress', 'Username', 'Password']
            if not devices:
                raise ValueError("CSV file is empty")


            # Check if all required columns are present
            actual_columns = devices[0].keys()
            missing_columns = [col for col in required_columns if col not in actual_columns]


            if missing_columns:
                raise ValueError(f"CSV missing required columns: {', '.join(missing_columns)}\n"
                               f"Required columns: {', '.join(required_columns)}")


            return devices


    except FileNotFoundError:
        raise FileNotFoundError(f"File not found: {file_path}")



def write_output_csv(file_path: str, results: List[Dict]) -> None:
    """
    Write results to output CSV file.


    Args:
        file_path: Path to output CSV file
        results: List of result dictionaries
    """
    # Define output CSV columns in the order we want them
    fieldnames = [
        "IPAddress",
        "Username",
        "SerialNumber",
        "FirmwareVersion",
        "OAK",
        "Model",
        "Status",
        "Timestamp"
    ]


    # Write CSV file
    with open(file_path, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)


        # Write header row
        writer.writeheader()


        # Write all result rows
        writer.writerows(results)



# ============================================================================
# MAIN PROGRAM
# ============================================================================


def main():
    """Main program entry point."""


    # Parse command line arguments
    parser = argparse.ArgumentParser(
        description="Retrieve OAK and device information from Axis cameras using CSV input",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
CSV Input Format:
    IPAddress,Username,Password
    192.168.1.100,root,password123
    192.168.1.101,admin,admin456


Examples:
    python get_axis_device_info_csv.py
    python get_axis_device_info_csv.py --input cameras.csv
    python get_axis_device_info_csv.py --input cameras.csv --output results.csv
        """
    )
    parser.add_argument(
        '--input', '-i',
        help='Path to input CSV file'
    )
    parser.add_argument(
        '--output', '-o',
        help='Path to output CSV file'
    )


    args = parser.parse_args()


    # Print banner
    print()
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print(f"{Colors.CYAN}  Axis Device Info - CSV Input Mode{Colors.RESET}")
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print()


    # Get input CSV path (either from command line or prompt user)
    input_csv = args.input
    if not input_csv:
        print(f"{Colors.YELLOW}CSV File Format Required:{Colors.RESET}")
        print(f"{Colors.GRAY}  IPAddress,Username,Password{Colors.RESET}")
        print(f"{Colors.GRAY}  192.168.1.100,root,password123{Colors.RESET}")
        print(f"{Colors.GRAY}  192.168.1.101,admin,admin456{Colors.RESET}")
        print()
        input_csv = input("Enter path to input CSV file: ").strip()


    # Read and validate input CSV
    try:
        devices = read_input_csv(input_csv)
    except (FileNotFoundError, ValueError) as e:
        print(f"{Colors.RED}ERROR: {e}{Colors.RESET}")
        sys.exit(1)


    print(f"{Colors.GREEN}Found {len(devices)} device(s) in CSV{Colors.RESET}")
    print()


    # Get output CSV path (either from command line or prompt user)
    output_csv = args.output
    if not output_csv:
        default_output = f"axis-results-{datetime.now().strftime('%Y%m%d-%H%M%S')}.csv"
        user_input = input(f"Output CSV filename (press Enter for '{default_output}'): ").strip()
        output_csv = user_input if user_input else default_output


    print()
    print(f"{Colors.CYAN}Starting device queries...{Colors.RESET}")
    print()


    # Query each device from the CSV
    results = []
    for index, device in enumerate(devices, start=1):
        result = query_device(
            ip_address=device['IPAddress'],
            username=device['Username'],
            password=device['Password'],
            index=index,
            total=len(devices)
        )
        results.append(result)


    # Display results summary table
    print()
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print(f"{Colors.CYAN}              RESULTS{Colors.RESET}")
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print()


    # Print results in a formatted table
    print(f"{'IP Address':<16} {'Serial':<16} {'Firmware':<12} {'Status':<10}")
    print("-" * 60)
    for result in results:
        status_color = Colors.GREEN if result['Status'] == 'Success' else Colors.RED
        print(f"{result['IPAddress']:<16} {result['SerialNumber']:<16} "
              f"{result['FirmwareVersion']:<12} {status_color}{result['Status']:<10}{Colors.RESET}")


    # Write results to output CSV
    try:
        write_output_csv(output_csv, results)
        print()
        print(f"{Colors.GREEN}Results exported to: {output_csv}{Colors.RESET}")
    except Exception as e:
        print()
        print(f"{Colors.RED}ERROR: Failed to export CSV: {e}{Colors.RESET}")


    # Print summary statistics
    print()
    success_count = sum(1 for r in results if r['Status'] == 'Success')
    print(f"{Colors.CYAN}Summary: {success_count} of {len(results)} devices queried successfully{Colors.RESET}")
    print()



# Entry point - only run main() if script is executed directly (not imported)
if __name__ == "__main__":
    main()

Get-AxisDeviceInfo (Powershell)

<#
.SYNOPSIS
    Retrieves OAK and Serial Number from Axis devices via VAPIX API


.DESCRIPTION
    Interactive script that prompts for camera IP addresses and credentials,
    then queries Axis cameras to retrieve:
    - OAK (Owner Authentication Key)
    - Serial Number
    - Model information


.NOTES
    Requires network connectivity to Axis devices
    Uses VAPIX API with HTTP Digest Authentication
#>


# Display banner
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "  Axis Device Information Retrieval" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""


# Prompt for IP addresses
Write-Host "Enter camera IP address(es):" -ForegroundColor Yellow
Write-Host "  - Single IP: 192.168.1.100" -ForegroundColor Gray
Write-Host "  - Multiple IPs (comma separated): 192.168.1.100, 192.168.1.101" -ForegroundColor Gray
Write-Host "  - IP range notation: 192.168.1.100-105" -ForegroundColor Gray
Write-Host ""
$ipInput = Read-Host "IP Address(es)"


# Parse IP addresses
$ipAddresses = @()
if ($ipInput -match '(\d+\.\d+\.\d+\.)(\d+)-(\d+)') {
    # Range notation (e.g., 192.168.1.100-105)
    $baseIP = $matches[1]
    $startRange = [int]$matches[2]
    $endRange = [int]$matches[3]


    for ($i = $startRange; $i -le $endRange; $i++) {
        $ipAddresses += "$baseIP$i"
    }
    Write-Host "Expanded range to $($ipAddresses.Count) addresses" -ForegroundColor Green
} else {
    # Single or comma-separated IPs
    $ipAddresses = $ipInput -split ',' | ForEach-Object { $_.Trim() }
}


Write-Host ""


# Prompt for credentials
Write-Host "Enter camera credentials:" -ForegroundColor Yellow
$username = Read-Host "Username"
$securePassword = Read-Host "Password" -AsSecureString


# Convert to credential object
$credential = New-Object System.Management.Automation.PSCredential($username, $securePassword)


Write-Host ""


# Ask about CSV export
$exportChoice = Read-Host "Export results to CSV? (y/n)"
$csvPath = $null
if ($exportChoice -eq 'y') {
    $defaultPath = "axis-devices-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
    $csvInput = Read-Host "CSV filename (press Enter for '$defaultPath')"
    $csvPath = if ([string]::IsNullOrWhiteSpace($csvInput)) { $defaultPath } else { $csvInput }
}


Write-Host ""
Write-Host "Starting device queries..." -ForegroundColor Cyan
Write-Host ""


# Function to query OAK
function Get-AxisOAK {
    param(
        [string]$DeviceIP,
        [PSCredential]$Cred
    )


    try {
        $oakUrl = "http://$DeviceIP/axis-cgi/oak.cgi"


        # Prepare JSON-RPC request body
        $body = @{
            apiVersion = "1.0"
            method = "getOak"
        } | ConvertTo-Json


        # Make POST request with JSON body
        $response = Invoke-RestMethod -Uri $oakUrl -Method Post `
            -Credential $Cred -ContentType "application/json" `
            -Body $body -TimeoutSec 10 -ErrorAction Stop


        # Parse OAK from JSON response
        if ($response.data.oak) {
            return $response.data.oak
        }
        elseif ($response.oak) {
            return $response.oak
        }
        else {
            Write-Warning "OAK not found in response from ${DeviceIP}"
            return $null
        }
    }
    catch {
        Write-Warning "Failed to retrieve OAK from ${DeviceIP}: $($_.Exception.Message)"
        return $null
    }
}


# Function to query Serial Number
function Get-AxisSerialNumber {
    param(
        [string]$DeviceIP,
        [PSCredential]$Cred
    )


    try {
        # Try basicdeviceinfo.cgi first (newer API)
        $infoUrl = "http://$DeviceIP/axis-cgi/basicdeviceinfo.cgi?apiversion=1.0"
        $response = Invoke-WebRequest -Uri $infoUrl -Credential $Cred `
            -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop


        # Parse serial number from response
        if ($response.Content -match '"SerialNumber":\s*"([^"]+)"') {
            return $matches[1]
        }
        elseif ($response.Content -match '<SerialNumber>([^<]+)</SerialNumber>') {
            return $matches[1]
        }
        else {
            # Fallback to param.cgi for older firmware
            $paramUrl = "http://$DeviceIP/axis-cgi/param.cgi?action=list&group=Properties.System.SerialNumber"
            $response = Invoke-WebRequest -Uri $paramUrl -Credential $Cred `
                -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop


            if ($response.Content -match 'SerialNumber=([^\s\r\n]+)') {
                return $matches[1]
            }
        }


        return $null
    }
    catch {
        Write-Warning "Failed to retrieve Serial Number from ${DeviceIP}: $($_.Exception.Message)"
        return $null
    }
}


# Function to get device model
function Get-AxisModel {
    param(
        [string]$DeviceIP,
        [PSCredential]$Cred
    )


    try {
        $paramUrl = "http://$DeviceIP/axis-cgi/param.cgi?action=list&group=Properties.System.ProdNbr"
        $response = Invoke-WebRequest -Uri $paramUrl -Credential $Cred `
            -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop


        if ($response.Content -match 'ProdNbr=([^\s\r\n]+)') {
            return $matches[1]
        }
        return $null
    }
    catch {
        return $null
    }
}


# Query each device
$results = @()
$counter = 1


foreach ($ip in $ipAddresses) {
    Write-Host "[$counter/$($ipAddresses.Count)] Querying: $ip" -ForegroundColor Yellow


    # Get OAK
    Write-Host "  Retrieving OAK..." -NoNewline
    $oak = Get-AxisOAK -DeviceIP $ip -Cred $credential
    if ($oak) {
        Write-Host " OK" -ForegroundColor Green
    } else {
        Write-Host " FAILED" -ForegroundColor Red
    }


    # Get Serial Number
    Write-Host "  Retrieving Serial Number..." -NoNewline
    $serial = Get-AxisSerialNumber -DeviceIP $ip -Cred $credential
    if ($serial) {
        Write-Host " OK" -ForegroundColor Green
    } else {
        Write-Host " FAILED" -ForegroundColor Red
    }


    # Get Model
    Write-Host "  Retrieving Model..." -NoNewline
    $model = Get-AxisModel -DeviceIP $ip -Cred $credential
    if ($model) {
        Write-Host " OK" -ForegroundColor Green
    } else {
        Write-Host " N/A" -ForegroundColor Gray
    }


    # Create result object
    $result = [PSCustomObject]@{
        IPAddress    = $ip
        SerialNumber = $serial
        OAK          = $oak
        Model        = $model
        Status       = if ($oak -or $serial) { "Success" } else { "Failed" }
        Timestamp    = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    }


    $results += $result
    $counter++
    Write-Host ""
}


# Display results
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "              RESULTS" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""


$results | Format-Table -AutoSize


# Export to CSV if requested
if ($csvPath) {
    try {
        $results | Export-Csv -Path $csvPath -NoTypeInformation
        Write-Host "Results exported to: $csvPath" -ForegroundColor Green
    }
    catch {
        Write-Warning "Failed to export CSV: $($_.Exception.Message)"
    }
}


# Summary
Write-Host ""
$successCount = ($results | Where-Object { $_.Status -eq "Success" }).Count
Write-Host "Summary: $successCount of $($results.Count) devices queried successfully" -ForegroundColor Cyan
Write-Host ""


Get-AxisDeviceInfo-FromCSV (Powershell)

<#
.SYNOPSIS
    Retrieves OAK and Serial Number from Axis devices using CSV input file


.DESCRIPTION
    Reads camera information from CSV file and queries each device to retrieve:
    - OAK (Owner Authentication Key)
    - Serial Number
    - Model information


    CSV Format:
    IPAddress,Username,Password
    192.168.1.100,root,password123
    192.168.1.101,admin,admin456


.PARAMETER InputCSV
    Path to input CSV file containing device information


.PARAMETER OutputCSV
    Path to output CSV file for results (optional)


.EXAMPLE
    .\Get-AxisDeviceInfo-FromCSV.ps1 -InputCSV "cameras.csv"


.EXAMPLE
    .\Get-AxisDeviceInfo-FromCSV.ps1 -InputCSV "cameras.csv" -OutputCSV "results.csv"
#>


param(
    [Parameter(Mandatory=$false)]
    [string]$InputCSV,


    [Parameter(Mandatory=$false)]
    [string]$OutputCSV
)


# Display banner
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "  Axis Device Info - CSV Input Mode" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""


# Prompt for input CSV if not provided
if (-not $InputCSV) {
    Write-Host "CSV File Format Required:" -ForegroundColor Yellow
    Write-Host "  IPAddress,Username,Password" -ForegroundColor Gray
    Write-Host "  192.168.1.100,root,password123" -ForegroundColor Gray
    Write-Host "  192.168.1.101,admin,admin456" -ForegroundColor Gray
    Write-Host ""
    $InputCSV = Read-Host "Enter path to input CSV file"
}


# Validate input file exists
if (-not (Test-Path $InputCSV)) {
    Write-Host "ERROR: File not found: $InputCSV" -ForegroundColor Red
    exit 1
}


# Prompt for output CSV if not provided
if (-not $OutputCSV) {
    $defaultOutput = "axis-results-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
    $outputChoice = Read-Host "Export results to CSV? Enter filename or press Enter for '$defaultOutput' (leave blank to skip)"
    if (-not [string]::IsNullOrWhiteSpace($outputChoice)) {
        $OutputCSV = $outputChoice
    } else {
        $OutputCSV = $defaultOutput
    }
}


Write-Host ""
Write-Host "Reading devices from: $InputCSV" -ForegroundColor Cyan


# Import CSV
try {
    $devices = Import-Csv -Path $InputCSV
}
catch {
    Write-Host "ERROR: Failed to read CSV file: $($_.Exception.Message)" -ForegroundColor Red
    exit 1
}


# Validate CSV format
$requiredColumns = @('IPAddress', 'Username', 'Password')
$csvColumns = $devices[0].PSObject.Properties.Name


foreach ($col in $requiredColumns) {
    if ($col -notin $csvColumns) {
        Write-Host "ERROR: CSV missing required column: $col" -ForegroundColor Red
        Write-Host "Required columns: IPAddress, Username, Password" -ForegroundColor Yellow
        exit 1
    }
}


Write-Host "Found $($devices.Count) device(s) in CSV" -ForegroundColor Green
Write-Host ""
Write-Host "Starting device queries..." -ForegroundColor Cyan
Write-Host ""


# Function to query OAK
function Get-AxisOAK {
    param(
        [string]$DeviceIP,
        [PSCredential]$Cred
    )


    try {
        $oakUrl = "http://$DeviceIP/axis-cgi/oak.cgi"


        # Prepare JSON-RPC request body
        $body = @{
            apiVersion = "1.0"
            method = "getOak"
        } | ConvertTo-Json


        # Make POST request with JSON body
        $response = Invoke-RestMethod -Uri $oakUrl -Method Post `
            -Credential $Cred -ContentType "application/json" `
            -Body $body -TimeoutSec 10 -ErrorAction Stop


        # Parse OAK from JSON response
        if ($response.data.oak) {
            return $response.data.oak
        }
        elseif ($response.oak) {
            return $response.oak
        }
        else {
            Write-Warning "OAK not found in response from ${DeviceIP}"
            return $null
        }
    }
    catch {
        Write-Warning "Failed to retrieve OAK from ${DeviceIP}: $($_.Exception.Message)"
        return $null
    }
}


# Function to query Serial Number
function Get-AxisSerialNumber {
    param(
        [string]$DeviceIP,
        [PSCredential]$Cred
    )


    try {
        # Try basicdeviceinfo.cgi first (newer API)
        $infoUrl = "http://$DeviceIP/axis-cgi/basicdeviceinfo.cgi?apiversion=1.0"
        $response = Invoke-WebRequest -Uri $infoUrl -Credential $Cred `
            -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop


        # Parse serial number from response
        if ($response.Content -match '"SerialNumber":\s*"([^"]+)"') {
            return $matches[1]
        }
        elseif ($response.Content -match '<SerialNumber>([^<]+)</SerialNumber>') {
            return $matches[1]
        }
        else {
            # Fallback to param.cgi for older firmware
            $paramUrl = "http://$DeviceIP/axis-cgi/param.cgi?action=list&group=Properties.System.SerialNumber"
            $response = Invoke-WebRequest -Uri $paramUrl -Credential $Cred `
                -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop


            if ($response.Content -match 'SerialNumber=([^\s\r\n]+)') {
                return $matches[1]
            }
        }


        return $null
    }
    catch {
        Write-Warning "Failed to retrieve Serial Number from ${DeviceIP}: $($_.Exception.Message)"
        return $null
    }
}


# Function to get device model
function Get-AxisModel {
    param(
        [string]$DeviceIP,
        [PSCredential]$Cred
    )


    try {
        $paramUrl = "http://$DeviceIP/axis-cgi/param.cgi?action=list&group=Properties.System.ProdNbr"
        $response = Invoke-WebRequest -Uri $paramUrl -Credential $Cred `
            -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop


        if ($response.Content -match 'ProdNbr=([^\s\r\n]+)') {
            return $matches[1]
        }
        return $null
    }
    catch {
        return $null
    }
}


# Function to get firmware version
function Get-AxisFirmwareVersion {
    param(
        [string]$DeviceIP,
        [PSCredential]$Cred
    )


    try {
        # Try basicdeviceinfo.cgi first (newer API)
        $infoUrl = "http://$DeviceIP/axis-cgi/basicdeviceinfo.cgi?apiversion=1.0"
        $response = Invoke-WebRequest -Uri $infoUrl -Credential $Cred `
            -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop


        # Parse firmware version from response
        if ($response.Content -match '"Version":\s*"([^"]+)"') {
            return $matches[1]
        }
        elseif ($response.Content -match '<Version>([^<]+)</Version>') {
            return $matches[1]
        }
        else {
            # Fallback to param.cgi
            $paramUrl = "http://$DeviceIP/axis-cgi/param.cgi?action=list&group=Properties.Firmware.Version"
            $response = Invoke-WebRequest -Uri $paramUrl -Credential $Cred `
                -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop


            if ($response.Content -match 'Version=([^\s\r\n]+)') {
                return $matches[1]
            }
        }


        return $null
    }
    catch {
        Write-Warning "Failed to retrieve Firmware Version from ${DeviceIP}: $($_.Exception.Message)"
        return $null
    }
}


# Query each device
$results = @()
$counter = 1


foreach ($device in $devices) {
    $ip = $device.IPAddress
    $username = $device.Username
    $password = $device.Password


    Write-Host "[$counter/$($devices.Count)] Querying: $ip (User: $username)" -ForegroundColor Yellow


    # Create credential object
    $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
    $credential = New-Object System.Management.Automation.PSCredential($username, $securePassword)


    # Get OAK
    Write-Host "  Retrieving OAK..." -NoNewline
    $oak = Get-AxisOAK -DeviceIP $ip -Cred $credential
    if ($oak) {
        Write-Host " OK" -ForegroundColor Green
    } else {
        Write-Host " FAILED" -ForegroundColor Red
    }


    # Get Serial Number
    Write-Host "  Retrieving Serial Number..." -NoNewline
    $serial = Get-AxisSerialNumber -DeviceIP $ip -Cred $credential
    if ($serial) {
        Write-Host " OK" -ForegroundColor Green
    } else {
        Write-Host " FAILED" -ForegroundColor Red
    }


    # Get Model
    Write-Host "  Retrieving Model..." -NoNewline
    $model = Get-AxisModel -DeviceIP $ip -Cred $credential
    if ($model) {
        Write-Host " OK" -ForegroundColor Green
    } else {
        Write-Host " N/A" -ForegroundColor Gray
    }


    # Get Firmware Version
    Write-Host "  Retrieving Firmware Version..." -NoNewline
    $firmwareVersion = Get-AxisFirmwareVersion -DeviceIP $ip -Cred $credential
    if ($firmwareVersion) {
        Write-Host " OK" -ForegroundColor Green
    } else {
        Write-Host " N/A" -ForegroundColor Gray
    }


    # Create result object
    $result = [PSCustomObject]@{
        IPAddress       = $ip
        Username        = $username
        SerialNumber    = $serial
        FirmwareVersion = $firmwareVersion
        OAK             = $oak
        Model           = $model
        Status          = if ($oak -or $serial) { "Success" } else { "Failed" }
        Timestamp       = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    }


    $results += $result
    $counter++
    Write-Host ""
}


# Display results
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "              RESULTS" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""


$results | Format-Table -AutoSize


# Export to CSV
if ($OutputCSV) {
    try {
        $results | Export-Csv -Path $OutputCSV -NoTypeInformation
        Write-Host "Results exported to: $OutputCSV" -ForegroundColor Green
    }
    catch {
        Write-Warning "Failed to export CSV: $($_.Exception.Message)"
    }
}


# Summary
Write-Host ""
$successCount = ($results | Where-Object { $_.Status -eq "Success" }).Count
Write-Host "Summary: $successCount of $($results.Count) devices queried successfully" -ForegroundColor Cyan
Write-Host ""

 set_axis_remote_service.py

#!/usr/bin/env python3
"""
Axis Remote Service Configuration Script


This script reads camera information from a CSV file and enables remote service
configuration on each device by sending POST request to /axis-cgi/remoteservice.cgi


Requirements:
    pip install requests


CSV Input Format:
    IPAddress,Username,Password
    192.168.1.100,root,password123
    192.168.1.101,admin,admin456


Usage:
    python set_axis_remote_service.py
    python set_axis_remote_service.py --input cameras.csv --output results.csv
"""


import csv
import sys
import argparse
from datetime import datetime
from typing import Dict, Optional, List
import json


# Third-party library for HTTP requests
# Install with: pip install requests
try:
    import requests
    from requests.auth import HTTPDigestAuth
except ImportError:
    print("ERROR: 'requests' library not found.")
    print("Please install it using: pip install requests")
    sys.exit(1)



# ============================================================================
# CONFIGURATION
# ============================================================================


# Timeout for HTTP requests in seconds
REQUEST_TIMEOUT = 10


# Color codes for terminal output (ANSI escape codes)
class Colors:
    """ANSI color codes for colored terminal output"""
    CYAN = '\033[96m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    GRAY = '\033[90m'
    RESET = '\033[0m'
    BOLD = '\033[1m'



# ============================================================================
# API CONFIGURATION FUNCTION
# ============================================================================


def set_axis_remote_service(ip_address: str, username: str, password: str) -> Dict:
    """
    Enable remote service on Axis device.


    Args:
        ip_address: IP address of the Axis camera
        username: Username for authentication
        password: Password for authentication


    Returns:
        Dictionary with 'success' (bool) and 'message' (str)
    """
    try:
        # Construct the Remote Service API endpoint URL
        url = f"http://{ip_address}/axis-cgi/remoteservice.cgi"


        # Prepare JSON request body
        # This enables the remote service feature on the Axis camera
        payload = {
            "apiVersion": "1.0",
            "method": "setConfig",
            "params": {
                "state": "enabled"
            }
        }


        # Set HTTP headers for JSON content
        headers = {
            "Content-Type": "application/json"
        }


        # Make POST request with HTTP Digest Authentication
        # Axis cameras use Digest Auth (more secure than Basic Auth)
        response = requests.post(
            url,
            json=payload,
            headers=headers,
            auth=HTTPDigestAuth(username, password),
            timeout=REQUEST_TIMEOUT
        )


        # Check if request was successful (HTTP 200)
        response.raise_for_status()


        # Parse JSON response
        data = response.json()


        # Check if API returned an error
        if "error" in data:
            error_code = data["error"].get("code", "unknown")
            error_message = data["error"].get("message", "Unknown error")
            return {
                "success": False,
                "message": f"API Error: {error_message} (Code: {error_code})"
            }
        else:
            # Success - remote service enabled
            return {
                "success": True,
                "message": "Remote service enabled successfully"
            }


    except requests.exceptions.Timeout:
        return {
            "success": False,
            "message": f"Request timeout for {ip_address}"
        }
    except requests.exceptions.HTTPError as e:
        return {
            "success": False,
            "message": f"HTTP Error: {e}"
        }
    except requests.exceptions.RequestException as e:
        return {
            "success": False,
            "message": f"Request failed: {e}"
        }
    except json.JSONDecodeError:
        return {
            "success": False,
            "message": "Invalid JSON response from device"
        }



# ============================================================================
# MAIN CONFIGURATION FUNCTION
# ============================================================================


def configure_device(ip_address: str, username: str, password: str, index: int, total: int) -> Dict:
    """
    Configure a single Axis device to enable remote service.


    Args:
        ip_address: IP address of the device
        username: Username for authentication
        password: Password for authentication
        index: Current device number (for progress display)
        total: Total number of devices


    Returns:
        Dictionary containing configuration result
    """
    # Print progress header
    print(f"{Colors.YELLOW}[{index}/{total}] Configuring: {ip_address} (User: {username}){Colors.RESET}")


    # Enable remote service
    print(f"  Setting remote service to 'enabled'...", end=" ", flush=True)
    result = set_axis_remote_service(ip_address, username, password)


    if result["success"]:
        print(f"{Colors.GREEN}OK{Colors.RESET}")
        print(f"{Colors.GRAY}  {result['message']}{Colors.RESET}")
    else:
        print(f"{Colors.RED}FAILED{Colors.RESET}")
        print(f"{Colors.RED}  {result['message']}{Colors.RESET}")


    print()  # Empty line for spacing


    # Return result dictionary
    return {
        "IPAddress": ip_address,
        "Username": username,
        "Status": "Success" if result["success"] else "Failed",
        "Message": result["message"],
        "Timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }



# ============================================================================
# CSV I/O FUNCTIONS
# ============================================================================


def read_input_csv(file_path: str) -> List[Dict]:
    """
    Read device information from input CSV file.


    Args:
        file_path: Path to input CSV file


    Returns:
        List of dictionaries containing device information


    Raises:
        FileNotFoundError: If file doesn't exist
        ValueError: If CSV format is invalid
    """
    # Check if file exists
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            # Read CSV using DictReader (automatically handles headers)
            reader = csv.DictReader(f)
            devices = list(reader)


            # Validate required columns exist
            required_columns = ['IPAddress', 'Username', 'Password']
            if not devices:
                raise ValueError("CSV file is empty")


            # Check if all required columns are present
            actual_columns = devices[0].keys()
            missing_columns = [col for col in required_columns if col not in actual_columns]


            if missing_columns:
                raise ValueError(f"CSV missing required columns: {', '.join(missing_columns)}\n"
                               f"Required columns: {', '.join(required_columns)}")


            return devices


    except FileNotFoundError:
        raise FileNotFoundError(f"File not found: {file_path}")



def write_output_csv(file_path: str, results: List[Dict]) -> None:
    """
    Write results to output CSV file.


    Args:
        file_path: Path to output CSV file
        results: List of result dictionaries
    """
    # Define output CSV columns in the order we want them
    fieldnames = [
        "IPAddress",
        "Username",
        "Status",
        "Message",
        "Timestamp"
    ]


    # Write CSV file
    with open(file_path, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)


        # Write header row
        writer.writeheader()


        # Write all result rows
        writer.writerows(results)



# ============================================================================
# MAIN PROGRAM
# ============================================================================


def main():
    """Main program entry point."""


    # Parse command line arguments
    parser = argparse.ArgumentParser(
        description="Enable remote service on Axis cameras using CSV input",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
CSV Input Format:
    IPAddress,Username,Password
    192.168.1.100,root,password123
    192.168.1.101,admin,admin456


Examples:
    python set_axis_remote_service.py
    python set_axis_remote_service.py --input cameras.csv
    python set_axis_remote_service.py --input cameras.csv --output results.csv
        """
    )
    parser.add_argument(
        '--input', '-i',
        help='Path to input CSV file'
    )
    parser.add_argument(
        '--output', '-o',
        help='Path to output CSV file'
    )


    args = parser.parse_args()


    # Print banner
    print()
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print(f"{Colors.CYAN}  Axis Remote Service Configuration{Colors.RESET}")
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print()


    # Get input CSV path (either from command line or prompt user)
    input_csv = args.input
    if not input_csv:
        print(f"{Colors.YELLOW}CSV File Format Required:{Colors.RESET}")
        print(f"{Colors.GRAY}  IPAddress,Username,Password{Colors.RESET}")
        print(f"{Colors.GRAY}  192.168.1.100,root,password123{Colors.RESET}")
        print(f"{Colors.GRAY}  192.168.1.101,admin,admin456{Colors.RESET}")
        print()
        input_csv = input("Enter path to input CSV file: ").strip()


    # Read and validate input CSV
    try:
        devices = read_input_csv(input_csv)
    except (FileNotFoundError, ValueError) as e:
        print(f"{Colors.RED}ERROR: {e}{Colors.RESET}")
        sys.exit(1)


    print(f"{Colors.GREEN}Found {len(devices)} device(s) in CSV{Colors.RESET}")
    print()


    # Get output CSV path (either from command line or prompt user)
    output_csv = args.output
    if not output_csv:
        default_output = f"axis-remoteservice-results-{datetime.now().strftime('%Y%m%d-%H%M%S')}.csv"
        user_input = input(f"Output CSV filename (press Enter for '{default_output}'): ").strip()
        output_csv = user_input if user_input else default_output


    print()
    print(f"{Colors.CYAN}Starting configuration changes...{Colors.RESET}")
    print()


    # Configure each device from the CSV
    results = []
    for index, device in enumerate(devices, start=1):
        result = configure_device(
            ip_address=device['IPAddress'],
            username=device['Username'],
            password=device['Password'],
            index=index,
            total=len(devices)
        )
        results.append(result)


    # Display results summary table
    print()
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print(f"{Colors.CYAN}              RESULTS{Colors.RESET}")
    print(f"{Colors.CYAN}{'='*50}{Colors.RESET}")
    print()


    # Print results in a formatted table
    print(f"{'IP Address':<16} {'Status':<10} {'Message':<40}")
    print("-" * 70)
    for result in results:
        status_color = Colors.GREEN if result['Status'] == 'Success' else Colors.RED
        # Truncate message if too long
        message = result['Message'][:37] + "..." if len(result['Message']) > 40 else result['Message']
        print(f"{result['IPAddress']:<16} {status_color}{result['Status']:<10}{Colors.RESET} {message:<40}")


    # Write results to output CSV
    try:
        write_output_csv(output_csv, results)
        print()
        print(f"{Colors.GREEN}Results exported to: {output_csv}{Colors.RESET}")
    except Exception as e:
        print()
        print(f"{Colors.RED}ERROR: Failed to export CSV: {e}{Colors.RESET}")


    # Print summary statistics
    print()
    success_count = sum(1 for r in results if r['Status'] == 'Success')
    print(f"{Colors.CYAN}Summary: {success_count} of {len(results)} devices configured successfully{Colors.RESET}")
    print()



# Entry point - only run main() if script is executed directly (not imported)
if __name__ == "__main__":
    main()


 Set-AxisRemoteService (Powershell)

<#
.SYNOPSIS
    Enable Remote Service on Axis devices via VAPIX API


.DESCRIPTION
    Reads camera information from CSV file and enables remote service configuration
    on each device by sending POST request to /axis-cgi/remoteservice.cgi


    CSV Input Format:
    IPAddress,Username,Password
    192.168.1.100,root,password123
    192.168.1.101,admin,admin456


.EXAMPLE
    .\Set-AxisRemoteService.ps1 -InputCSV "cameras.csv"


.EXAMPLE
    .\Set-AxisRemoteService.ps1 -InputCSV "cameras.csv" -OutputCSV "results.csv"
#>


param(
    [Parameter(Mandatory=$false)]
    [string]$InputCSV,


    [Parameter(Mandatory=$false)]
    [string]$OutputCSV
)


# Display banner
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "  Axis Remote Service Configuration" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""


# Prompt for input CSV if not provided
if (-not $InputCSV) {
    Write-Host "CSV File Format Required:" -ForegroundColor Yellow
    Write-Host "  IPAddress,Username,Password" -ForegroundColor Gray
    Write-Host "  192.168.1.100,root,password123" -ForegroundColor Gray
    Write-Host "  192.168.1.101,admin,admin456" -ForegroundColor Gray
    Write-Host ""
    $InputCSV = Read-Host "Enter path to input CSV file"
}


# Validate input file exists
if (-not (Test-Path $InputCSV)) {
    Write-Host "ERROR: File not found: $InputCSV" -ForegroundColor Red
    exit 1
}


# Prompt for output CSV if not provided
if (-not $OutputCSV) {
    $defaultOutput = "axis-remoteservice-results-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
    $outputChoice = Read-Host "Export results to CSV? Enter filename or press Enter for '$defaultOutput' (leave blank to skip)"
    if (-not [string]::IsNullOrWhiteSpace($outputChoice)) {
        $OutputCSV = $outputChoice
    } else {
        $OutputCSV = $defaultOutput
    }
}


Write-Host ""
Write-Host "Reading devices from: $InputCSV" -ForegroundColor Cyan


# Import CSV
try {
    $devices = Import-Csv -Path $InputCSV
}
catch {
    Write-Host "ERROR: Failed to read CSV file: $($_.Exception.Message)" -ForegroundColor Red
    exit 1
}


# Validate CSV format
$requiredColumns = @('IPAddress', 'Username', 'Password')
$csvColumns = $devices[0].PSObject.Properties.Name


foreach ($col in $requiredColumns) {
    if ($col -notin $csvColumns) {
        Write-Host "ERROR: CSV missing required column: $col" -ForegroundColor Red
        Write-Host "Required columns: IPAddress, Username, Password" -ForegroundColor Yellow
        exit 1
    }
}


Write-Host "Found $($devices.Count) device(s) in CSV" -ForegroundColor Green
Write-Host ""
Write-Host "Starting configuration changes..." -ForegroundColor Cyan
Write-Host ""


# Function to enable remote service
function Set-AxisRemoteService {
    param(
        [string]$DeviceIP,
        [PSCredential]$Cred
    )


    try {
        # Construct the Remote Service API endpoint URL
        $url = "http://$DeviceIP/axis-cgi/remoteservice.cgi"


        # Prepare JSON request body
        $body = @{
            apiVersion = "1.0"
            method = "setConfig"
            params = @{
                state = "enabled"
            }
        } | ConvertTo-Json -Compress


        # Make POST request with JSON body
        $response = Invoke-RestMethod -Uri $url -Method Post `
            -Credential $Cred -ContentType "application/json" `
            -Body $body -TimeoutSec 10 -ErrorAction Stop


        # Check response for success
        if ($response.error) {
            return @{
                Success = $false
                Message = "API Error: $($response.error.message) (Code: $($response.error.code))"
            }
        }
        else {
            return @{
                Success = $true
                Message = "Remote service enabled successfully"
            }
        }
    }
    catch {
        return @{
            Success = $false
            Message = "Request failed: $($_.Exception.Message)"
        }
    }
}


# Process each device
$results = @()
$counter = 1


foreach ($device in $devices) {
    $ip = $device.IPAddress
    $username = $device.Username
    $password = $device.Password


    Write-Host "[$counter/$($devices.Count)] Configuring: $ip (User: $username)" -ForegroundColor Yellow


    # Create credential object
    $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
    $credential = New-Object System.Management.Automation.PSCredential($username, $securePassword)


    # Enable remote service
    Write-Host "  Setting remote service to 'enabled'..." -NoNewline
    $result = Set-AxisRemoteService -DeviceIP $ip -Cred $credential


    if ($result.Success) {
        Write-Host " OK" -ForegroundColor Green
        Write-Host "  $($result.Message)" -ForegroundColor Gray
    } else {
        Write-Host " FAILED" -ForegroundColor Red
        Write-Host "  $($result.Message)" -ForegroundColor Red
    }


    # Create result object
    $resultObj = [PSCustomObject]@{
        IPAddress = $ip
        Username  = $username
        Status    = if ($result.Success) { "Success" } else { "Failed" }
        Message   = $result.Message
        Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    }


    $results += $resultObj
    $counter++
    Write-Host ""
}


# Display results
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "              RESULTS" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""


$results | Format-Table -AutoSize


# Export to CSV
if ($OutputCSV) {
    try {
        $results | Export-Csv -Path $OutputCSV -NoTypeInformation
        Write-Host "Results exported to: $OutputCSV" -ForegroundColor Green
    }
    catch {
        Write-Warning "Failed to export CSV: $($_.Exception.Message)"
    }
}


# Summary
Write-Host ""
$successCount = ($results | Where-Object { $_.Status -eq "Success" }).Count
Write-Host "Summary: $successCount of $($results.Count) devices configured successfully" -ForegroundColor Cyan
Write-Host ""

 

 

 

  • Was this article helpful?