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();