Added support for pre-defined scripts.

Ylian Saint-Hilaire committed Aug 19, 2022 at 20:15 UTC f6c38837358d86602e9e5ac3eda1b4eb1c49835c
4 files changed +136 -6
meshcentral-config-schema.json
+33
@@ -383,6 +383,39 @@
383 "expire": { "type": "number", "description": "When set, limits the self-created guest sharing link to this number of minutes." }
384 }
385 },
386 + "PreconfiguredScripts": {
387 + "type": "array",
388 + "default": null,
389 + "description": "When set, your can try click the run button to run on of these scripts on the remote device.",
390 + "items": {
391 + "type": "object",
392 + "required": [ "name", "type" ],
393 + "properties": {
394 + "name": {
395 + "description": "Name of the script.",
396 + "type": "string"
397 + },
398 + "type": {
399 + "description": "The type of script.",
400 + "type": "string",
401 + "enum": [ "bat", "ps1", "sh", "agent" ]
402 + },
403 + "runas": {
404 + "description": "How to run this script, does not appy to agent scripts.",
405 + "type": "string",
406 + "enum": ["agent", "userfirst", "user"]
407 + },
408 + "cmd": {
409 + "description": "The command or \\r\\n seperated commands to run, if set do not use the file key.",
410 + "type": "string"
411 + },
412 + "file": {
413 + "description": "The script file path and name, if set do not use the cmd key. This file path starts in meshcentral-data.",
414 + "type": "string"
415 + }
416 + }
417 + }
418 + },
419 "preConfiguredRemoteInput": {
420 "type": "array",
421 "default": null,
meshuser.js
+49 -3
@@ -593,6 +593,18 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
593 if ((typeof domain.terminal.linuxshell == 'string') && (domain.terminal.linuxshell != 'any')) { serverinfo.linuxshell = domain.terminal.linuxshell; }
594 }
595 if (Array.isArray(domain.preconfiguredremoteinput)) { serverinfo.preConfiguredRemoteInput = domain.preconfiguredremoteinput; }
596 + if (Array.isArray(domain.preconfiguredscripts)) {
597 + const r = [];
598 + for (var i in domain.preconfiguredscripts) {
599 + const types = ['', 'bat', 'ps1', 'sh', 'agent']; // 1 = Windows Command, 2 = Windows PowerShell, 3 = Linux, 4 = Agent
600 + const script = domain.preconfiguredscripts[i];
601 + if ((typeof script.name == 'string') && (script.name.length <= 32) && (typeof script.type == 'string') && ((typeof script.file == 'string') || (typeof script.cmd == 'string'))) {
602 + const s = { name: script.name, type: types.indexOf(script.type.toLowerCase()) };
603 + if (s.type > 0) { r.push(s); }
604 + }
605 + }
606 + serverinfo.preConfiguredScripts = r;
607 + }
608
609 // Send server information
610 try { ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: serverinfo })); } catch (ex) { }
@@ -2783,8 +2795,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2795 case 'runcommands':
2796 {
2797 if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
2786 - if (typeof command.type != 'number') break; // Check command type
2787 - if (typeof command.runAsUser != 'number') { command.runAsUser = 0; } // Check runAsUser
2798 + if (typeof command.presetcmd != 'number') {
2799 + if (typeof command.type != 'number') break; // Check command type
2800 + if (typeof command.runAsUser != 'number') { command.runAsUser = 0; } // Check runAsUser
2801 + }
2802
2803 const processRunCommand = function (command) {
2804 for (i in command.nodeids) {
@@ -2873,7 +2887,39 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2887 }
2888 }
2889
2876 - if (typeof command.cmdpath == 'string') {
2890 + if (typeof command.presetcmd == 'number') {
2891 + // If a pre-set command is used, load the command
2892 + if (Array.isArray(domain.preconfiguredscripts) == false) return;
2893 + const script = domain.preconfiguredscripts[command.presetcmd];
2894 + if (script == null) return;
2895 + delete command.presetcmd;
2896 +
2897 + // Decode script type
2898 + const types = ['', 'bat', 'ps1', 'sh', 'agent']; // 1 = Windows Command, 2 = Windows PowerShell, 3 = Linux, 4 = Agent
2899 + if (typeof script.type == 'string') { const stype = types.indexOf(script.type.toLowerCase()); if (stype > 0) { command.type = stype; } }
2900 + if (command.type == null) return;
2901 +
2902 + // Decode script runas
2903 + if (command.type != 4) {
2904 + const runAsModes = ['agent', 'userfirst', 'user']; // 0 = AsAgent, 1 = UserFirst, 2 = UserOnly
2905 + if (typeof script.runas == 'string') { const srunas = runAsModes.indexOf(script.runas.toLowerCase()); if (srunas >= 0) { command.runAsUser = srunas; } }
2906 + }
2907 +
2908 + if (typeof script.file == 'string') {
2909 + // The pre-defined script commands are in a file, load it
2910 + const scriptPath = parent.common.joinPath(parent.parent.datapath, script.file);
2911 + fs.readFile(scriptPath, function (err, data) {
2912 + // If loaded correctly, run loaded commands
2913 + if ((err != null) || (data == null) || (data.length == 0) || (data.length > 65535)) return;
2914 + command.cmds = data.toString();
2915 + processRunCommand(command);
2916 + });
2917 + } else if (typeof script.cmd == 'string') {
2918 + // The pre-defined script commands are right in the config.json, use that
2919 + command.cmds = script.cmd;
2920 + processRunCommand(command);
2921 + }
2922 + } else if (typeof command.cmdpath == 'string') {
2923 // If a server command path is used, load the script from the path
2924 var file = parent.getServerFilePath(user, domain, command.cmdpath);
2925 if (file != null) {
sample-config-advanced.json
+25
@@ -243,6 +243,31 @@
243 }
244 ]
245 },
246 + "PreconfiguredScripts": [
247 + {
248 + "name": "Run NotePad as user",
249 + "file": "scripts/notepad.bat",
250 + "type": "bat",
251 + "runas": "user"
252 + },
253 + {
254 + "name": "Run NotePad as agent",
255 + "cmd": "notepad.exe",
256 + "type": "bat",
257 + "runas": "agent"
258 + },
259 + {
260 + "name": "Run echo",
261 + "cmd": "echo \"hello world\"",
262 + "type": "sh",
263 + "runas": "agent"
264 + },
265 + {
266 + "name": "Agent Update",
267 + "cmd": "agentupdate",
268 + "type": "agent"
269 + }
270 + ],
271 "PreconfiguredRemoteInput": [
272 {
273 "name": "CompanyUrl",
views/default.handlebars
+29 -3
@@ -138,7 +138,10 @@
138 <span id="deskPreConfigShortcutContextMenu2"></span>
139 <div class="cmtext" onclick="cmdeskpreconfigtypeaction(-1,event)">Customize...</div>
140 </div>
141 -
141 + <div id="deskPreConfigScriptContextMenu" class="contextMenu noselect" style="display:none;min-width:0px;max-height:calc(80% - 50px);overflow-y:auto">
142 + <span id="deskPreConfigScriptContextMenu1"></span>
143 + <span id="deskPreConfigScriptContextMenu2"></span>
144 + </div>
145 <!--
146 <div id="pluginTabContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
147 <div id="cxclose" class="cmtext" onclick="pluginTabClose(event)">Close Tab</div>
@@ -703,7 +706,7 @@
706 <span id="DeskTimer" style="line-height:22px" title="Session time"></span>
707 <input id=DeskToolsButton type=button value=Tools title="Toggle tools view" onkeypress="return false" onkeydown="return false" onclick="toggleDeskTools()" />
708 <span>&nbsp;</span>
706 - <span id=DeskRunButton class="deskarea" title="Run a script on this computer"><img class="desktopButtons" src='images/icon-play.png' onclick=runDeviceCmd() height=16 width=16 style=padding-top:2px /></span>
709 + <span id=DeskRunButton cmenu="deskPreConfigScriptContextMenu" class="deskarea" title="Run a script on this computer"><img class="desktopButtons" src='images/icon-play.png' onclick=runDeviceCmd() height=16 width=16 style=padding-top:2px /></span>
710 <span id=DeskChatButton class="deskarea" title="Open chat window to this computer"><img class="desktopButtons" src='images/icon-chat.png' onclick=deviceChat(event) height=16 width=16 style=padding-top:2px /></span>
711 <span id=DeskNotifyButton title="Display a notification on the remote computer"><img class="desktopButtons" src='images/icon-notify.png' onclick=deviceToastFunction() height=16 width=16 style=padding-top:2px /></span>
712 <span id=DeskLockButton title="Lock the remote computer"><img src='images/icon-lock.png' class="desktopButtons" onclick=deviceLockFunction() height=16 width=16 /></span>
@@ -2373,11 +2376,24 @@
2376 }
2377 }
2378 if (serverinfo.preConfiguredRemoteInput) {
2379 + // Setup the pre-configured keyboard remote input strings
2380 var x = '';
2381 for (var i in serverinfo.preConfiguredRemoteInput) { x += '<div class="cmtext" onclick="cmdeskpreconfigtypeaction(' + i + ',event)">' + EscapeHtml(serverinfo.preConfiguredRemoteInput[i].name) + '</div>'; }
2382 if (x != '') { x += '<hr />'; }
2383 QH('deskPreConfigShortcutContextMenu1', x);
2384 }
2385 + if (serverinfo.preConfiguredScripts) {
2386 + // Setup the pre-configured scripts
2387 + var x = '', y = '';
2388 + for (var i in serverinfo.preConfiguredScripts) {
2389 + var s = serverinfo.preConfiguredScripts[i];
2390 + var z = '<div class="cmtext" onclick="cmdeskpreconfigscriptaction(' + i + ',event)">' + EscapeHtml(serverinfo.preConfiguredScripts[i].name) + '</div>';
2391 + if (s.type != 3) { x += z; }
2392 + if (s.type >= 3) { y += z; }
2393 + }
2394 + QH('deskPreConfigScriptContextMenu1', x); // Windows devices
2395 + QH('deskPreConfigScriptContextMenu2', y); // Other devices
2396 + }
2397 break;
2398 }
2399 case 'userinfo': {
@@ -6265,6 +6281,10 @@
6281 else { showDeskTypeEx(deskKeyboardStrings[action - 1000].v); } // Type user pre-configured input string
6282 }
6283
6284 + function cmdeskpreconfigscriptaction(action) {
6285 + meshserver.send({ action: 'runcommands', nodeids: [ currentNode._id ], presetcmd: action });
6286 + }
6287 +
6288 function p13deletefileCm(b, file) {
6289 files.sendText({ action: 'rm', reqid: 1, path: p13filetreelocation.join('/'), delfiles: [ file.n ], rec: false });
6290 p13folderup(999);
@@ -6300,6 +6320,7 @@
6320 QV('deskPlayerContextMenu', false);
6321 QV('deskKeyShortcutContextMenu', false);
6322 QV('deskPreConfigShortcutContextMenu', false);
6323 + QV('deskPreConfigScriptContextMenu', false);
6324 QV('expandAllContextMenu', false);
6325 //QV('pluginTabContextMenu', false);
6326 contextelement = null;
@@ -6951,6 +6972,10 @@
6972 if (!currentNode || currentNode._id != node._id || refresh == true) {
6973 currentNode = node;
6974
6975 + // Pre defined scripts
6976 + QV('deskPreConfigScriptContextMenu1', (currentNode.mtype == 2) && isWindowsNode(currentNode)); // Windows devices
6977 + QV('deskPreConfigScriptContextMenu2', (currentNode.mtype == 2) && !isWindowsNode(currentNode)); // Other devices
6978 +
6979 // Device Notification
6980 QV('p10deviceNotify', (currentNode.sessions != null) && ((currentNode.sessions.kvm != null) || (currentNode.sessions.terminal != null) || (currentNode.sessions.files != null) || (currentNode.sessions.tcp != null) || (currentNode.sessions.udp != null)));
6981 QV('p10deviceStar', stars[currentNode._id] == 1);
@@ -7221,7 +7246,7 @@
7246 if (((meshrights & (4 + 8 + 64 + 262144)) != 0) && (node.mtype < 3) && ((node.agent == null) || (node.agent.id != 34))) { x += '<input type=button value="' + "Actions" + '" title="' + "Perform power actions on the device" + '" onclick=deviceActionFunction() />'; }
7247 x += '<input type=button value="' + "Notes" + '" title="' + "View notes about this device" + '" onclick=showNotes(' + ((meshrights & 128) == 0) + ',"' + encodeURIComponentEx(node._id) + '") />';
7248 x += '<input type=button value="' + "Log Event" + '" title="' + "Write an event for this device" + '" onclick=writeDeviceEvent("' + encodeURIComponentEx(node._id) + '") />';
7224 - if ((node.mtype == 2) && (connectivity & 1) && ((meshrights & 131072) != 0)) { x += '<input type=button value="' + "Run" + '" title="' + "Run commands on this device" + '" onclick=runDeviceCmd("' + encodeURIComponentEx(node._id) + '") />'; }
7249 + if ((node.mtype == 2) && (connectivity & 1) && ((meshrights & 131072) != 0)) { x += '<input type=button cmenu=deskPreConfigScriptContextMenu value="' + "Run" + '" title="' + "Run commands on this device" + '" onclick=runDeviceCmd("' + encodeURIComponentEx(node._id) + '") />'; }
7250 if (node.mtype != 4) {
7251 if ((meshrights & 8) && ((connectivity & 1) || ((node.pmt == 1) && ((features2 & 2) != 0)))) { x += '<input type=button value="' + "Message" + '" title="' + "Display a text message on the remote device" + '" onclick=deviceMessageFunction() />'; }
7252 //if ((connectivity & 1) && (meshrights & 8) && (node.agent.id < 5)) { x += '<input type=button value=Toast title="' + "Display a text message of the remote device" + '" onclick=deviceToastFunction() />'; }
@@ -17899,6 +17924,7 @@
17924 }
17925 function round(value, precision) { var multiplier = Math.pow(10, precision || 0); return Math.round(value * multiplier) / multiplier; }
17926 function safeNewWindow(url, target) { var newWindow = window.open(url, target, 'noopener,noreferrer'); if (newWindow) { newWindow.opener = null; } }
17927 + function isWindowsNode(node) { if ((node.mtype != 2) || (node.agent == null) || (node.agent.id == null)) return false; return ([1,2,3,4,21,22,34].indexOf(node.agent.id) >= 0); }
17928
17929 // Webkit seems to have a problem with "download" tag causing "network error", but openning the download in a hidden frame fixes it.
17930 // So we do that for all browsers except FireFox