API use case: Fetching application versions for all devices

Here's an example use case that's requested frequently from the mambo EMM team, the ability to view Android application versions across all deployed devices.

Due to local network or device conditions, applications can't always be updated when a new version is released. But how do you monitor a major version deployment?

Pulling application version through the API

In this use case, we are looking to understand what version of Google Chrome is deployed to our active devices.

  • We want to validate Google Chrome version 100 has been deployed to devices
  • We want a CSV generated for ease of data manipulation later
  • In the CSV, we're labelling the app Google Chrome, but for other apps, this can be adjusted as desired
  • We have a very simple validator offering a Yes or No for version match, to make CSV manipulation easier later
  • We have fetched our Team ID and Access token already, and have input it where XXXXX currently sits against TEAM_ID and ACCESS_TOKEN in the below script respectively.

The environment we're working with is JavaScript, and we'll use the following script, executed through Node.js. When complete, the script will output a CSV file named app-version-check.csv in the folder the script was executed from.

Set up node

  1. nodeJS should be installed as described for your OS, the relevant page is here (opens new window)
  2. Install CSV-Stringify, which we need for the CSV portion of the below script, and axios as a dependency, with npm install csv-stringify axios

Run the script

  1. Save the below to a local destination on your PC as a .js file.
  2. Run the script: node ./scriptname.js
  3. The CSV will be output to the same directory the script is run from

The script

const fs = require('fs');

// Run `npm install csv-stringify axios` before running this script
const { stringify: csvStringify } = require('csv-stringify');

/**
 * Below are the options you can define before running the script
 */

// This requires an access token with "view policies, enrolment tokens, and devices" permissions
const ACCESS_TOKEN = 'XXXXX';

// Define the team ID to fethch for
const TEAM_ID = 'XXXXX';

// Define the package to filter for
const PACKAGE_NAME = 'com.android.chrome';

// Define the package label for CSV
const PACKAGE_LABEL = 'Google Chrome';

// Define version match
const PACKAGE_VERSION_MATCH = '100';

// Define where to write the CSV file
const FILE_PATH = `${__dirname}/app-version-check.csv`;

/**
 * Below is the executable code of the script
 */

// Define CSV headers as a map of the `deviceFilter()` object to readable label
const csvHeaders = {
    imei: 'IMEI',
    deployedVersion: `${PACKAGE_LABEL} Deployed Version`,
    versionMatch: `Meets ${PACKAGE_LABEL} Version Match`
};

/** Request setup */

// Define auth headers
const headers = new Headers();
headers.append('Authorization', `Bearer ${ACCESS_TOKEN}`);
headers.append('Accept', 'application/json');

// Setup request
const requestOpts = {
    method: 'GET',
    headers
};
const BASE_URL = `https://dashboard.mambomobility.com/api/platform/0/v1/teams/${TEAM_ID}/devices?includes[]=policy&limit=50&q[]=details:not:null`;

// Define a request state to process devices in chunks
const state = {
    page: 0,
    fetched: 0,
    hasMore: true
};
/** Define CSV flow */

// Initiate CSV stream
const csvStream = csvStringify({
    header: true,
    columns: csvHeaders
});

// Append to CSV file when receiving data
csvStream.on('data', (row) => fs.appendFile(FILE_PATH, row, () => void 0));

// Log CSV errors
csvStream.on('error', (err) => {
    console.error(`CSV stream error occured:`, err);
});

// Define the device filtering logic
function deviceFilter(device) {
    // Find app from device app reports
    const app = device?.details?.applicationReports?.find(
        (report) => report.packageName === PACKAGE_NAME
    );

    // Map device to package version match
    return {
        imei: device.imei,
        deployedVersion: app?.versionName ?? 'N/A',
        versionMatch: app?.versionName?.startsWith(PACKAGE_VERSION_MATCH)
            ? 'Yes'
            : 'No'
    };
}

// Define the request flow
async function run() {
    // Empty file to begin writing
    await new Promise((resolve) => fs.writeFile(FILE_PATH, '', resolve));

    // Loop until all devices are processed
    while (state.hasMore) {
        // Fetch devices
        const request = new Request(`${BASE_URL}&page=${++state.page}`);
        const res = await fetch(request, requestOpts);

        // Parse response to JSON
        const data = await res.json();

        // Log response
        console.log('RECEIVED QUERY RESPONSE:', data);

        // Process devices
        data.data.forEach((device) => {
            // Map device to CSV data
            const formattedData = deviceFilter(device);

            // Write to CSV
            csvStream.write(formattedData);
        });

        // Update state
        state.fetched += data.data.length;
        if (data.total <= state.fetched) {
            state.hasMore = false;
        }
    }

    // End the CSV stream
    csvStream.end();
}

// Run the script
run();

Notes

  1. The script can be run as often as desired, and will always overwrite the output CSV file unless ${__dirname}/app-version-check.csv is updated.