2024.8.23
Loading...
Searching...
No Matches
class.nssm.php
Go to the documentation of this file.
1<?php
2/*
3 * Copyright (c) 2021-2024 Bearsampp
4 * License: GNU General Public License version 3 or later; see LICENSE.txt
5 * Author: Bear
6 * Website: https://bearsampp.com
7 * Github: https://github.com/Bearsampp
8 */
9
10/**
11 * Class Nssm
12 *
13 * This class provides methods to manage Windows services using NSSM (Non-Sucking Service Manager).
14 * It includes functionalities to create, delete, start, stop, and retrieve the status of services.
15 * The class also logs operations and errors.
16 */
17class Nssm
18{
19 // Start params
20 const SERVICE_AUTO_START = 'SERVICE_AUTO_START';
21 const SERVICE_DELAYED_START = 'SERVICE_DELAYED_START';
22 const SERVICE_DEMAND_START = 'SERVICE_DEMAND_START';
23 const SERVICE_DISABLED = 'SERVICE_DISABLED';
24
25 // Type params
26 const SERVICE_WIN32_OWN_PROCESS = 'SERVICE_WIN32_OWN_PROCESS';
27 const SERVICE_INTERACTIVE_PROCESS = 'SERVICE_INTERACTIVE_PROCESS';
28
29 // Status
30 const STATUS_CONTINUE_PENDING = 'SERVICE_CONTINUE_PENDING';
31 const STATUS_PAUSE_PENDING = 'SERVICE_PAUSE_PENDING';
32 const STATUS_PAUSED = 'SERVICE_PAUSED';
33 const STATUS_RUNNING = 'SERVICE_RUNNING';
34 const STATUS_START_PENDING = 'SERVICE_START_PENDING';
35 const STATUS_STOP_PENDING = 'SERVICE_STOP_PENDING';
36 const STATUS_STOPPED = 'SERVICE_STOPPED';
37 const STATUS_NOT_EXIST = 'SERVICE_NOT_EXIST';
38 const STATUS_NA = '-1';
39
40 // Infos keys
41 const INFO_APP_DIRECTORY = 'AppDirectory';
42 const INFO_APPLICATION = 'Application';
43 const INFO_APP_PARAMETERS = 'AppParameters';
44 const INFO_APP_STDERR = 'AppStderr';
45 const INFO_APP_STDOUT = 'AppStdout';
46 const INFO_APP_ENVIRONMENT_EXTRA = 'AppEnvironmentExtra';
47
48 const PENDING_TIMEOUT = 10;
49 const SLEEP_TIME = 500000;
50
51 private $name;
52 private $displayName;
53 private $binPath;
54 private $params;
55 private $start;
56 private $stdout;
57 private $stderr;
59 private $latestError;
61
62 /**
63 * Nssm constructor.
64 * Initializes the Nssm class and logs the initialization.
65 *
66 * @param string $name The name of the service.
67 */
68 public function __construct($name)
69 {
70 Util::logInitClass( $this );
71 $this->name = $name;
72 }
73
74 /**
75 * Writes a log entry.
76 *
77 * @param string $log The log message to write.
78 */
79 private function writeLog($log)
80 {
81 global $bearsamppRoot;
82 Util::logDebug( $log, $bearsamppRoot->getNssmLogFilePath() );
83 }
84
85 /**
86 * Writes an informational log entry.
87 *
88 * @param string $log The log message to write.
89 */
90 private function writeLogInfo($log)
91 {
92 global $bearsamppRoot;
93 Util::logInfo( $log, $bearsamppRoot->getNssmLogFilePath() );
94 }
95
96 /**
97 * Writes an error log entry.
98 *
99 * @param string $log The log message to write.
100 */
101 private function writeLogError($log)
102 {
103 global $bearsamppRoot;
104 Util::logError( $log, $bearsamppRoot->getNssmLogFilePath() );
105 }
106
107 /**
108 * Executes an NSSM command.
109 *
110 * @param string $args The arguments for the NSSM command.
111 *
112 * @return array|false The result of the execution, or false on failure.
113 */
114 private function exec($args)
115 {
116 global $bearsamppCore;
117
118 $command = '"' . $bearsamppCore->getNssmExe() . '" ' . $args;
119 $this->writeLogInfo( 'Cmd: ' . $command );
120
121 $result = Batch::exec( 'nssm', $command, 10 );
122 if ( is_array( $result ) ) {
123 $rebuildResult = array();
124 foreach ( $result as $row ) {
125 $row = trim( $row );
126 if ( !empty( $row ) ) {
127 $rebuildResult[] = preg_replace( '/[\x00-\x1F\x80-\xFF]/', '', $row );
128 }
129 }
130 $result = $rebuildResult;
131 if ( count( $result ) > 1 ) {
132 $this->latestError = implode( ' ; ', $result );
133 }
134
135 return $result;
136 }
137
138 return false;
139 }
140
141 /**
142 * Retrieves the status of the service.
143 *
144 * @param bool $timeout Whether to apply a timeout for the status check.
145 *
146 * @return string The status of the service.
147 */
148 public function status($timeout = true)
149 {
150 usleep( self::SLEEP_TIME );
151
152 $this->latestStatus = self::STATUS_NA;
153 $maxtime = time() + self::PENDING_TIMEOUT;
154
155 while ( $this->latestStatus == self::STATUS_NA || $this->isPending( $this->latestStatus ) ) {
156 $exec = $this->exec( 'status ' . $this->getName() );
157 if ( $exec !== false ) {
158 if ( count( $exec ) > 1 ) {
159 $this->latestStatus = self::STATUS_NOT_EXIST;
160 }
161 else {
162 $this->latestStatus = $exec[0];
163 }
164 }
165 if ( $timeout && $maxtime < time() ) {
166 break;
167 }
168 }
169
170 if ( $this->latestStatus == self::STATUS_NOT_EXIST ) {
171 $this->latestError = 'Error 3: The specified service does not exist as an installed service.';
172 $this->latestStatus = self::STATUS_NA;
173 }
174
175 return $this->latestStatus;
176 }
177
178 /**
179 * Creates a new service.
180 *
181 * @return bool True if the service was created successfully, false otherwise.
182 */
183 public function create()
184 {
185 $this->writeLog( 'Create service' );
186 $this->writeLog( '-> service: ' . $this->getName() );
187 $this->writeLog( '-> display: ' . $this->getDisplayName() );
188 $this->writeLog( '-> description: ' . $this->getDisplayName() );
189 $this->writeLog( '-> path: ' . $this->getBinPath() );
190 $this->writeLog( '-> params: ' . $this->getParams() );
191 $this->writeLog( '-> stdout: ' . $this->getStdout() );
192 $this->writeLog( '-> stderr: ' . $this->getStderr() );
193 $this->writeLog( '-> environment extra: ' . $this->getEnvironmentExtra() );
194 $this->writeLog( '-> start_type: ' . ($this->getStart() != null ? $this->getStart() : self::SERVICE_DEMAND_START) );
195
196 // Install bin
197 $exec = $this->exec( 'install ' . $this->getName() . ' "' . $this->getBinPath() . '"' );
198 if ( $exec === false ) {
199 return false;
200 }
201
202 // Params
203 $exec = $this->exec( 'set ' . $this->getName() . ' AppParameters "' . $this->getParams() . '"' );
204 if ( $exec === false ) {
205 return false;
206 }
207
208 // DisplayName
209 $exec = $this->exec( 'set ' . $this->getName() . ' DisplayName "' . $this->getDisplayName() . '"' );
210 if ( $exec === false ) {
211 return false;
212 }
213
214 // Description
215 $exec = $this->exec( 'set ' . $this->getName() . ' Description "' . $this->getDisplayName() . '"' );
216 if ( $exec === false ) {
217 return false;
218 }
219
220 // No AppNoConsole to fix nssm problems with Windows 10 Creators update.
221 $exec = $this->exec( 'set ' . $this->getName() . ' AppNoConsole "1"' );
222 if ( $exec === false ) {
223 return false;
224 }
225
226 // Start
227 $exec = $this->exec( 'set ' . $this->getName() . ' Start "' . ($this->getStart() != null ? $this->getStart() : self::SERVICE_DEMAND_START) . '"' );
228 if ( $exec === false ) {
229 return false;
230 }
231
232 // Stdout
233 $exec = $this->exec( 'set ' . $this->getName() . ' AppStdout "' . $this->getStdout() . '"' );
234 if ( $exec === false ) {
235 return false;
236 }
237
238 // Stderr
239 $exec = $this->exec( 'set ' . $this->getName() . ' AppStderr "' . $this->getStderr() . '"' );
240 if ( $exec === false ) {
241 return false;
242 }
243
244 // Environment Extra
245 $exec = $this->exec( 'set ' . $this->getName() . ' AppEnvironmentExtra ' . $this->getEnvironmentExtra() );
246 if ( $exec === false ) {
247 return false;
248 }
249
250 if ( !$this->isInstalled() ) {
251 $this->latestError = null;
252
253 return false;
254 }
255
256 return true;
257 }
258
259 /**
260 * Deletes the service.
261 *
262 * @return bool True if the service was deleted successfully, false otherwise.
263 */
264 public function delete()
265 {
266 $this->stop();
267
268 $this->writeLog( 'Delete service ' . $this->getName() );
269 $exec = $this->exec( 'remove ' . $this->getName() . ' confirm' );
270 if ( $exec === false ) {
271 return false;
272 }
273
274 if ( $this->isInstalled() ) {
275 $this->latestError = null;
276
277 return false;
278 }
279
280 return true;
281 }
282
283 /**
284 * Starts the service.
285 *
286 * @return bool True if the service was started successfully, false otherwise.
287 */
288 public function start()
289 {
290 $this->writeLog( 'Start service ' . $this->getName() );
291
292 $exec = $this->exec( 'start ' . $this->getName() );
293 if ( $exec === false ) {
294 return false;
295 }
296
297 if ( !$this->isRunning() ) {
298 $this->latestError = null;
299
300 return false;
301 }
302
303 return true;
304 }
305
306 /**
307 * Stops the service.
308 *
309 * @return bool True if the service was stopped successfully, false otherwise.
310 */
311 public function stop()
312 {
313 $this->writeLog( 'Stop service ' . $this->getName() );
314
315 $exec = $this->exec( 'stop ' . $this->getName() );
316 if ( $exec === false ) {
317 return false;
318 }
319
320 if ( !$this->isStopped() ) {
321 $this->latestError = null;
322
323 return false;
324 }
325
326 return true;
327 }
328
329 /**
330 * Restarts the service.
331 *
332 * @return bool True if the service was restarted successfully, false otherwise.
333 */
334 public function restart()
335 {
336 if ( $this->stop() ) {
337 return $this->start();
338 }
339
340 return false;
341 }
342
343 /**
344 * Retrieves information about the service.
345 *
346 * @return array|false The service information, or false on failure.
347 */
348 public function infos()
349 {
350 global $bearsamppRegistry;
351
352 $infos = Vbs::getServiceInfos( $this->getName() );
353 if ( $infos === false ) {
354 return false;
355 }
356
357 $infosNssm = array();
358 $infosKeys = array(
359 self::INFO_APPLICATION,
360 self::INFO_APP_PARAMETERS,
361 );
362
363 foreach ( $infosKeys as $infoKey ) {
364 $value = null;
365 $exists = $bearsamppRegistry->exists(
367 'SYSTEM\CurrentControlSet\Services\\' . $this->getName() . '\Parameters',
368 $infoKey
369 );
370 if ( $exists ) {
371 $value = $bearsamppRegistry->getValue(
373 'SYSTEM\CurrentControlSet\Services\\' . $this->getName() . '\Parameters',
374 $infoKey
375 );
376 }
377 $infosNssm[$infoKey] = $value;
378 }
379
380 if ( !isset( $infosNssm[self::INFO_APPLICATION] ) ) {
381 return $infos;
382 }
383
384 $infos[Win32Service::VBS_PATH_NAME] = $infosNssm[Nssm::INFO_APPLICATION] . ' ' . $infosNssm[Nssm::INFO_APP_PARAMETERS];
385
386 return $infos;
387 }
388
389 /**
390 * Checks if the service is installed.
391 *
392 * @return bool True if the service is installed, false otherwise.
393 */
394 public function isInstalled()
395 {
396 $status = $this->status();
397 $this->writeLog( 'isInstalled ' . $this->getName() . ': ' . ($status != self::STATUS_NA ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
398
399 return $status != self::STATUS_NA;
400 }
401
402 /**
403 * Checks if the service is running.
404 *
405 * @return bool True if the service is running, false otherwise.
406 */
407 public function isRunning()
408 {
409 $status = $this->status();
410 $this->writeLog( 'isRunning ' . $this->getName() . ': ' . ($status == self::STATUS_RUNNING ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
411
412 return $status == self::STATUS_RUNNING;
413 }
414
415 /**
416 * Checks if the service is stopped.
417 *
418 * @return bool True if the service is stopped, false otherwise.
419 */
420 public function isStopped()
421 {
422 $status = $this->status();
423 $this->writeLog( 'isStopped ' . $this->getName() . ': ' . ($status == self::STATUS_STOPPED ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
424
425 return $status == self::STATUS_STOPPED;
426 }
427
428 /**
429 * Checks if the service is paused.
430 *
431 * @return bool True if the service is paused, false otherwise.
432 */
433 public function isPaused()
434 {
435 $status = $this->status();
436 $this->writeLog( 'isPaused ' . $this->getName() . ': ' . ($status == self::STATUS_PAUSED ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
437
438 return $status == self::STATUS_PAUSED;
439 }
440
441 /**
442 * Checks if the service status is pending.
443 *
444 * @param string $status The status to check.
445 *
446 * @return bool True if the status is pending, false otherwise.
447 */
448 public function isPending($status)
449 {
450 return $status == self::STATUS_START_PENDING || $status == self::STATUS_STOP_PENDING
451 || $status == self::STATUS_CONTINUE_PENDING || $status == self::STATUS_PAUSE_PENDING;
452 }
453
454 /**
455 * Retrieves the description of the service status.
456 *
457 * @param string $status The status to describe.
458 *
459 * @return string|null The description of the status, or null if not recognized.
460 */
461 private function getServiceStatusDesc($status)
462 {
463 switch ( $status ) {
465 return 'The service continue is pending.';
466
468 return 'The service pause is pending.';
469
471 return 'The service is paused.';
472
474 return 'The service is running.';
475
477 return 'The service is starting.';
478
480 return 'The service is stopping.';
481
483 return 'The service is not running.';
484
485 case self::STATUS_NA:
486 return 'Cannot retrieve service status.';
487
488 default:
489 return null;
490 }
491 }
492
493 /**
494 * Gets the name of the service.
495 *
496 * @return string The name of the service.
497 */
498 public function getName()
499 {
500 return $this->name;
501 }
502
503 /**
504 * Sets the name of the service.
505 *
506 * @param string $name The name to set.
507 */
508 public function setName($name)
509 {
510 $this->name = $name;
511 }
512
513 /**
514 * Gets the display name of the service.
515 *
516 * @return string The display name of the service.
517 */
518 public function getDisplayName()
519 {
520 return $this->displayName;
521 }
522
523 /**
524 * Sets the display name of the service.
525 *
526 * @param string $displayName The display name to set.
527 */
529 {
530 $this->displayName = $displayName;
531 }
532
533 /**
534 * Gets the binary path of the service.
535 *
536 * @return string The binary path of the service.
537 */
538 public function getBinPath()
539 {
540 return $this->binPath;
541 }
542
543 /**
544 * Sets the binary path of the service.
545 *
546 * @param string $binPath The binary path to set.
547 */
548 public function setBinPath($binPath)
549 {
550 $this->binPath = str_replace( '"', '', Util::formatWindowsPath( $binPath ) );
551 }
552
553 /**
554 * Gets the parameters of the service.
555 *
556 * @return string The parameters of the service.
557 */
558 public function getParams()
559 {
560 return $this->params;
561 }
562
563 /**
564 * Sets the parameters of the service.
565 *
566 * @param string $params The parameters to set.
567 */
568 public function setParams($params)
569 {
570 $this->params = $params;
571 }
572
573 /**
574 * Gets the start type of the service.
575 *
576 * @return string The start type of the service.
577 */
578 public function getStart()
579 {
580 return $this->start;
581 }
582
583 /**
584 * Sets the start type of the service.
585 *
586 * @param string $start The start type to set.
587 */
588 public function setStart($start)
589 {
590 $this->start = $start;
591 }
592
593 /**
594 * Gets the stdout path of the service.
595 *
596 * @return string The stdout path of the service.
597 */
598 public function getStdout()
599 {
600 return $this->stdout;
601 }
602
603 /**
604 * Sets the stdout path of the service.
605 *
606 * @param string $stdout The stdout path to set.
607 */
608 public function setStdout($stdout)
609 {
610 $this->stdout = $stdout;
611 }
612
613 /**
614 * Gets the stderr path of the service.
615 *
616 * @return string The stderr path of the service.
617 */
618 public function getStderr()
619 {
620 return $this->stderr;
621 }
622
623 /**
624 * Sets the stderr path of the service.
625 *
626 * @param string $stderr The stderr path to set.
627 */
628 public function setStderr($stderr)
629 {
630 $this->stderr = $stderr;
631 }
632
633 /**
634 * Gets the additional environment variables for the service.
635 *
636 * @return string The additional environment variables.
637 */
638 public function getEnvironmentExtra()
639 {
641 }
642
643 /**
644 * Sets the additional environment variables for the service.
645 *
646 * @param string $environmentExtra The additional environment variables to set.
647 */
649 {
650 $this->environmentExtra = Util::formatWindowsPath( $environmentExtra );
651 }
652
653 /**
654 * Gets the latest status of the service.
655 *
656 * @return string The latest status of the service.
657 */
658 public function getLatestStatus()
659 {
660 return $this->latestStatus;
661 }
662
663 /**
664 * Gets the latest error message related to the service.
665 *
666 * @return string The latest error message.
667 */
668 public function getLatestError()
669 {
670 return $this->latestError;
671 }
672
673 /**
674 * Retrieves the error message or status description of the service.
675 *
676 * @return string|null The error message or status description, or null if no error or status is available.
677 */
678 public function getError()
679 {
680 global $bearsamppLang;
681
682 if ( !empty( $this->latestError ) ) {
683 return $bearsamppLang->getValue( Lang::ERROR ) . ' ' . $this->latestError;
684 }
685 elseif ( $this->latestStatus != self::STATUS_NA ) {
686 return $bearsamppLang->getValue( Lang::STATUS ) . ' ' . $this->latestStatus . ' : ' . $this->getWin32ServiceStatusDesc( $this->latestStatus );
687 }
688
689 return null;
690 }
691}
$result
global $bearsamppLang
global $bearsamppRoot
global $bearsamppCore
static exec($basename, $content, $timeout=true, $catchOutput=true, $standalone=false, $silent=true, $rebuild=true)
const ERROR
const STATUS
const PENDING_TIMEOUT
const STATUS_NA
getServiceStatusDesc($status)
const SERVICE_DELAYED_START
const STATUS_STOPPED
setStderr($stderr)
const INFO_APP_DIRECTORY
getBinPath()
const STATUS_RUNNING
__construct($name)
setEnvironmentExtra($environmentExtra)
isRunning()
isPaused()
setDisplayName($displayName)
restart()
const STATUS_START_PENDING
const STATUS_CONTINUE_PENDING
const STATUS_PAUSED
getDisplayName()
const INFO_APP_STDERR
status($timeout=true)
setStart($start)
exec($args)
setName($name)
getEnvironmentExtra()
const SERVICE_DEMAND_START
getParams()
const INFO_APP_ENVIRONMENT_EXTRA
const STATUS_PAUSE_PENDING
getLatestStatus()
const SERVICE_DISABLED
writeLog($log)
setStdout($stdout)
$latestError
getName()
getError()
isPending($status)
const SERVICE_INTERACTIVE_PROCESS
const STATUS_STOP_PENDING
getStart()
isInstalled()
const SERVICE_WIN32_OWN_PROCESS
getLatestError()
writeLogError($log)
writeLogInfo($log)
$environmentExtra
getStderr()
const SERVICE_AUTO_START
const STATUS_NOT_EXIST
const INFO_APP_STDOUT
setParams($params)
const INFO_APP_PARAMETERS
getStdout()
$displayName
const INFO_APPLICATION
const SLEEP_TIME
isStopped()
$latestStatus
setBinPath($binPath)
const HKEY_LOCAL_MACHINE
static logError($data, $file=null)
static logDebug($data, $file=null)
static logInitClass($classInstance)
static logInfo($data, $file=null)
static formatWindowsPath($path)
static getServiceInfos($serviceName)