Skip to content
Rod Christiansen edited this page Nov 22, 2024 · 25 revisions

There is a Datatables API that is used for accessing reports. To help you get started use the shell script and python script examples below.

To get the data to CSV format, pipe the output to JSON to CSV parser (see below).

Note: starting with MunkiReport v5.3.1 you need to send a CSRF token with the API request. Check the Shell script below for the details.

Retrieve Data

Shell Version

#!/bin/sh
#
# Script to run automated queries against the munkireport datatables API
#
# Results are returned in JSON format
# The actual entries are in the 'data' variable
#
# To make this work, set up a regular user in munkireport and adjust the 
# proper values below
#
# Author: Arjen van Bochoven
# Date: 2015-11-06
# Modified: 2020-03-31 Added CSRF support

# Retrieve data from munkireport
# DEBUG=1
MR_BASE_URL='http://localhost:8888/index.php?'
MR_DATA_QUERY='/datatables/data'
MR_LOGIN='test'
MR_PASSWORD='test'

CLIENT_COLUMNS=(
    "machine.serial_number"
    "machine.hostname"
    "machine.machine_desc"
    "reportdata.timestamp"
    "reportdata.console_user"
    "machine.os_version"
    "reportdata.remote_ip"
    "munkireport.manifestname"
)

# Create query from columns
columns_to_query()
{
    # Pick up array as argument
    declare -a COLUMNS=("${!1}")
    
    MR_QUERY=""
    COL=0
    for i in "${COLUMNS[@]}"; do
        MR_QUERY="${MR_QUERY}columns[${COL}][name]=${i}&"
        COL=$((COL+1))
    done
}

# Authenticate and capture cookie
if [ $DEBUG ]; then echo 'Authenticating to munkireport..'; fi
COOKIE_JAR=$(curl -s --cookie-jar - --data "login=${MR_LOGIN}&password=${MR_PASSWORD}" ${MR_BASE_URL}/auth/login)
SESSION_COOKIE=$(echo $COOKIE_JAR | sed -n 's/.*PHPSESSID[[:space:]]/PHPSESSID=/p')
CSRF_TOKEN=$(echo "$COOKIE_JAR" | sed -n 's/.*CSRF-TOKEN[[:space:]]/X-CSRF-TOKEN: /p')

# Retrieve data with session cookie
columns_to_query CLIENT_COLUMNS[@]
if [ $DEBUG ]; then echo 'Retrieving client data..'; fi
echo $(curl -s -H "$CSRF_TOKEN" --cookie "$SESSION_COOKIE" --data $MR_QUERY ${MR_BASE_URL}${MR_DATA_QUERY})

Python Version using Requests

#!/usr/bin/env python3
import requests

base_url = "https://munkireport.example.com/index.php?"
login = ""
password = ""

columns = [
    "machine.serial_number",
    "machine.hostname",
    "machine.machine_desc",
    "reportdata.timestamp",
    "reportdata.console_user",
    "machine.os_version",
    "reportdata.remote_ip",
    "munkireport.manifestname",
]

# authenticate and get a session cookie
auth_url = f"{base_url}/auth/login"
query_url = f"{base_url}/datatables/data"
session = requests.Session()
auth_request = session.post(auth_url, data={"login": login, "password": password})

if auth_request.status_code != 200:
    print("Invalid url!")
    raise SystemExit

headers = {"x-csrf-token": session.cookies["CSRF-TOKEN"]}
def generate_query():
    q = {f"columns[{i}][name]": c for i, c in enumerate(columns)}
    return q


query_data = session.post(query_url, data=generate_query(), headers=headers)
print(query_data.json())

Specific Endpoint

s_groening reports that you can get data for a specific endpoint by appending the serial number:

https://munkireport.example.com/index.php?/module/reportdata/report/<serialnumber>

Delete Data

Python

#!/usr/bin/env python3
# Code to delete array of SNs from MunkiReport
# Requires requests python module - `sudo pip install requests`

import requests

def munkireport_delete_machine(serial_number, MRadmin, MRpassword):
    base_url = "https://munkireport.example.com/index.php?"
    auth_url = base_url + "/auth/login"
    delete_url = base_url + "/manager/delete_machine/" + serial_number

    ses = requests.session()
    response = ses.post(auth_url, data={'login': MRadmin, 'password': MRpassword})
    
    headers = {"x-csrf-token": ses.cookies["CSRF-TOKEN"]}
    
    delete_result = ses.delete(delete_url, headers=headers)
    
    if delete_result.status_code != 200:
        print("    Error on MunkiReport delete")
        raise SystemExit
    else:
        print(('    {} deleted from MunkiReport'.format(serial_number)))
        return delete_result.status_code
    
def main():

    MRadmin = ""
    MRpassword = ""
    serial_numbers = ['ARRAY', 'OF', 'SERIALNUMBERS']

    for serial_number in serial_numbers:
        print ('============================')
        print(('Checking MunkiReport for {}'.format(serial_number)))
        if not serial_number is None:
            munkireport_delete_machine(serial_number, MRadmin, MRpassword)
        else:
            print(('    Serial number invalid'.format(serial_number)))
        print ('============================')
 
if __name__ == "__main__":
    main()

Convert to CSV

If you need the data in Comma Separated Value format (csv), you could alter the above script to output in CSV format:

#!/usr/bin/python

import requests
import csv, json, sys

base_url='https://domain.example.com/report/index.php?'
login='tim_apple'
password=''

columns=[
    "machine.serial_number",
    "machine.hostname",
    "localadmin.users"
]

# authenticate and get a session cookie
auth_url ='{0}/auth/login'.format(base_url)
query_url='{0}/datatables/data'.format(base_url)
session = requests.Session()
auth_request = session.post(auth_url, data={'login': login , 'password': password})

if auth_request.status_code != 200:
    print('Invalid url!')
    raise SystemExit

headers = {"x-csrf-token": session.cookies["CSRF-TOKEN"]}

def generate_query():
    q = {'columns[{0}][name]'.format(i): c for i, c in enumerate(columns)}
    return q

query_data = session.post(query_url, data=generate_query(), headers=headers)
data = query_data.json()['data']

class SKV(csv.excel):
    # like excel, but uses semicolons
    delimiter = ";"

csv.register_dialect("SKV", SKV)

output = csv.writer(sys.stdout, "SKV")

for row in data:
    output.writerow(row)

Postman

There is also a Postman collection available at https://github.com/joncrain/munkireport-postman-collection with some other samples of the API.

FileMaker Pro

There is also a FileMaker Pro template to gather and display info via the munkireport API available at https://www.precursor.ca/mrq/ . This was part of a presentation at MacTech Conference 2019 with slides available at: https://pics.mactech.com/PresentationFiles/MTC-2019/191016-MTC-Alex-Narvey-Going-API-With-FileMaker.zip

MunkiReport 6

You can still call the old API in version 6 (at least at the time of this writing) but it is now a three stage rather than a two stage process.

Stage 1: cURL to get a CSRF Token and write it to a Cookie-Jar

curl -v 'https://mr6.example.com/login' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -c '/Path/To/cookie.txt'

A CSRF Token will be supplied in two places in the resulting HTML result:

in the header it appears in a meta tag e.g.

<meta name="csrf-token" content="Mb372xvNwBKxZ7jprstigDcnoEKbw6QLKfawse3r">

and in the body it appears as a hidden form item: e.g.

<input type="hidden" name="_token" value="Mb372xvNwBKxZ7jprstigDcnoEKbw6QLKfawse3r">

Stage 2: cURL to authenticate using your email address, password, CSRF token (from Stage 1) and Session cookie (from your Cookie-Jar, also Stage 1):

curl -v 'https://mr6.example.com/login' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-raw '_token=Mb372xvNwBKxZ7jprstigDcnoEKbw6QLKfawse3r&email=yourname@example.com&password=YourPassword' \
  -b '/Path/To/cookie.txt'

This will return a Session ID in the returned HEADER (not the HTML) which you will use to get data.

The Session ID you need is under the Set-Cookie (not Cookie) tag :

< Set-Cookie: munkireport_session=eyJpdiI6IkUzMzhvSHpPZjVsMFpMQnQ1M1R6dHc9PSIsInZhbHVlIjoibVA1WFNXNVQ2WG9vZERCWm5UbUo5aVRsOVBQVDZpazFNeFFWeUFxZXFGMnE1UzhWVDArcG9hVGFxMUZBMlVTWUlUSTVtTEZNQm1TSTNiY1o5SGtHV28zY3NpbXl3Wk92YnAvdktSa09vVURRNkMweWtXSlJaWHo3OVFMOTBkTWoiLCJtYWMiOiJmNjkwNTA4NWJlZDM2YWZlOTQ4ZGVkZGI0MWZjMjcyMTFhNTBhNzMyODk2ZjIzMjM4NDk4MWY1YjUxZjkyNWYzIiwidGFnIjoiIn0%3D

Stage 3: cURL to get data

You now use the CSRF Token from step 1 with the munkireport_session you got from the Header in step 2. Lets say you are getting data from the function module/reportdata/getUptimeStats

curl 'https://mr6.example.com/module/reportdata/getUptimeStats' \
  -H 'Accept: application/json' \
  -H 'Cookie: munkireport_session=eyJpdiI6IkUzMzhvSHpPZjVsMFpMQnQ1M1R6dHc9PSIsInZhbHVlIjoibVA1WFNXNVQ2WG9vZERCWm5UbUo5aVRsOVBQVDZpazFNeFFWeUFxZXFGMnE1UzhWVDArcG9hVGFxMUZBMlVTWUlUSTVtTEZNQm1TSTNiY1o5SGtHV28zY3NpbXl3Wk92YnAvdktSa09vVURRNkMweWtXSlJaWHo3OVFMOTBkTWoiLCJtYWMiOiJmNjkwNTA4NWJlZDM2YWZlOTQ4ZGVkZGI0MWZjMjcyMTFhNTBhNzMyODk2ZjIzMjM4NDk4MWY1YjUxZjkyNWYzIiwidGFnIjoiIn0%3D' \
  -H 'X-CSRF-TOKEN: Mb372xvNwBKxZ7jprstigDcnoEKbw6QLKfawse3r'

The result will be some json resembling:

{"total":2,"oneday":1,"oneweek":1,"oneweekplus":null}

Python code to delete a machine using a CSRF Token:

#!/usr/bin/env python3

import requests
import subprocess
import sys
import logging

# Configure logging
logging.basicConfig(
    filename='/var/log/munkireport_unenroll.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def munkireport_delete_machine(serial_number, MRadmin, MRpassword):
    """
    Deletes a machine from MunkiReport using provided credentials.

    Args:
        serial_number (str): The serial number of the machine to delete.
        MRadmin (str): The username for MunkiReport LOCAL authentication.
        MRpassword (str): The password for MunkiReport LOCAL authentication.

    Returns:
        int: HTTP status code of the DELETE request.
    """
    # Initialize a session to persist cookies
    ses = requests.Session()

    # Authentication URL
    auth_url = 'https://munkireport.ecuad.ca/index.php?/auth/login'

    try:
        # Authenticate with MunkiReport using LOCAL credentials
        auth_response = ses.post(auth_url, data={'login': MRadmin, 'password': MRpassword}, timeout=10)
        auth_response.raise_for_status()
        logging.info(f"Authenticated as {MRadmin}")
    except requests.exceptions.RequestException as e:
        logging.error(f"Authentication failed: {e}")
        print(f"Authentication failed: {e}")
        sys.exit(1)

    # Extract CSRF token from cookies
    csrf_token = ses.cookies.get("CSRF-TOKEN")
    if not csrf_token:
        logging.error("CSRF token not found. Authentication may have failed.")
        print("CSRF token not found. Authentication may have failed.")
        sys.exit(1)

    # Construct the DELETE URL
    delete_url = f'https://munkireport.ecuad.ca/index.php?/manager/delete_machine/{serial_number}'

    # Set headers with CSRF token
    headers = {"X-CSRF-TOKEN": csrf_token}

    try:
        # Send DELETE request to MunkiReport API
        delete_response = ses.delete(delete_url, headers=headers, timeout=10)
        delete_response.raise_for_status()
        logging.info(f"Successfully deleted machine {serial_number}. Status Code: {delete_response.status_code}")
        print(f"Successfully deleted machine {serial_number}. Status Code: {delete_response.status_code}")
        return delete_response.status_code
    except requests.exceptions.HTTPError as http_err:
        logging.error(f"HTTP error occurred while deleting machine {serial_number}: {http_err} - {delete_response.text}")
        print(f"Failed to delete machine {serial_number}: {http_err} - {delete_response.text}")
        sys.exit(1)
    except requests.exceptions.RequestException as e:
        logging.error(f"Error occurred while deleting machine {serial_number}: {e}")
        print(f"Failed to delete machine {serial_number}: {e}")
        sys.exit(1)

def main():
    # Hardcoded credentials for testing purposes
    MRadmin = "api_user"           # Replace with your LOCAL API username
    MRpassword = "YourPassword123" # Replace with your LOCAL API password

    try:
        # Retrieve the serial number of the current machine
        serial = subprocess.check_output(
            'ioreg -d2 -c IOPlatformExpertDevice | grep IOPlatformSerialNumber | cut -d \'"\' -f4',
            shell=True
        )
        serial_number = serial.decode().strip()
    except subprocess.CalledProcessError as e:
        logging.error(f"Failed to retrieve serial number: {e}")
        print(f"Failed to retrieve serial number: {e}")
        sys.exit(1)

    if serial_number:
        logging.info(f"Attempting to delete machine with Serial Number: {serial_number}")
        print(f"Attempting to delete machine with Serial Number: {serial_number}")
        munkireport_delete_machine(serial_number, MRadmin, MRpassword)
    else:
        logging.error("Invalid serial number provided.")
        print("Invalid serial number provided.")
        sys.exit(1)

if __name__ == "__main__":
    main()
Clone this wiki locally