Bearsampp 2026.7.11
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
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 = 100000;
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
68 public function __construct($name)
69 {
70 Log::initClass( $this );
71 $this->name = $name;
72 }
73
79 private function writeLog($log)
80 {
81 global $bearsamppRoot;
83 }
84
90 private function writeLogInfo($log)
91 {
92 global $bearsamppRoot;
94 }
95
101 private function writeLogError($log)
102 {
103 global $bearsamppRoot;
105 }
106
114 private function exec($args)
115 {
116 global $bearsamppCore;
117
118 $command = '"' . Path::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
148 public function status($timeout = true)
149 {
150 $this->latestStatus = self::STATUS_NA;
151 $maxtime = time() + self::PENDING_TIMEOUT;
152
153 while ( $this->latestStatus == self::STATUS_NA || $this->isPending( $this->latestStatus ) ) {
154 $exec = $this->exec( 'status ' . $this->getName() );
155 if ( $exec !== false ) {
156 if ( count( $exec ) > 1 ) {
157 $this->latestStatus = self::STATUS_NOT_EXIST;
158 }
159 else {
160 $this->latestStatus = $exec[0];
161 }
162 }
163 if ( $timeout && $maxtime < time() ) {
164 break;
165 }
166
167 // Only sleep if we're going to loop again
168 if ($this->latestStatus == self::STATUS_NA || $this->isPending($this->latestStatus)) {
169 usleep(self::SLEEP_TIME);
170 }
171 }
172
173 if ( $this->latestStatus == self::STATUS_NOT_EXIST ) {
174 $this->latestError = 'Error 3: The specified service does not exist as an installed service.';
175 $this->latestStatus = self::STATUS_NA;
176 }
177
178 return $this->latestStatus;
179 }
180
186 public function create()
187 {
188 $this->writeLog( 'Create service' );
189 $this->writeLog( '-> service: ' . $this->getName() );
190 $this->writeLog( '-> display: ' . $this->getDisplayName() );
191 $this->writeLog( '-> description: ' . $this->getDisplayName() );
192 $this->writeLog( '-> path: ' . $this->getBinPath() );
193 $this->writeLog( '-> params: ' . $this->getParams() );
194 $this->writeLog( '-> stdout: ' . $this->getStdout() );
195 $this->writeLog( '-> stderr: ' . $this->getStderr() );
196 $this->writeLog( '-> environment extra: ' . $this->getEnvironmentExtra() );
197 $this->writeLog( '-> start_type: ' . ($this->getStart() != null ? $this->getStart() : self::SERVICE_DEMAND_START) );
198
199 // Install bin
200 $exec = $this->exec( 'install ' . $this->getName() . ' "' . $this->getBinPath() . '"' );
201 if ( $exec === false ) {
202 return false;
203 }
204
205 // Params
206 $exec = $this->exec( 'set ' . $this->getName() . ' AppParameters "' . $this->getParams() . '"' );
207 if ( $exec === false ) {
208 return false;
209 }
210
211 // DisplayName
212 $exec = $this->exec( 'set ' . $this->getName() . ' DisplayName "' . $this->getDisplayName() . '"' );
213 if ( $exec === false ) {
214 return false;
215 }
216
217 // Description
218 $exec = $this->exec( 'set ' . $this->getName() . ' Description "' . $this->getDisplayName() . '"' );
219 if ( $exec === false ) {
220 return false;
221 }
222
223 // No AppNoConsole to fix nssm problems with Windows 10 Creators update.
224 $exec = $this->exec( 'set ' . $this->getName() . ' AppNoConsole "1"' );
225 if ( $exec === false ) {
226 return false;
227 }
228
229 // Start
230 $exec = $this->exec( 'set ' . $this->getName() . ' Start "' . ($this->getStart() != null ? $this->getStart() : self::SERVICE_DEMAND_START) . '"' );
231 if ( $exec === false ) {
232 return false;
233 }
234
235 // Stdout
236 $exec = $this->exec( 'set ' . $this->getName() . ' AppStdout "' . $this->getStdout() . '"' );
237 if ( $exec === false ) {
238 return false;
239 }
240
241 // Stderr
242 $exec = $this->exec( 'set ' . $this->getName() . ' AppStderr "' . $this->getStderr() . '"' );
243 if ( $exec === false ) {
244 return false;
245 }
246
247 // Environment Extra
248 $exec = $this->exec( 'set ' . $this->getName() . ' AppEnvironmentExtra ' . $this->getEnvironmentExtra() );
249 if ( $exec === false ) {
250 return false;
251 }
252
253 if ( !$this->isInstalled() ) {
254 $this->latestError = null;
255
256 return false;
257 }
258
259 return true;
260 }
261
267 public function delete()
268 {
269 $this->stop();
270
271 $this->writeLog( 'Delete service ' . $this->getName() );
272 $exec = $this->exec( 'remove ' . $this->getName() . ' confirm' );
273 if ( $exec === false ) {
274 return false;
275 }
276
277 if ( $this->isInstalled() ) {
278 $this->latestError = null;
279
280 return false;
281 }
282
283 return true;
284 }
285
291 public function start()
292 {
293 $this->writeLog( 'Start service ' . $this->getName() );
294
295 $exec = $this->exec( 'start ' . $this->getName() );
296 if ( $exec === false ) {
297 return false;
298 }
299
300 if ( !$this->isRunning() ) {
301 $this->latestError = null;
302
303 return false;
304 }
305
306 return true;
307 }
308
314 public function stop()
315 {
316 $this->writeLog( 'Stop service ' . $this->getName() );
317
318 $exec = $this->exec( 'stop ' . $this->getName() );
319 if ( $exec === false ) {
320 return false;
321 }
322
323 if ( !$this->isStopped() ) {
324 $this->latestError = null;
325
326 return false;
327 }
328
329 return true;
330 }
331
337 public function restart()
338 {
339 if ( $this->stop() ) {
340 return $this->start();
341 }
342
343 return false;
344 }
345
351 public function infos()
352 {
353 global $bearsamppRegistry;
354
355 $infos = Win32Native::getServiceInfo( $this->getName() );
356 if ( $infos === false ) {
357 return false;
358 }
359
360 $infosNssm = array();
361 $infosKeys = array(
362 self::INFO_APPLICATION,
363 self::INFO_APP_PARAMETERS,
364 );
365
366 foreach ( $infosKeys as $infoKey ) {
367 $value = null;
368 $exists = $bearsamppRegistry->exists(
370 'SYSTEM\CurrentControlSet\Services\\' . $this->getName() . '\Parameters',
371 $infoKey
372 );
373 if ( $exists ) {
374 $value = $bearsamppRegistry->getValue(
376 'SYSTEM\CurrentControlSet\Services\\' . $this->getName() . '\Parameters',
377 $infoKey
378 );
379 }
380 $infosNssm[$infoKey] = $value;
381 }
382
383 if ( !isset( $infosNssm[self::INFO_APPLICATION] ) ) {
384 return $infos;
385 }
386
387 $infos[Win32Service::VBS_PATH_NAME] = $infosNssm[Nssm::INFO_APPLICATION] . ' ' . $infosNssm[Nssm::INFO_APP_PARAMETERS];
388
389 return $infos;
390 }
391
397 public function isInstalled()
398 {
399 $status = $this->status();
400 $this->writeLog( 'isInstalled ' . $this->getName() . ': ' . ($status != self::STATUS_NA ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
401
402 return $status != self::STATUS_NA;
403 }
404
410 public function isRunning()
411 {
412 $status = $this->status();
413 $this->writeLog( 'isRunning ' . $this->getName() . ': ' . ($status == self::STATUS_RUNNING ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
414
415 return $status == self::STATUS_RUNNING;
416 }
417
423 public function isStopped()
424 {
425 $status = $this->status();
426 $this->writeLog( 'isStopped ' . $this->getName() . ': ' . ($status == self::STATUS_STOPPED ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
427
428 return $status == self::STATUS_STOPPED;
429 }
430
436 public function isPaused()
437 {
438 $status = $this->status();
439 $this->writeLog( 'isPaused ' . $this->getName() . ': ' . ($status == self::STATUS_PAUSED ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
440
441 return $status == self::STATUS_PAUSED;
442 }
443
451 public function isPending($status)
452 {
453 return $status == self::STATUS_START_PENDING || $status == self::STATUS_STOP_PENDING
454 || $status == self::STATUS_CONTINUE_PENDING || $status == self::STATUS_PAUSE_PENDING;
455 }
456
464 private function getServiceStatusDesc($status)
465 {
466 switch ( $status ) {
467 case self::STATUS_CONTINUE_PENDING:
468 return 'The service continue is pending.';
469
470 case self::STATUS_PAUSE_PENDING:
471 return 'The service pause is pending.';
472
473 case self::STATUS_PAUSED:
474 return 'The service is paused.';
475
476 case self::STATUS_RUNNING:
477 return 'The service is running.';
478
479 case self::STATUS_START_PENDING:
480 return 'The service is starting.';
481
482 case self::STATUS_STOP_PENDING:
483 return 'The service is stopping.';
484
485 case self::STATUS_STOPPED:
486 return 'The service is not running.';
487
488 case self::STATUS_NA:
489 return 'Cannot retrieve service status.';
490
491 default:
492 return null;
493 }
494 }
495
501 public function getName()
502 {
503 return $this->name;
504 }
505
511 public function setName($name)
512 {
513 $this->name = $name;
514 }
515
521 public function getDisplayName()
522 {
523 return $this->displayName;
524 }
525
532 {
533 $this->displayName = $displayName;
534 }
535
541 public function getBinPath()
542 {
543 return $this->binPath;
544 }
545
551 public function setBinPath($binPath)
552 {
553 $this->binPath = str_replace( '"', '', Path::formatWindowsPath( $binPath ) );
554 }
555
561 public function getParams()
562 {
563 return $this->params;
564 }
565
571 public function setParams($params)
572 {
573 $this->params = $params;
574 }
575
581 public function getStart()
582 {
583 return $this->start;
584 }
585
591 public function setStart($start)
592 {
593 $this->start = $start;
594 }
595
601 public function getStdout()
602 {
603 return $this->stdout;
604 }
605
611 public function setStdout($stdout)
612 {
613 $this->stdout = $stdout;
614 }
615
621 public function getStderr()
622 {
623 return $this->stderr;
624 }
625
631 public function setStderr($stderr)
632 {
633 $this->stderr = $stderr;
634 }
635
641 public function getEnvironmentExtra()
642 {
644 }
645
652 {
653 $this->environmentExtra = Path::formatWindowsPath( $environmentExtra );
654 }
655
661 public function getLatestStatus()
662 {
663 return $this->latestStatus;
664 }
665
671 public function getLatestError()
672 {
673 return $this->latestError;
674 }
675
681 public function getError()
682 {
683 global $bearsamppLang;
684
685 if ( !empty( $this->latestError ) ) {
686 return $bearsamppLang->getValue( Lang::ERROR ) . ' ' . $this->latestError;
687 }
688 elseif ( $this->latestStatus != self::STATUS_NA ) {
689 return $bearsamppLang->getValue( Lang::STATUS ) . ' ' . $this->latestStatus . ' : ' . $this->getWin32ServiceStatusDesc( $this->latestStatus );
690 }
691
692 return null;
693 }
694}
$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
static info($data, $file=null)
static debug($data, $file=null)
static error($data, $file=null)
static initClass($classInstance)
setStart($start)
const INFO_APP_ENVIRONMENT_EXTRA
const SERVICE_DELAYED_START
const INFO_APP_STDERR
getError()
getDisplayName()
const STATUS_PAUSE_PENDING
const INFO_APP_DIRECTORY
writeLog($log)
setName($name)
getStdout()
$environmentExtra
const INFO_APP_STDOUT
const STATUS_RUNNING
getName()
const STATUS_PAUSED
setStdout($stdout)
writeLogInfo($log)
__construct($name)
$latestStatus
writeLogError($log)
const STATUS_START_PENDING
exec($args)
status($timeout=true)
const STATUS_CONTINUE_PENDING
isPaused()
getLatestStatus()
const STATUS_NOT_EXIST
getServiceStatusDesc($status)
const INFO_APP_PARAMETERS
const STATUS_STOPPED
const SERVICE_DISABLED
getLatestError()
getStderr()
$latestError
setParams($params)
setStderr($stderr)
getStart()
isStopped()
const STATUS_STOP_PENDING
getEnvironmentExtra()
$displayName
getBinPath()
setDisplayName($displayName)
restart()
isRunning()
const SERVICE_INTERACTIVE_PROCESS
setBinPath($binPath)
getParams()
setEnvironmentExtra($environmentExtra)
const SERVICE_WIN32_OWN_PROCESS
isInstalled()
const SERVICE_DEMAND_START
const INFO_APPLICATION
const SERVICE_AUTO_START
const PENDING_TIMEOUT
const STATUS_NA
isPending($status)
const SLEEP_TIME
static formatWindowsPath($path)
static getNssmLogFilePath($aetrayPath=false)
static getNssmExe($aetrayPath=false)
const HKEY_LOCAL_MACHINE
static getServiceInfo($serviceName, $properties=[])