1. Added user-consent

2. Added Diagnostic Agent test methods for 'console'
This commit is contained in:
Bryan Roe 2019-06-14 13:57:05 -07:00
parent 9c2da0696f
commit 6611b56cc6

View File

@ -22,6 +22,7 @@ process.on('uncaughtException', function (ex) {
//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); });
// Mesh Rights
var MNG_ERROR = 65;
var MESHRIGHT_EDITMESH = 1;
var MESHRIGHT_MANAGEUSERS = 2;
var MESHRIGHT_MANAGECOMPUTERS = 4;
@ -36,10 +37,12 @@ var MESHRIGHT_NOFILES = 1024;
var MESHRIGHT_NOAMT = 2048;
var MESHRIGHT_LIMITEDINPUT = 4096;
function createMeshCore(agent) {
function createMeshCore(agent)
{
var obj = {};
if (process.platform == 'darwin' && !process.versions) {
if (process.platform == 'darwin' && !process.versions)
{
// This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = '';
@ -48,18 +51,21 @@ function createMeshCore(agent) {
child.stdin.write(" if(c[1]==\"dict\"){ split(a[2], d, \"</dict>\"); if(split(d[1], truval, \"<true/>\")>1) { split(truval[1], kn1, \"<key>\"); split(kn1[2], kn2, \"</key>\"); print kn2[1]; } }");
child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n");
child.waitExit();
if (child.stdout.str.trim() == 'Crashed') {
if (child.stdout.str.trim() == 'Crashed')
{
child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = '';
child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n");
child.waitExit();
if (parseInt(child.stdout.str.trim()) == process.pid) {
if (parseInt(child.stdout.str.trim()) == process.pid)
{
// The currently running MeshAgent is us, so we can continue with the update
var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString();
var tokens = plist.split('<key>KeepAlive</key>');
if (tokens[1].split('>')[0].split('<')[1] == 'dict') {
if (tokens[1].split('>')[0].split('<')[1] == 'dict')
{
var tmp = tokens[1].split('</dict>');
tmp.shift();
tokens[1] = '\n <true/>' + tmp.join('</dict>');
@ -114,6 +120,133 @@ function createMeshCore(agent) {
}
}
// Create Secure IPC for Diagnostic Agent Communications
obj.DAIPC = require('net').createServer();
if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (ee) { } }
obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC');
try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH }); } catch (e) { }
obj.DAIPC.on('connection', function (c)
{
c._send = function (j)
{
var data = JSON.stringify(j);
var packet = Buffer.alloc(data.length + 4);
packet.writeUInt32LE(data.length + 4, 0);
Buffer.from(data).copy(packet, 4);
this.end(packet);
};
this._daipc = c;
c.parent = this;
c.on('end', function () { console.log('Connection Closed'); this.parent._daipc = null; });
c.on('data', function (chunk)
{
if (chunk.length < 4) { this.unshift(chunk); return; }
var len = chunk.readUInt32LE(0);
if (len > 8192) { this.parent._daipc = null; this.end(); return; }
if (chunk.length < len) { this.unshift(chunk); return; }
var data = chunk.slice(4, len);
try
{
data = JSON.parse(data.toString());
}
catch(de)
{
this.parent._daipc = null; this.end(); return;
}
if (!data.cmd) { this.parent._daipc = null; this.end(); return; }
try
{
switch(data.cmd)
{
case 'query':
switch(data.value)
{
case 'connection':
data.result = require('MeshAgent').ConnectedServer;
this._send(data);
break;
}
break;
default:
this.parent._daipc = null; this.end(); return;
break;
}
}
catch(xe)
{
this.parent._daipc = null; this.end(); return;
}
});
});
function diagnosticAgent_uninstall()
{
require('service-manager').manager.uninstallService('meshagentDiagnostic');
require('task-scheduler').delete('meshagentDiagnostic/periodicStart');
};
function diagnosticAgent_installCheck(install)
{
try
{
var diag = require('service-manager').manager.getService('meshagentDiagnostic');
return (diag);
}
catch (e)
{
}
if (!install) { return (null); }
var svc = null;
try
{
require('service-manager').manager.installService(
{
name: 'meshagentDiagnostic',
displayName: 'Mesh Agent Diagnostic Service',
description: 'Mesh Agent Diagnostic Service',
servicePath: process.execPath,
parameters: ['-recovery']
//files: [{ newName: 'diagnostic.js', _buffer: Buffer.from('LyoNCkNvcHlyaWdodCAyMDE5IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgaG9zdCA9IHJlcXVpcmUoJ3NlcnZpY2UtaG9zdCcpLmNyZWF0ZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpOw0KdmFyIFJlY292ZXJ5QWdlbnQgPSByZXF1aXJlKCdNZXNoQWdlbnQnKTsNCg0KaG9zdC5vbignc2VydmljZVN0YXJ0JywgZnVuY3Rpb24gKCkNCnsNCiAgICBjb25zb2xlLnNldERlc3RpbmF0aW9uKGNvbnNvbGUuRGVzdGluYXRpb25zLkxPR0ZJTEUpOw0KICAgIGhvc3Quc3RvcCA9IGZ1bmN0aW9uKCkNCiAgICB7DQogICAgICAgIHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZSgnbWVzaGFnZW50RGlhZ25vc3RpYycpLnN0b3AoKTsNCiAgICB9DQogICAgUmVjb3ZlcnlBZ2VudC5vbignQ29ubmVjdGVkJywgZnVuY3Rpb24gKHN0YXR1cykNCiAgICB7DQogICAgICAgIGlmIChzdGF0dXMgPT0gMCkNCiAgICAgICAgew0KICAgICAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IFNlcnZlciBjb25uZWN0aW9uIGxvc3QuLi4nKTsNCiAgICAgICAgICAgIHJldHVybjsNCiAgICAgICAgfQ0KICAgICAgICBjb25zb2xlLmxvZygnRGlhZ25vc3RpYyBBZ2VudDogQ29ubmVjdGlvbiBFc3RhYmxpc2hlZCB3aXRoIFNlcnZlcicpOw0KICAgICAgICBzdGFydCgpOw0KICAgIH0pOw0KfSk7DQpob3N0Lm9uKCdub3JtYWxTdGFydCcsIGZ1bmN0aW9uICgpDQp7DQogICAgaG9zdC5zdG9wID0gZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHByb2Nlc3MuZXhpdCgpOw0KICAgIH0NCiAgICBjb25zb2xlLmxvZygnTm9uIFNlcnZpY2UgTW9kZScpOw0KICAgIFJlY292ZXJ5QWdlbnQub24oJ0Nvbm5lY3RlZCcsIGZ1bmN0aW9uIChzdGF0dXMpDQogICAgew0KICAgICAgICBpZiAoc3RhdHVzID09IDApDQogICAgICAgIHsNCiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdEaWFnbm9zdGljIEFnZW50OiBTZXJ2ZXIgY29ubmVjdGlvbiBsb3N0Li4uJyk7DQogICAgICAgICAgICByZXR1cm47DQogICAgICAgIH0NCiAgICAgICAgY29uc29sZS5sb2coJ0RpYWdub3N0aWMgQWdlbnQ6IENvbm5lY3Rpb24gRXN0YWJsaXNoZWQgd2l0aCBTZXJ2ZXInKTsNCiAgICAgICAgc3RhcnQoKTsNCiAgICB9KTsNCn0pOw0KaG9zdC5vbignc2VydmljZVN0b3AnLCBmdW5jdGlvbiAoKSB7IHByb2Nlc3MuZXhpdCgpOyB9KTsNCmhvc3QucnVuKCk7DQoNCg0KZnVuY3Rpb24gc3RhcnQoKQ0Kew0KDQp9Ow0K', 'base64') }]
});
svc = require('service-manager').manager.getService('meshagentDiagnostic');
}
catch (e)
{
return (null);
}
var proxyConfig = require('global-tunnel').proxyConfig;
var cert = require('MeshAgent').GenerateAgentCertificate('CN=MeshNodeDiagnosticCertificate');
var nodeid = require('tls').loadCertificate(cert.root).getKeyHash().toString('base64');
ddb = require('SimpleDataStore').Create(svc.appWorkingDirectory().replace('\\', '/') + '/meshagentDiagnostic.db');
ddb.Put('disableUpdate', '1');
ddb.Put('MeshID', Buffer.from(require('MeshAgent').ServerInfo.MeshID, 'hex'));
ddb.Put('ServerID', require('MeshAgent').ServerInfo.ServerID);
ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri);
if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); }
if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); }
if (proxyConfig)
{
ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port);
}
else
{
ddb.Put('ignoreProxyFile', '1');
}
require('MeshAgent').SendCommand({ action: 'diagnostic', value: { command: 'register', value: nodeid } });
require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: 'Diagnostic Agent Registered [' + nodeid.length + '/' + nodeid + ']' });
delete ddb;
// Set a recurrent task, to run the Diagnostic Agent every 2 days
require('task-scheduler').create({name: 'meshagentDiagnostic/periodicStart', daily: 2, time: require('tls').generateRandomInteger('0', '23') + ':' + require('tls').generateRandomInteger('0', '59').padStart(2, '0'), service: 'meshagentDiagnostic'});
//require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' });
return (svc);
}
/*
function borderController() {
this.container = null;
@ -191,6 +324,8 @@ function createMeshCore(agent) {
mesh = agent.getMeshApi();
}
mesh.DAIPC = obj.DAIPC;
/*
var AMTScanner = require("AMTScanner");
var scan = new AMTScanner();
@ -815,39 +950,7 @@ function createMeshCore(agent) {
return;
}
// Test the console messaging system
//this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'This is a sample test for remote terminal...' })); // Send a console message back using the console channel, "\n" is supported.
// Perform notification if needed. Toast messages may not be supported on all platforms.
if (this.httprequest.consent && (this.httprequest.consent & 2)) {
try { require('toaster').Toast('MeshCentral', this.httprequest.username + ' started a remote terminal session.'); } catch (ex) { }
}
// Remote terminal using native pipes
if (process.platform == "win32")
{
this.httprequest._term = require('win-terminal').Start(80, 25);
this.httprequest._term.pipe(this, { dataTypeSkip: 1 });
this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false });
this.prependListener('end', function () { this.httprequest._term.end(function () { console.log('Terminal was closed'); }); });
//this.httprequest.process = childProcess.execFile("%windir%\\system32\\cmd.exe");
} else {
if (fs.existsSync("/bin/bash")) {
this.httprequest.process = childProcess.execFile("/bin/bash", ["bash", "-i"], { type: childProcess.SpawnTypes.TERM });
if (process.platform == 'linux') { this.httprequest.process.stdin.write("alias ls='ls --color=auto'\nclear\n"); }
} else {
this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
if (process.platform == 'linux') { this.httprequest.process.stdin.write("stty erase ^H\nalias ls='ls --color=auto'\nPS1='\\u@\\h:\\w\\$ '\nclear\n"); }
}
//if (this.httprequest.process == null) { }
this.httprequest.process.tunnel = this;
this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
this.prependListener('end', function () { this.httprequest.process.kill(); });
}
this.end = function () {
if (process.platform == "win32") {
// Unpipe the web socket
@ -868,11 +971,71 @@ function createMeshCore(agent) {
}
};
// Remote terminal using native pipes
if (process.platform == "win32") {
this.httprequest._term = require('win-terminal').Start(80, 25);
this.httprequest._term.pipe(this, { dataTypeSkip: 1 });
this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false });
this.prependListener('end', function () { this.httprequest._term.end(function () { console.log('Terminal was closed'); }); });
} else {
this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM });
this.httprequest.process.tunnel = this;
this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
this.prependListener('end', function () { this.httprequest.process.kill(); });
this.httprequest.process.stdin.write("stty erase ^H\nalias ls='ls --color=auto'\nclear\n");
}
// Perform notification if needed. Toast messages may not be supported on all platforms.
if (this.httprequest.consent && (this.httprequest.consent & 16)) {
// User Consent Prompt is required
// Send a console message back using the console channel, "\n" is supported.
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' }));
var pr = require('message-box').create('Mesh Central', this.httprequest.username + ' requesting Terminal Access. Grant access?', 10);
pr.ws = this;
this.pause();
pr.then(
function () {
// Success!
this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2)) {
// User Notifications is required
try { require('toaster').Toast('MeshCentral', this.ws.httprequest.username + ' started a remote terminal session.'); } catch (ex) { }
}
this.ws.resume();
},
function (e) {
// User Consent Denied/Failed!
this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() }));
this.ws.end();
});
}
else {
// User Consent Prompt is not required
if (this.httprequest.consent && (this.httprequest.consent & 2)) {
// User Notifications is required
try { require('toaster').Toast('MeshCentral', this.httprequest.username + ' started a remote terminal session.'); } catch (ex) { }
}
this.resume();
}
this.removeAllListeners('data');
this.on('data', onTunnelControlData);
//this.write('MeshCore Terminal Hello');
} else if (this.httprequest.protocol == 2) {
} else if (this.httprequest.protocol == 2)
{
// Check user access rights for desktop
if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) {
// Disengage this tunnel, user does not have the rights to do this!!
@ -882,13 +1045,6 @@ function createMeshCore(agent) {
return;
}
// Test the console messaging system
//this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'This is a sample test for remote desktop...' })); // Send a console message back using the console channel, "\n" is supported.
// Perform notification if needed. Toast messages may not be supported on all platforms.
if (this.httprequest.consent && (this.httprequest.consent & 1)) {
try { require('toaster').Toast('MeshCentral', this.httprequest.username + ' started a remote desktop session.'); } catch (ex) { }
}
// Remote desktop using native pipes
this.httprequest.desktop = { state: 0, kvm: mesh.getRemoteDesktopStream(), tunnel: this };
@ -926,7 +1082,56 @@ function createMeshCore(agent) {
// TODO!!!
}
this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. Pipe the KVM --> Browser images.
// Perform notification if needed. Toast messages may not be supported on all platforms.
if (this.httprequest.consent && (this.httprequest.consent & 8))
{
// User Consent Prompt is required
// Send a console message back using the console channel, "\n" is supported.
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' }));
var pr = require('message-box').create('Mesh Central', this.httprequest.username + ' requesting KVM Access. Grant access?', 10);
pr.ws = this;
this.pause();
pr.then(
function ()
{
// Success!
this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1))
{
// User Notifications is required
try { require('toaster').Toast('MeshCentral', this.ws.httprequest.username + ' started a remote desktop session.'); } catch (ex) { }
}
this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 });
this.ws.resume();
},
function (e)
{
// User Consent Denied/Failed!
this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() }));
//var err = 'User consent: ' + e.toString();
//var b = Buffer.alloc(5 + err.length);
//b.writeUInt16BE(MNG_ERROR, 0);
//b.writeUInt16BE(err.length + 4, 2);
//Buffer.from(err).copy(b, 4);
//this.ws.end(b);
});
}
else
{
// User Consent Prompt is not required
if (this.httprequest.consent && (this.httprequest.consent & 1))
{
// User Notifications is required
try { require('toaster').Toast('MeshCentral', this.httprequest.username + ' started a remote desktop session.'); } catch (ex) { }
}
this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 });
}
this.removeAllListeners('data');
this.on('data', onTunnelControlData);
//this.write('MeshCore KVM Hello!1');
@ -942,12 +1147,41 @@ function createMeshCore(agent) {
return;
}
// Test the console messaging system
//this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'This is a sample test for remote files...' })); // Send a console message back using the console channel, "\n" is supported.
// Perform notification if needed. Toast messages may not be supported on all platforms.
if (this.httprequest.consent && (this.httprequest.consent & 32))
{
// User Consent Prompt is required
// Perform notification if needed
if (this.httprequest.consent && (this.httprequest.consent & 4)) {
try { require('toaster').Toast('MeshCentral', this.httprequest.username + ' started a remote file access.'); } catch (ex) { }
// Send a console message back using the console channel, "\n" is supported.
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' }));
var pr = require('message-box').create('Mesh Central', this.httprequest.username + ' requesting remote File Access. Grant access?', 10);
pr.ws = this;
this.pause();
pr.then(
function () {
// Success!
this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4))
{
// User Notifications is required
try { require('toaster').Toast('MeshCentral', this.ws.httprequest.username + ' started a remote file session.'); } catch (ex) { }
}
this.ws.resume();
},
function (e) {
// User Consent Denied/Failed!
this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() }));
});
}
else {
// User Consent Prompt is not required
if (this.httprequest.consent && (this.httprequest.consent & 4)) {
// User Notifications is required
try { require('toaster').Toast('MeshCentral', this.httprequest.username + ' started a remote file session.'); } catch (ex) { }
}
this.resume();
}
// Setup files
@ -1372,12 +1606,9 @@ function createMeshCore(agent) {
break;
}
case 'toast': {
if (process.platform == 'win32') {
if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else {
try { require('toaster').Toast('MeshCentral', args['_'][0]); response = 'ok'; } catch (ex) { response = ex; }
}
} else {
response = 'Only supported on Windows.';
if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else
{
require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText);
}
break;
}
@ -1763,6 +1994,48 @@ function createMeshCore(agent) {
}
break;
}
case 'diagnostic':
{
if (!mesh.DAIPC.listening)
{
response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system';
break;
}
var diag = diagnosticAgent_installCheck();
if (diag)
{
if (args['_'].length == 1 && args['_'][0] == 'uninstall')
{
diagnosticAgent_uninstall();
response = 'Diagnostic Agent uninstalled';
}
else
{
response = 'Diagnostic Agent installed at: ' + diag.appLocation();
}
}
else
{
if (args['_'].length == 1 && args['_'][0] == 'install')
{
diag = diagnosticAgent_installCheck(true);
if (diag)
{
response = 'Diagnostic agent was installed at: ' + diag.appLocation();
}
else
{
response = 'Diagnostic agent installation failed';
}
}
else
{
response = 'Diagnostic Agent Not installed. To install: diagnostic install';
}
}
if (diag) { diag.close(); diag = null; }
break;
}
default: { // This is an unknown command, return an error message
response = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.';
break;