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> </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