1
0
mirror of https://github.com/Ylianst/MeshAgent synced 2025-12-06 00:13:33 +00:00

Documented code for agent-installer

This commit is contained in:
Bryan Roe
2022-06-27 14:50:43 -07:00
parent ab94c03594
commit b6349db37e
2 changed files with 107 additions and 17 deletions

File diff suppressed because one or more lines are too long

View File

@@ -15,8 +15,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
//
// This is a helper utility that is used by the Mesh Agent to install itself
// as a background service, on all platforms that the agent supports.
//
try
{
// This peroperty is a polyfill for an Array, to fetch the specified element if it exists, removing the surrounding quotes if they are there
Object.defineProperty(Array.prototype, 'getParameterEx',
{
value: function (name, defaultValue)
@@ -34,6 +41,8 @@ try
return (defaultValue);
}
});
// This property is a polyfill for an Array, to fetch the specified element if it exists
Object.defineProperty(Array.prototype, 'getParameter',
{
value: function (name, defaultValue)
@@ -46,6 +55,7 @@ catch(x)
{ }
try
{
// This property is a polyfill for an Array, to fetch the index of the specified element, if it exists
Object.defineProperty(Array.prototype, 'getParameterIndex',
{
value: function (name)
@@ -66,6 +76,7 @@ catch(x)
{ }
try
{
// This property is a polyfill for an Array, to remove the specified element, if it exists
Object.defineProperty(Array.prototype, 'deleteParameter',
{
value: function (name)
@@ -82,6 +93,7 @@ catch(x)
{ }
try
{
// This property is a polyfill for an Array, to to fetch the value YY of an element XX in the format --XX=YY, if it exists
Object.defineProperty(Array.prototype, 'getParameterValue',
{
value: function (i)
@@ -95,6 +107,7 @@ try
catch(x)
{ }
// This function performs some checks on the parameter structure, to make sure the minimum set of requried elements are present
function checkParameters(parms)
{
var msh = _MSH();
@@ -104,6 +117,7 @@ function checkParameters(parms)
if (msh.fileName != null)
{
// This converts the --fileName parameter of the installer, to the --target=XXX format required by service-manager.js
var i = parms.getParameterIndex('fileName');
if(i>=0)
{
@@ -116,6 +130,7 @@ function checkParameters(parms)
{
if(msh.meshServiceName != null)
{
// This adds the specified service name, to be consumed by service-manager.js
parms.push('--meshServiceName="' + msh.meshServiceName + '"');
}
else
@@ -129,6 +144,8 @@ function checkParameters(parms)
catch(xx)
{
}
// The default is 'Mesh Agent' for Windows, and 'meshagent' for everything else...
if(tmp != (process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'))
{
parms.push('--meshServiceName="' + tmp + '"');
@@ -136,6 +153,8 @@ function checkParameters(parms)
}
}
}
// This is the entry point for installing the service
function installService(params)
{
process.stdout.write('...Installing service');
@@ -145,6 +164,7 @@ function installService(params)
var targetx = params.getParameterIndex('target');
if (targetx >= 0)
{
// Let's remove any embedded spaces in 'target' as that can mess up some OSes
target = params.getParameterValue(targetx);
params.splice(targetx, 1);
target = target.split(' ').join('');
@@ -157,6 +177,7 @@ function installService(params)
proxyFile = proxyFile.split('.exe').join('.proxy');
try
{
// Add this parameter, so the agent instance will be embedded with the Windows User that installed the service
params.push('--installedByUser="' + require('win-registry').usernameToUserKey(require('user-sessions').getProcessOwnerName(process.pid).name) + '"');
}
catch(exc)
@@ -165,6 +186,7 @@ function installService(params)
}
else
{
// On Linux, the --installedByUser property is populated with the UID of the user that is installing the service
var u = require('user-sessions').tty();
var uid = 0;
try
@@ -178,6 +200,9 @@ function installService(params)
proxyFile += '.proxy';
}
// We're going to create the OPTIONS object to hand to service-manager.js. We're going to populate all the properties we can, using
// values that were passed into the installer, using default values for the ones that aren't specified.
var options =
{
name: params.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'),
@@ -191,8 +216,12 @@ function installService(params)
options.description = params.getParameter('description', options.name + ' background service'); params.deleteParameter('description');
if (process.platform == 'win32') { options.companyName = ''; }
if (require('fs').existsSync(proxyFile)) { options.files = [{ source: proxyFile, newName: options.target + '.proxy' }]; }
// If a .proxy file was found, we'll include it in the list of files to be copied when installing the agent
if (require('fs').existsSync(proxyFile)) { options.files = [{ source: proxyFile, newName: options.target + '.proxy' }]; }
// if '--copy-msh' is specified, we will try to copy the .msh configuration file found in the current working directory
var i;
if ((i = params.indexOf('--copy-msh="1"')) >= 0)
{
@@ -209,6 +238,7 @@ function installService(params)
options.installInPlace = true;
}
// We're going to specify what folder the agent should be installed into
if (global._workingpath != null && global._workingpath != '' && global._workingpath != '/')
{
for (i = 0; i < options.parameters.length; ++i)
@@ -224,13 +254,14 @@ function installService(params)
options.parameters.push('--installPath="' + global._workingpath + '"');
}
}
if ((i = options.parameters.getParameterIndex('installPath')) >= 0)
{
options.installPath = options.parameters.getParameterValue(i);
options.installInPlace = false;
options.parameters.splice(i, 1);
}
// If companyName was specified, we're going to move it into the structure
if ((i = options.parameters.getParameterIndex('companyName')) >= 0)
{
options.companyName = options.parameters.getParameterValue(i);
@@ -239,10 +270,12 @@ function installService(params)
try
{
// Let's actually install the service
require('service-manager').manager.installService(options);
process.stdout.write(' [DONE]\n');
if(process.platform == 'win32')
{
// On Windows, we're going to enable this service to be runnable from SafeModeWithNetworking
require('win-bcd').enableSafeModeService(options.name);
}
}
@@ -252,6 +285,9 @@ function installService(params)
process.exit();
}
var svc = require('service-manager').manager.getService(options.name);
// macOS needs a LaunchAgent to help with some usages that need to run from within the user session,
// so we can setup ourselves to accomplish that.
if (process.platform == 'darwin')
{
svc.load();
@@ -274,6 +310,7 @@ function installService(params)
}
}
// For Windows, we're going to add an INBOUND UDP rule for WebRTC Data
if(process.platform == 'win32')
{
var loc = svc.appLocation();
@@ -293,6 +330,8 @@ function installService(params)
require('win-firewall').addFirewallRule(rule);
process.stdout.write(' [DONE]\n');
}
// Let's try to start the service that we just installed
process.stdout.write(' -> Starting service...');
try
{
@@ -304,15 +343,18 @@ function installService(params)
process.stdout.write(' [ERROR]\n');
}
if (process.platform == 'win32') { svc.close(); }
// On Windows we should explicitly close the service manager when we are done, instead of relying on the Garbage Collection, so the service object isn't unnecessarily locked
if (process.platform == 'win32') { svc.close(); }
if (parseInt(params.getParameter('__skipExit', 0)) == 0)
{
process.exit();
}
}
// The last step in uninstalling a service
function uninstallService3(params)
{
// macOS has a LaunchAgent, that we need to uninstall
if (process.platform == 'darwin')
{
process.stdout.write(' -> Uninstalling launch agent...');
@@ -328,16 +370,20 @@ function uninstallService3(params)
process.stdout.write(' [ERROR]\n');
}
}
if (params != null && !params.includes('_stop'))
{
// Since we are done uninstalling a previously installed service, we can continue with installation
installService(params);
}
else
{
// We are going to stop here, if we are only intending to uninstall the service
process.exit();
}
}
// Step 2 in service uninstallation
function uninstallService2(params, msh)
{
var secondaryagent = false;
@@ -345,16 +391,20 @@ function uninstallService2(params, msh)
var dataFolder = null;
var appPrefix = null;
var uninstallOptions = null;
var serviceName = params.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent');
var serviceName = params.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); // get the service name, using the provided defaults if not specified
// Remove the .msh file if present
try { require('fs').unlinkSync(msh); } catch (mshe) { }
if ((i = params.indexOf('__skipBinaryDelete')) >= 0)
{
// We will skip deleting of the actual binary, if this option was provided.
// This will happen if we try to install the service to a location where we are running the installer from.
params.splice(i, 1);
uninstallOptions = { skipDeleteBinary: true };
}
if (params && params.includes('--_deleteData="1"'))
{
// This will facilitate cleanup of the files associated with the agent
dataFolder = params.getParameterEx('_workingDir', null);
appPrefix = params.getParameterEx('_appPrefix', null);
}
@@ -362,17 +412,22 @@ function uninstallService2(params, msh)
process.stdout.write(' -> Uninstalling previous installation...');
try
{
// Let's actually try to uninstall the service
require('service-manager').manager.uninstallService(serviceName, uninstallOptions);
process.stdout.write(' [DONE]\n');
if (process.platform == 'win32')
{
// For Windows, we can remove the entry to enable this service to be runnable from SafeModeWithNetworking
require('win-bcd').disableSafeModeService(serviceName);
}
// Lets try to cleanup the uninstalled service
if (dataFolder && appPrefix)
{
process.stdout.write(' -> Deleting agent data...');
if (process.platform != 'win32')
{
// On Non-Windows platforms, we're going to cleanup using the shell
var levelUp = dataFolder.split('/');
levelUp.pop();
levelUp = levelUp.join('/');
@@ -408,6 +463,7 @@ function uninstallService2(params, msh)
}
else
{
// On Windows, we're going to spawn a command shell to cleanup
var levelUp = dataFolder.split('\\');
levelUp.pop();
levelUp = levelUp.join('\\');
@@ -452,6 +508,7 @@ function uninstallService2(params, msh)
if(secondaryagent)
{
// If a secondary agent was found, remove the CRON job for it
process.stdout.write(' -> removing secondary agent from task scheduler...');
var p = require('task-scheduler').delete(serviceName + 'Diagnostic/periodicStart');
p._params = params;
@@ -470,9 +527,14 @@ function uninstallService2(params, msh)
uninstallService3(params);
}
}
// First step in service uninstall
function uninstallService(params)
{
// Before we uninstall, we need to fetch the service from service-manager.js
var svc = require('service-manager').manager.getService(params.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'));
// We can calculate what the .msh file location is, based on the appLocation of the service
var msh = svc.appLocation();
if (process.platform == 'win32')
{
@@ -483,6 +545,7 @@ function uninstallService(params)
msh = msh + '.msh';
}
// Let's try to stop the service if we think it might be running
if (svc.isRunning == null || svc.isRunning())
{
process.stdout.write(' -> Stopping Service...');
@@ -504,6 +567,7 @@ function uninstallService(params)
{
if (process.platform == 'darwin')
{
// macOS requries us to unload the service
svc.unload();
}
else
@@ -520,11 +584,14 @@ function uninstallService(params)
uninstallService2(params, msh);
}
}
// A previous service installation was found, so lets do some extra processing
function serviceExists(loc, params)
{
process.stdout.write(' [FOUND: ' + loc + ']\n');
if(process.platform == 'win32')
{
// On Windows, we need to cleanup the firewall rules associated with our install path
process.stdout.write(' -> Checking firewall rules for previous installation... [0%]');
var p = require('win-firewall').getFirewallRulesAsync({ program: loc, noResult: true, minimal: true, timeout: 15000 });
p.on('progress', function (c)
@@ -533,6 +600,7 @@ function serviceExists(loc, params)
});
p.on('rule', function (r)
{
// Remove firewall entries for our install path
require('win-firewall').removeFirewallRule(r.DisplayName);
});
p.finally(function ()
@@ -547,23 +615,26 @@ function serviceExists(loc, params)
}
}
// Entry point for -fulluninstall
function fullUninstall(jsonString)
{
var parms = JSON.parse(jsonString);
if (parseInt(parms.getParameter('verbose', 0)) == 0)
{
console.setDestination(console.Destinations.DISABLED);
console.setDestination(console.Destinations.DISABLED); // IF verbose is disabled(default), we will no-op console.log
}
else
{
console.setInfoLevel(1);
console.setInfoLevel(1); // IF verbose is specified, we will show info level 1 messages
}
parms.push('_stop');
parms.push('_stop'); // Since we are intending to halt after uninstalling the service, we specify this, since we are re-using the uninstall code with the installer.
checkParameters(parms);
checkParameters(parms); // Perform some checks on the passed in parameters
var name = parms.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent');
var name = parms.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); // Set the service name, using the defaults if not specified
// Check for a previous installation of the service
try
{
process.stdout.write('...Checking for previous installation of "' + name + '"');
@@ -579,22 +650,26 @@ function fullUninstall(jsonString)
}
catch (e)
{
// No previous installation was found, so we can just exit
process.stdout.write(' [NONE]\n');
process.exit();
}
serviceExists(loc, parms);
}
// Entry point for -fullinstall
function fullInstall(jsonString)
{
// Perform some checks on the specified parameters
var parms = JSON.parse(jsonString);
checkParameters(parms);
var loc = null;
var i;
var name = parms.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent');
var name = parms.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); // Set the service name, using defaults if not specified
if (process.platform != 'win32') { name = name.split(' ').join('_'); }
// No-op console.log() if verbose is not specified, otherwise set the verbosity level to level 1
if (parseInt(parms.getParameter('verbose', 0)) == 0)
{
console.setDestination(console.Destinations.DISABLED);
@@ -604,6 +679,7 @@ function fullInstall(jsonString)
console.setInfoLevel(1);
}
// Check for a previous installation of the service
try
{
process.stdout.write('...Checking for previous installation of "' + name + '"');
@@ -617,15 +693,16 @@ function fullInstall(jsonString)
}
catch (e)
{
// No previous installation was found, so we can continue with installation
process.stdout.write(' [NONE]\n');
installService(parms);
return;
}
if (process.execPath == loc)
{
parms.push('__skipBinaryDelete');
parms.push('__skipBinaryDelete'); // If the installer is running from the installed service path, skip deleting the binary
}
serviceExists(loc, parms);
serviceExists(loc, parms); // Previous installation was found, so we need to do some extra processing before we continue with installation
}
@@ -635,6 +712,8 @@ module.exports =
fullUninstall: fullUninstall
};
// Legacy Windows Helper function, to perform a self-update
function sys_update(isservice, b64)
{
// This is run on the 'updated' agent.
@@ -778,6 +857,7 @@ function sys_update(isservice, b64)
});
}
// Another Windows Legacy Helper for Self-Update, that shows the updater version
function agent_updaterVersion(updatePath)
{
var ret = 0;
@@ -807,6 +887,8 @@ function agent_updaterVersion(updatePath)
return (ret);
}
// Windows Helper to clear firewall entries
function win_clearfirewall(passthru)
{
process.stdout.write('Clearing firewall rules... [0%]');
@@ -829,6 +911,8 @@ function win_clearfirewall(passthru)
return (p);
}
}
// Windows Helper for enumerating Firewall Rules associated with our binary
function win_checkfirewall()
{
process.stdout.write('Checking firewall rules... [0%]');
@@ -850,6 +934,8 @@ function win_checkfirewall()
process.exit();
});
}
// Windows Helper for setting a firewall rule entry
function win_setfirewall()
{
var p = win_clearfirewall(true);
@@ -873,6 +959,7 @@ function win_setfirewall()
}
// Windows Helper, for performing SelfUpdate on Console Mode Agent
function win_consoleUpdate()
{
// This is run from the 'old' agent, to copy the 'updated' agent.
@@ -889,12 +976,15 @@ function win_consoleUpdate()
require('child_process')._execve(process.execPath.split('.exe').join('.update.exe'), args);
}
// Legacy Helper for Windows Self-Update. Shouldn't really be used anymore, but is still here for Legacy Support
module.exports.update = sys_update;
module.exports.updaterVersion = agent_updaterVersion;
if (process.platform == 'win32')
{
module.exports.consoleUpdate = win_consoleUpdate;
module.exports.clearfirewall = win_clearfirewall;
module.exports.setfirewall = win_setfirewall;
module.exports.checkfirewall = win_checkfirewall;
module.exports.consoleUpdate = win_consoleUpdate; // Windows Helper, for performing SelfUpdate on Console Mode Agent
module.exports.clearfirewall = win_clearfirewall; // Windows Helper, to clear firewall entries
module.exports.setfirewall = win_setfirewall; // Windows Helper, to set firewall entries
module.exports.checkfirewall = win_checkfirewall; // Windows Helper, to check firewall rules
}