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 ""

