Skip to content

Commit

Permalink
fix(winsvc): Make Windows service status checks quicker
Browse files Browse the repository at this point in the history
Fixes #912
  • Loading branch information
Göran Sander committed Dec 12, 2023
1 parent dc76b63 commit 001f99f
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 4 deletions.
4 changes: 4 additions & 0 deletions src/lib/post_to_influxdb.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ function postButlerMemoryUsageToInfluxdb(memory) {

// Add function to store windows service status to InfluxDB
function postWindowsServiceStatusToInfluxDB(serviceStatus) {
globals.logger.verbose(
`INFLUXDB WINDOWS SERVICE STATUS: Sending service status to InfluxDB: service="${serviceStatus.serviceFriendlyName}", status="${serviceStatus.serviceStatus}"`
);

// Create lookup table for Windows service state to numeric value, starting with 1 for stopped
const serviceStateLookup = {
STOPPED: 1,
Expand Down
25 changes: 22 additions & 3 deletions src/lib/service_monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ const serviceMonitorMqttSend2 = (config, logger, svc) => {
) {
logger.verbose(`"${svc.serviceName}"No MQTT topic defined in config entry "Butler.mqttConfig.serviceStatusTopic"`);
} else {
logger.verbose(`Sending service status to MQTT: service="${svc.serviceName}", status="${svc.serviceStatus}"`);
logger.verbose(
`MQTT WINDOWS SERVICE STATUS: Sending service status to MQTT: service="${svc.serviceDetails.displayName}", status="${svc.serviceStatus}"`
);

globals.mqttClient.publish(
`${config.get('Butler.mqttConfig.serviceStatusTopic')}/${svc.host}/${svc.serviceName}`,
JSON.stringify({
Expand Down Expand Up @@ -170,9 +173,25 @@ const checkServiceStatus = async (config, logger, isFirstCheck = false) => {
logger.verbose(`Checking status of Windows services on host ${host.host}`);
const servicesToCheck = host.services;

// Get status of all services on host
logger.verbose(`Getting status of all Windows services on host ${host.host}`);

const serviceStatusAll = await svcTools.statusAll(logger, host.host);

servicesToCheck.forEach(async (service) => {
logger.verbose(`Checking status of Windows service ${service.name} (="${service.friendlyName}") on host ${host.host}`);

// Does this service exist in the serviceStatusAll array?
const svcMonitored = serviceStatusAll.find((svc) => svc.name === service.name);

if (svcMonitored === undefined) {
logger.error(
`Service ${service.name} (="${service.friendlyName}") on host ${host.host} does not exist or cannot be reached.`
);
return;
}

// Get status of this service
const serviceStatus = await svcTools.status(logger, service.name, host.host);
logger.verbose(`Got reply: Service ${service.name} (="${service.friendlyName}") on host ${host.host} status: ${serviceStatus}`);

Expand Down Expand Up @@ -467,9 +486,9 @@ const checkServiceStatus = async (config, logger, isFirstCheck = false) => {

// InfluDB
if (
globals.config.has('Butler.emailNotification.enable') &&
globals.config.has('Butler.influxDb.enable') &&
globals.config.has('Butler.serviceMonitor.alertDestination.influxDb.enable') &&
globals.config.get('Butler.emailNotification.enable') === true &&
globals.config.get('Butler.influxDb.enable') === true &&
globals.config.get('Butler.serviceMonitor.alertDestination.influxDb.enable') === true
) {
const instanceTag = globals.config.has('Butler.influxDb.instanceTag')
Expand Down
88 changes: 87 additions & 1 deletion src/lib/winsvc.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,92 @@ function exists(logger, serviceName, host = null) {
}

/**
* Get status of provided service
* Get status of all services on a host
* @param {object} logger Logger object
* @param {string} host Host on which service is running
*/
function statusAll(logger, host = null) {
// Create promise
return new Promise((resolve, reject) => {
// If host is not specified, get services on localhost
let command = '';

if (host === null) {
// Run command for get states of all services on local machine
logger.verbose('WINSVC STATUSALL: Getting status of all services on local machine');
command = 'sc.exe query state= all';
} else {
// A host other that local machine is specfied
logger.verbose(`WINSVC STATUSALL: Getting status of all services on host ${host}`);
command = `sc.exe \\\\${host} query state= all`;
}

// Run command for create service with provided data
logger.debug(`WINSVC STATUSALL: Running command ${command}`);
exec(command, (err, stdout) => {
// On error, reject and exit
if (err) {
logger.error(`WINSVC STATUSALL: Error while getting status of all services on host ${host}`);
if (err.code) {
logger.error(`WINSVC STATUSALL: Error code: ${err.code}`);
}

reject(err);
return;
}

// Create temporary stdout variable
let stdoutTmp = stdout;

// Remove first line, if it is empty
if (stdoutTmp.toString().split('\r\n')[0] === '') {
stdoutTmp = stdoutTmp.toString().split('\r\n').slice(1).join('\r\n');
}

// Each service block starts with SERVICE_NAME and ends with an empty line
// Split stdout into blocks of services
// Create object from each block, then add objects to array of services
const serviceStatusAll = stdoutTmp
.toString()
.split('\r\n\r\n')
.map((block) => {
const lines = block.split('\r\n');
// The state line has format "<statenum><one or more spaces><statetext>"
// Extract stateNum and stateText from state line, to separate properties
const service = {
name: lines.find((line) => line.indexOf('SERVICE_NAME') !== -1).replace('SERVICE_NAME: ', ''),
displayName: lines.find((line) => line.indexOf('DISPLAY_NAME') !== -1).replace(/\s*DISPLAY_NAME\s*: /, ''),
type: lines.find((line) => line.indexOf('TYPE') !== -1).replace(/\s*TYPE\s*: /, ''),
stateNum: lines
.find((line) => line.indexOf('STATE') !== -1)
.replace('STATE', '')
.replace(/\s*:\s*/, '')
.split(' ')[0],
stateText: lines
.find((line) => line.indexOf('STATE') !== -1)
.replace('STATE', '')
.replace(/\s*:\s*/, '')
.split(' ')[2],
win32ExitCode: lines.find((line) => line.indexOf('WIN32_EXIT_CODE') !== -1).replace(/\s*WIN32_EXIT_CODE\s*: /, ''),
serviceExitCode: lines
.find((line) => line.indexOf('SERVICE_EXIT_CODE') !== -1)
.replace(/\s*SERVICE_EXIT_CODE\s*: /, ''),
};

return service;
});

logger.verbose(`WINSVC STATUSALL: Got status of all services on host ${host}`);
logger.debug(serviceStatusAll);

// Resolve with array of service objects
resolve(serviceStatusAll);
});
});
}

/**
* Get status of a specific service on a host
* @param {object} logger Logger object
* @param {string} serviceName Name of service
* @param {string} host Host on which service is running
Expand Down Expand Up @@ -344,4 +429,5 @@ module.exports = {
details,
exists,
status,
statusAll,
};

0 comments on commit 001f99f

Please sign in to comment.