Bearsampp 2026.9.23
Loading...
Searching...
No Matches
class.nssm.php
Go to the documentation of this file.
1<?php
2/*
3 * Copyright (c) 2021-2026 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 } else {
159 $this->latestStatus = $exec[0];
160 }
161 }
162 if ($timeout && $maxtime < time()) {
163 break;
164 }
165
166 // Only sleep if we're going to loop again
167 if ($this->latestStatus == self::STATUS_NA || $this->isPending($this->latestStatus)) {
168 usleep(self::SLEEP_TIME);
169 }
170 }
171
172 if ($this->latestStatus == self::STATUS_NOT_EXIST) {
173 $this->latestError = 'Error 3: The specified service does not exist as an installed service.';
174 $this->latestStatus = self::STATUS_NA;
175 }
176
177 return $this->latestStatus;
178 }
179
185 public function create()
186 {
187 $this->writeLog('Create service');
188 $this->writeLog('-> service: ' . $this->getName());
189 $this->writeLog('-> display: ' . $this->getDisplayName());
190 $this->writeLog('-> description: ' . $this->getDisplayName());
191 $this->writeLog('-> path: ' . $this->getBinPath());
192 $this->writeLog('-> params: ' . $this->getParams());
193 $this->writeLog('-> stdout: ' . $this->getStdout());
194 $this->writeLog('-> stderr: ' . $this->getStderr());
195 $this->writeLog('-> environment extra: ' . $this->getEnvironmentExtra());
196 $this->writeLog('-> start_type: ' . ($this->getStart() != null ? $this->getStart() : self::SERVICE_DEMAND_START));
197
198 // Install bin
199 $exec = $this->exec('install ' . $this->getName() . ' "' . $this->getBinPath() . '"');
200 if ($exec === false) {
201 return false;
202 }
203
204 // Params
205 $exec = $this->exec('set ' . $this->getName() . ' AppParameters "' . $this->getParams() . '"');
206 if ($exec === false) {
207 return false;
208 }
209
210 // DisplayName
211 $exec = $this->exec('set ' . $this->getName() . ' DisplayName "' . $this->getDisplayName() . '"');
212 if ($exec === false) {
213 return false;
214 }
215
216 // Description
217 $exec = $this->exec('set ' . $this->getName() . ' Description "' . $this->getDisplayName() . '"');
218 if ($exec === false) {
219 return false;
220 }
221
222 // No AppNoConsole to fix nssm problems with Windows 10 Creators update.
223 $exec = $this->exec('set ' . $this->getName() . ' AppNoConsole "1"');
224 if ($exec === false) {
225 return false;
226 }
227
228 // Start
229 $exec = $this->exec('set ' . $this->getName() . ' Start "' . ($this->getStart() != null ? $this->getStart() : self::SERVICE_DEMAND_START) . '"');
230 if ($exec === false) {
231 return false;
232 }
233
234 // Stdout
235 $exec = $this->exec('set ' . $this->getName() . ' AppStdout "' . $this->getStdout() . '"');
236 if ($exec === false) {
237 return false;
238 }
239
240 // Stderr
241 $exec = $this->exec('set ' . $this->getName() . ' AppStderr "' . $this->getStderr() . '"');
242 if ($exec === false) {
243 return false;
244 }
245
246 // Environment Extra
247 $exec = $this->exec('set ' . $this->getName() . ' AppEnvironmentExtra ' . $this->getEnvironmentExtra());
248 if ($exec === false) {
249 return false;
250 }
251
252 if (!$this->isInstalled()) {
253 $this->latestError = null;
254
255 return false;
256 }
257
258 return true;
259 }
260
266 public function delete()
267 {
268 $this->stop();
269
270 $this->writeLog('Delete service ' . $this->getName());
271 $exec = $this->exec('remove ' . $this->getName() . ' confirm');
272 if ($exec === false) {
273 return false;
274 }
275
276 if ($this->isInstalled()) {
277 $this->latestError = null;
278
279 return false;
280 }
281
282 return true;
283 }
284
290 public function start()
291 {
292 $this->writeLog('Start service ' . $this->getName());
293
294 $exec = $this->exec('start ' . $this->getName());
295 if ($exec === false) {
296 return false;
297 }
298
299 if (!$this->isRunning()) {
300 $this->latestError = null;
301
302 return false;
303 }
304
305 return true;
306 }
307
313 public function stop()
314 {
315 $this->writeLog('Stop service ' . $this->getName());
316
317 $exec = $this->exec('stop ' . $this->getName());
318 if ($exec === false) {
319 return false;
320 }
321
322 if (!$this->isStopped()) {
323 $this->latestError = null;
324
325 return false;
326 }
327
328 return true;
329 }
330
336 public function restart()
337 {
338 if ($this->stop()) {
339 return $this->start();
340 }
341
342 return false;
343 }
344
350 public function infos()
351 {
352 global $bearsamppRegistry;
353
354 $infos = Win32Native::getServiceInfo($this->getName());
355 if ($infos === false) {
356 return false;
357 }
358
359 $infosNssm = array();
360 $infosKeys = array(
361 self::INFO_APPLICATION,
362 self::INFO_APP_PARAMETERS,
363 );
364
365 foreach ($infosKeys as $infoKey) {
366 $value = null;
367 $exists = $bearsamppRegistry->exists(
369 'SYSTEM\CurrentControlSet\Services\\' . $this->getName() . '\Parameters',
370 $infoKey
371 );
372 if ($exists) {
373 $value = $bearsamppRegistry->getValue(
375 'SYSTEM\CurrentControlSet\Services\\' . $this->getName() . '\Parameters',
376 $infoKey
377 );
378 }
379 $infosNssm[$infoKey] = $value;
380 }
381
382 if (!isset($infosNssm[self::INFO_APPLICATION])) {
383 return $infos;
384 }
385
386 $infos[Win32Service::VBS_PATH_NAME] = $infosNssm[Nssm::INFO_APPLICATION] . ' ' . $infosNssm[Nssm::INFO_APP_PARAMETERS];
387
388 return $infos;
389 }
390
396 public function isInstalled()
397 {
398 $status = $this->status();
399 $this->writeLog('isInstalled ' . $this->getName() . ': ' . ($status != self::STATUS_NA ? 'YES' : 'NO') . ' (status: ' . $status . ')');
400
401 return $status != self::STATUS_NA;
402 }
403
409 public function isRunning()
410 {
411 $status = $this->status();
412 $this->writeLog('isRunning ' . $this->getName() . ': ' . ($status == self::STATUS_RUNNING ? 'YES' : 'NO') . ' (status: ' . $status . ')');
413
414 return $status == self::STATUS_RUNNING;
415 }
416
422 public function isStopped()
423 {
424 $status = $this->status();
425 $this->writeLog('isStopped ' . $this->getName() . ': ' . ($status == self::STATUS_STOPPED ? 'YES' : 'NO') . ' (status: ' . $status . ')');
426
427 return $status == self::STATUS_STOPPED;
428 }
429
435 public function isPaused()
436 {
437 $status = $this->status();
438 $this->writeLog('isPaused ' . $this->getName() . ': ' . ($status == self::STATUS_PAUSED ? 'YES' : 'NO') . ' (status: ' . $status . ')');
439
440 return $status == self::STATUS_PAUSED;
441 }
442
450 public function isPending($status)
451 {
452 return $status == self::STATUS_START_PENDING || $status == self::STATUS_STOP_PENDING
453 || $status == self::STATUS_CONTINUE_PENDING || $status == self::STATUS_PAUSE_PENDING;
454 }
455
463 private function getServiceStatusDesc($status)
464 {
465 switch ($status) {
466 case self::STATUS_CONTINUE_PENDING:
467 return 'The service continue is pending.';
468
469 case self::STATUS_PAUSE_PENDING:
470 return 'The service pause is pending.';
471
472 case self::STATUS_PAUSED:
473 return 'The service is paused.';
474
475 case self::STATUS_RUNNING:
476 return 'The service is running.';
477
478 case self::STATUS_START_PENDING:
479 return 'The service is starting.';
480
481 case self::STATUS_STOP_PENDING:
482 return 'The service is stopping.';
483
484 case self::STATUS_STOPPED:
485 return 'The service is not running.';
486
487 case self::STATUS_NA:
488 return 'Cannot retrieve service status.';
489
490 default:
491 return null;
492 }
493 }
494
500 public function getName()
501 {
502 return $this->name;
503 }
504
510 public function setName($name)
511 {
512 $this->name = $name;
513 }
514
520 public function getDisplayName()
521 {
522 return $this->displayName;
523 }
524
531 {
532 $this->displayName = $displayName;
533 }
534
540 public function getBinPath()
541 {
542 return $this->binPath;
543 }
544
550 public function setBinPath($binPath)
551 {
552 $this->binPath = str_replace('"', '', Path::formatWindowsPath($binPath));
553 }
554
560 public function getParams()
561 {
562 return $this->params;
563 }
564
570 public function setParams($params)
571 {
572 $this->params = $params;
573 }
574
580 public function getStart()
581 {
582 return $this->start;
583 }
584
590 public function setStart($start)
591 {
592 $this->start = $start;
593 }
594
600 public function getStdout()
601 {
602 return $this->stdout;
603 }
604
610 public function setStdout($stdout)
611 {
612 $this->stdout = $stdout;
613 }
614
620 public function getStderr()
621 {
622 return $this->stderr;
623 }
624
630 public function setStderr($stderr)
631 {
632 $this->stderr = $stderr;
633 }
634
640 public function getEnvironmentExtra()
641 {
643 }
644
651 {
652 $this->environmentExtra = Path::formatWindowsPath($environmentExtra);
653 }
654
660 public function getLatestStatus()
661 {
662 return $this->latestStatus;
663 }
664
670 public function getLatestError()
671 {
672 return $this->latestError;
673 }
674
680 public function getError()
681 {
682 global $bearsamppLang;
683
684 if (!empty($this->latestError)) {
685 return $bearsamppLang->getValue(Lang::ERROR) . ' ' . $this->latestError;
686 } elseif ($this->latestStatus != self::STATUS_NA) {
687 return $bearsamppLang->getValue(Lang::STATUS) . ' ' . $this->latestStatus . ' : ' . $this->getWin32ServiceStatusDesc($this->latestStatus);
688 }
689
690 return null;
691 }
692}
$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=[])
if( $raw) foreach($opts as $opt) if( $store_system_config) if($store_user_config) $command
Definition pearcmd.php:322