import fs from 'fs';
import path from 'path';
import axios from 'axios';
var merakiNetworks = [];
var config = JSON.parse(fs.readFileSync('./config/config.json'));
var devices = JSON.parse(fs.readFileSync('./enriched-meraki.csv.json'));
async function showTime() {
for (var device of devices) {
if (device.meraki && device.meraki.connection == "Wired" && (device.meraki.vlan == "103" || device.meraki.vlan == "143" || device.meraki.vlan == "145" || device.meraki.vlan == "310" || device.meraki.vlan == "360")) {
console.log(`Working on ${device.device_friendly_name}.`)
await updateSwitchPort(device.meraki.deviceSerial, device.meraki.switchport, {
enabled: false, vlan: 340, accessPolicyType: "Sticky MAC allow list",
stickyMacAllowList: [],
stickyMacAllowListLimit: 1
})
await wait(10)
await updateSwitchPort(device.meraki.deviceSerial, device.meraki.switchport, { enabled: true })
//await sendMDMCommand(device.device_id, {
// "CommandXml": "RequestTypeRestartDevice"
//})
//await wait(30)
//await updateSwitchPort(device.meraki.deviceSerial, device.meraki.switchport, { enabled: true, vlan: 340 })
//await wait(30)
//await sendMDMCommand(device.device_id, {
// "CommandXml": "RequestTypeDeviceInformationQueriesModelSerialNumberOSVersion"
//}
//)
}
}
}
async function sendMDMCommand(deviceId, command) {
const tokenResponse = await axios.post(
'https://na.uemauth.workspaceone.com/connect/token',
new URLSearchParams({
grant_type: 'client_credentials',
client_id: '4e43d8a448fd423984776c0b376d9047',
client_secret: '0F858A7DC18563D057176BE1BF11A7B3',
}),
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}
);
const accessToken = tokenResponse.data.access_token;
const baseUrl = `https://as1991.awmdm.com/api`; // e.g., as1991.awmdm.com
const headers = {
Authorization: `Bearer ${accessToken}`,
'aw-tenant-code': 'cMrIjIsQkKSj0N7giRG36o9OgvNHhuG4+dQGkZXm7G8=',
Accept: 'application/json'
};
try {
const response = await axios.post(
`${baseUrl}/mdm/devices/${deviceId}/commands?command=CustomMdmCommand`,
command,
{ headers }
);
console.log(`Command sent successfully: ${response.data}`);
return response.data;
} catch (error) {
console.error('Error sending MDM Query:');
if (error.response) {
console.error('Status:', error.response.status);
console.error('Response:', error.response.data);
} else {
console.error(error.message);
}
//throw error;
}
}
async function updateSwitchPort(serial, portId, settings) {
const baseUrl = 'https://api.meraki.com/api/v1';
try {
const response = await axios.put(
`${baseUrl}/devices/${serial}/switch/ports/${portId}`,
settings,
{
headers: {
'X-Cisco-Meraki-API-Key': config.auth.meraki.apiKey,
'Content-Type': 'application/json'
}
}
);
//console.log('Success:', response.data);
return "Port updated.";
} catch (error) {
console.error('Error updating port:');
console.error(error.response?.data || error.message);
throw error;
}
}
async function findMerakiNetworkId(storeNum) {
var startTime = new Date().getTime();
var networkSearchTerm = Number(storeNum).toString().padStart(5, "0");
try {
// Filter networks by partial name match (case-insensitive)
const matchingNetworks = merakiNetworks.filter(net =>
(net.name || '').toLowerCase().includes(networkSearchTerm.toLowerCase())
);
console.log(matchingNetworks)
if (matchingNetworks.length === 0) {
return null;
}
if (matchingNetworks.length > 1) {
console.log(`Multiple networks match '${networkSearchTerm}':`);
matchingNetworks.forEach(net => console.log(`- ${net.name} (ID: ${net.id})`));
console.log('Using the first match...');
}
return matchingNetworks[0].id;
} catch (error) {
console.error(`Error finding Meraki Network: ${error}`);
return null;
}
}
async function cacheMerakiNetworks() {
var startTime = new Date().getTime();
try {
let allNetworks = [];
let nextUrl = `https://api.meraki.com/api/v1/organizations/${config.auth.meraki.orgId}/networks?perPage=1000`;
while (nextUrl) {
const response = await axios.get(nextUrl, {
headers: {
'X-Cisco-Meraki-API-Key': config.auth.meraki.apiKey,
'Content-Type': 'application/json'
}
});
const pageNetworks = response.data;
allNetworks = allNetworks.concat(pageNetworks);
//logger(`cacheMerakiNetworks()`, `Fetched ${pageNetworks.length} networks (total so far: ${allNetworks.length})`);
// Log raw header
//logger(`cacheMerakiNetworks()`, `Link header raw: ${response.headers.link || '(none)'}`);
// Robust next URL extraction
const linkHeader = response.headers.link;
let foundNext = null;
if (linkHeader) {
//logger(`cacheMerakiNetworks()`, `Full Link header (raw): ${linkHeader}`);
const parts = linkHeader.split(',');
//logger(`cacheMerakiNetworks()`, `Split into ${parts.length} parts`);
for (const part of parts) {
const trimmed = part.trim();
//logger(`cacheMerakiNetworks()`, `Examining part: "${trimmed}"`);
// Forgiving checks: lower case, no quotes required, partial match
const lowerTrimmed = trimmed.toLowerCase();
if (lowerTrimmed.includes('rel=next') || lowerTrimmed.includes('rel="next"') || lowerTrimmed.includes("rel='next'")) {
//logger(`cacheMerakiNetworks()`, `→ Detected rel=next in: "${trimmed}"`);
const urlMatch = trimmed.match(/<([^>]+)>/);
if (urlMatch && urlMatch[1]) {
foundNext = urlMatch[1].trim(); // extra trim just in case
//logger(`cacheMerakiNetworks()`, `→ Extracted next URL: ${foundNext}`);
break;
} else {
//logger(`cacheMerakiNetworks()`, `→ URL match failed on that part`);
}
}
}
}
nextUrl = foundNext;
if (!nextUrl) {
//logger(`cacheMerakiNetworks()`, `No next page detected – ending loop`);
} else {
//logger(`cacheMerakiNetworks()`, `Advancing to next URL: ${nextUrl}`);
}
}
merakiNetworks = allNetworks;
logger(`cacheMerakiNetworks()`, `Cached ${merakiNetworks.length} networks. (${new Date().getTime() - startTime}ms)`);
} catch (error) {
console.error('Meraki Networks API error:', error.message);
if (error.response) {
console.error('Status:', error.response.status);
console.error('Data:', error.response.data);
}
}
}
function logger(functionName, message) {
var d = new Date();
var year = d.getFullYear();
var month = (d.getMonth() + 1).toString().padStart(2, "0");
var day = d.getDate().toString().padStart(2, "0");
let logFile = path.join(`./logs/${year}${month}${day}.log`);
console.log(d.toLocaleString() + " " + functionName + ": " + message);
fs.appendFileSync(logFile, d.toLocaleString() + " " + functionName + ": " + message + "\n");
}
function wait(seconds) {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
showTime();