Bearsampp 2025.8.29
Loading...
Searching...
No Matches
class.win32service.php
Go to the documentation of this file.
1<?php
2/*
3 *
4 * * Copyright (c) 2022-2025 Bearsampp
5 * * License: GNU General Public License version 3 or later; see LICENSE.txt
6 * * Website: https://bearsampp.com
7 * * Github: https://github.com/Bearsampp
8 *
9 */
10
18{
19 // Win32Service Service Status Constants
27 const WIN32_SERVICE_NA = '0';
28
29 // Win32 Error Codes
59 const WIN32_NO_ERROR = '0';
60
63
64 const SERVICE_AUTO_START = '2';
66 const SERVICE_DISABLED = '4';
67
68 const PENDING_TIMEOUT = 20;
69 const SLEEP_TIME = 500000;
70
71 const VBS_NAME = 'Name';
72 const VBS_DISPLAY_NAME = 'DisplayName';
73 const VBS_DESCRIPTION = 'Description';
74 const VBS_PATH_NAME = 'PathName';
75 const VBS_STATE = 'State';
76
77 private $name;
78 private $displayName;
79 private $binPath;
80 private $params;
81 private $startType;
83 private $nssm;
84
86 private $latestError;
87
88 // Track which functions have been logged to avoid duplicate log entries
89 private static $loggedFunctions = array();
90
96 public function __construct($name)
97 {
98 Util::logInitClass( $this );
99 $this->name = $name;
100 }
101
107 private function writeLog($log): void
108 {
109 global $bearsamppRoot;
110 Util::logDebug( $log, $bearsamppRoot->getServicesLogFilePath() );
111 }
112
118 public static function getVbsKeys(): array
119 {
120 return array(
121 self::VBS_NAME,
122 self::VBS_DISPLAY_NAME,
123 self::VBS_DESCRIPTION,
124 self::VBS_PATH_NAME,
125 self::VBS_STATE
126 );
127 }
128
138 private function callWin32Service($function, $param, $checkError = false): mixed
139 {
140 $result = false;
141 if ( function_exists( $function ) ) {
142 if (!isset(self::$loggedFunctions[$function])) {
143 Util::logTrace('Win32 function: ' . $function . ' exists');
144 self::$loggedFunctions[$function] = true;
145 }
146
147 // Special handling for win32_query_service_status to prevent hanging
148 if ($function === 'win32_query_service_status') {
149 Util::logTrace("Using enhanced handling for win32_query_service_status");
150
151 // Set a shorter timeout for this specific function
152 $originalTimeout = ini_get('max_execution_time');
153 set_time_limit(5); // 5 seconds timeout
154
155 try {
156 // Ensure proper parameter handling for PHP 8.2.3 compatibility
157 $result = call_user_func($function, $param);
158
159 // Reset the timeout
160 set_time_limit($originalTimeout);
161
162 if ($checkError && $result !== null) {
163 // Convert to int before using dechex for PHP 8.2.3 compatibility
164 $resultInt = is_numeric($result) ? (int)$result : 0;
165 if (dechex($resultInt) != self::WIN32_NO_ERROR) {
166 $this->latestError = dechex($resultInt);
167 }
168 }
169 } catch (\Win32ServiceException $e) {
170 // Reset the timeout
171 set_time_limit($originalTimeout);
172
173 Util::logTrace("Win32ServiceException caught: " . $e->getMessage());
174
175 // Handle "service does not exist" exception
176 if (strpos($e->getMessage(), 'service does not exist') !== false) {
177 Util::logTrace("Service does not exist exception handled for: " . $param);
178 // Return the appropriate error code for "service does not exist"
179 $result = hexdec(self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST);
180 } else {
181 // For other exceptions, log and return false
182 Util::logTrace("Unhandled Win32ServiceException: " . $e->getMessage());
183 $result = false;
184 }
185 } catch (\Exception $e) {
186 // Reset the timeout
187 set_time_limit($originalTimeout);
188
189 // Catch any other exceptions to prevent application freeze
190 Util::logTrace("Exception caught in callWin32Service: " . $e->getMessage());
191 $result = false;
192 } catch (\Throwable $e) {
193 // Reset the timeout
194 set_time_limit($originalTimeout);
195
196 // Catch any other throwable (PHP 7+) to prevent application freeze
197 Util::logTrace("Throwable caught in callWin32Service: " . $e->getMessage());
198 $result = false;
199 }
200 } else {
201 // Standard handling for other functions
202 try {
203 // Ensure proper parameter handling for PHP 8.2.3 compatibility
204 $result = call_user_func($function, $param);
205 if ($checkError && $result !== null) {
206 // Convert to int before using dechex for PHP 8.2.3 compatibility
207 $resultInt = is_numeric($result) ? (int)$result : 0;
208 if (dechex($resultInt) != self::WIN32_NO_ERROR) {
209 $this->latestError = dechex($resultInt);
210 }
211 }
212 } catch (\Win32ServiceException $e) {
213 Util::logTrace("Win32ServiceException caught: " . $e->getMessage());
214
215 // Handle "service does not exist" exception
216 if (strpos($e->getMessage(), 'service does not exist') !== false) {
217 Util::logTrace("Service does not exist exception handled for: " . $param);
218 // Return the appropriate error code for "service does not exist"
219 $result = hexdec(self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST);
220 } else {
221 // For other exceptions, log and return false
222 Util::logTrace("Unhandled Win32ServiceException: " . $e->getMessage());
223 $result = false;
224 }
225 } catch (\Exception $e) {
226 // Catch any other exceptions to prevent application freeze
227 Util::logTrace("Exception caught in callWin32Service: " . $e->getMessage());
228 $result = false;
229 } catch (\Throwable $e) {
230 // Catch any other throwable (PHP 7+) to prevent application freeze
231 Util::logTrace("Throwable caught in callWin32Service: " . $e->getMessage());
232 $result = false;
233 }
234 }
235 } else {
236 if (!isset(self::$loggedFunctions[$function])) {
237 Util::logTrace('Win32 function: ' . $function . ' missing');
238 self::$loggedFunctions[$function] = true;
239 }
240 }
241 return $result;
242 }
243
251 public function status($timeout = true): string
252 {
253 usleep( self::SLEEP_TIME );
254
255 $this->latestStatus = self::WIN32_SERVICE_NA;
256 $maxtime = time() + self::PENDING_TIMEOUT;
257
258 Util::logTrace("Querying status for service: " . $this->getName() . " (timeout: " . ($timeout ? "enabled" : "disabled") . ")");
259 if ($timeout) {
260 Util::logTrace("Max timeout time set to: " . date('Y-m-d H:i:s', $maxtime));
261 }
262
263 // Add a safety counter to prevent infinite loops
264 $loopCount = 0;
265 $maxLoops = 5; // Maximum number of attempts
266 $startTime = microtime(true);
267
268 try {
269 while ( ($this->latestStatus == self::WIN32_SERVICE_NA || $this->isPending( $this->latestStatus )) && $loopCount < $maxLoops ) {
270 $loopCount++;
271 Util::logTrace("Calling win32_query_service_status for service: " . $this->getName() . " (attempt " . $loopCount . " of " . $maxLoops . ")");
272
273 // Add a timeout check before making the call
274 if (microtime(true) - $startTime > 10) { // 10 seconds overall timeout
275 Util::logTrace("Overall timeout reached before making service status call");
276 break;
277 }
278
279 $this->latestStatus = $this->callWin32Service( 'win32_query_service_status', $this->getName() );
280
281 if ( is_array( $this->latestStatus ) && isset( $this->latestStatus['CurrentState'] ) ) {
282 // Ensure proper type conversion for PHP 8.2.3 compatibility
283 $stateInt = is_numeric($this->latestStatus['CurrentState']) ? (int)$this->latestStatus['CurrentState'] : 0;
284 $this->latestStatus = dechex( $stateInt );
285 Util::logTrace("Service status returned as array, CurrentState: " . $this->latestStatus);
286 }
287 elseif ( $this->latestStatus !== null ) {
288 // Ensure proper type conversion for PHP 8.2.3 compatibility
289 $statusInt = is_numeric($this->latestStatus) ? (int)$this->latestStatus : 0;
290 $statusHex = dechex( $statusInt );
291 Util::logTrace("Service status returned as value: " . $statusHex);
292
293 if ( $statusHex == self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST ) {
294 $this->latestStatus = $statusHex;
295 Util::logTrace("Service does not exist, breaking loop");
296 break; // Exit the loop immediately if service doesn't exist
297 }
298 } else {
299 Util::logTrace("Service status query returned null");
300 // If we get a null result, assume service does not exist to avoid hanging
301 if ($loopCount >= 2) { // Only do this after at least one retry
302 Util::logTrace("Multiple null results, assuming service does not exist");
303 $this->latestStatus = self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST;
304 break;
305 }
306 }
307
308 if ( $timeout && $maxtime < time() ) {
309 Util::logTrace("Timeout reached while querying service status");
310 break;
311 }
312
313 // Only sleep if we're going to loop again
314 if ($loopCount < $maxLoops && ($this->latestStatus == self::WIN32_SERVICE_NA || $this->isPending($this->latestStatus))) {
315 Util::logTrace("Sleeping before next status check attempt");
316 usleep(self::SLEEP_TIME);
317 }
318 }
319 } catch (\Exception $e) {
320 Util::logTrace("Exception in status method: " . $e->getMessage());
321 // If an exception occurs, assume service does not exist
322 $this->latestStatus = self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST;
323 } catch (\Throwable $e) {
324 Util::logTrace("Throwable in status method: " . $e->getMessage());
325 // If a throwable occurs, assume service does not exist
326 $this->latestStatus = self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST;
327 }
328
329 if ($loopCount >= $maxLoops) {
330 Util::logTrace("Maximum query attempts reached for service: " . $this->getName());
331 }
332
333 $elapsedTime = microtime(true) - $startTime;
334 Util::logTrace("Status check completed in " . round($elapsedTime, 2) . " seconds after " . $loopCount . " attempts");
335
336 if ( $this->latestStatus == self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST ) {
337 $this->latestError = $this->latestStatus;
338 $this->latestStatus = self::WIN32_SERVICE_NA;
339 Util::logTrace("Service does not exist, setting status to NA");
340 }
341
342 Util::logTrace("Final status for service " . $this->getName() . ": " . $this->latestStatus);
343 return $this->latestStatus;
344 }
345
351 public function create(): bool
352 {
353 global $bearsamppBins;
354
355 Util::logTrace("Starting Win32Service::create for service: " . $this->getName());
356
357 if ( $this->getName() == BinPostgresql::SERVICE_NAME ) {
358 Util::logTrace("PostgreSQL service detected - using specialized installation");
359 $bearsamppBins->getPostgresql()->rebuildConf();
360 Util::logTrace("PostgreSQL configuration rebuilt");
361
362 $bearsamppBins->getPostgresql()->initData();
363 Util::logTrace("PostgreSQL data initialized");
364
366 Util::logTrace("PostgreSQL service installation " . ($result ? "succeeded" : "failed"));
367 return $result;
368 }
369
370 if ( $this->getNssm() instanceof Nssm ) {
371 Util::logTrace("Using NSSM for service installation");
372
373 $nssmEnvPath = Util::getAppBinsRegKey( false );
374 Util::logTrace("NSSM environment path (bins): " . $nssmEnvPath);
375
376 $nssmEnvPath .= Util::getNssmEnvPaths();
377 Util::logTrace("NSSM environment path (with additional paths): " . $nssmEnvPath);
378
379 $nssmEnvPath .= '%SystemRoot%/system32;';
380 $nssmEnvPath .= '%SystemRoot%;';
381 $nssmEnvPath .= '%SystemRoot%/system32/Wbem;';
382 $nssmEnvPath .= '%SystemRoot%/system32/WindowsPowerShell/v1.0';
383 Util::logTrace("NSSM final environment PATH: " . $nssmEnvPath);
384
385 $this->getNssm()->setEnvironmentExtra( 'PATH=' . $nssmEnvPath );
386 Util::logTrace("NSSM service parameters:");
387 Util::logTrace("-> Name: " . $this->getNssm()->getName());
388 Util::logTrace("-> DisplayName: " . $this->getNssm()->getDisplayName());
389 Util::logTrace("-> BinPath: " . $this->getNssm()->getBinPath());
390 Util::logTrace("-> Params: " . $this->getNssm()->getParams());
391 Util::logTrace("-> Start: " . $this->getNssm()->getStart());
392 Util::logTrace("-> Stdout: " . $this->getNssm()->getStdout());
393 Util::logTrace("-> Stderr: " . $this->getNssm()->getStderr());
394
395 $result = $this->getNssm()->create();
396 Util::logTrace("NSSM service creation " . ($result ? "succeeded" : "failed"));
397 if (!$result) {
398 Util::logTrace("NSSM error: " . $this->getNssm()->getLatestError());
399 }
400 return $result;
401 }
402
403 Util::logTrace("Using win32_create_service for service installation");
404 $serviceParams = array(
405 'service' => $this->getName(),
406 'display' => $this->getDisplayName(),
407 'description' => $this->getDisplayName(),
408 'path' => $this->getBinPath(),
409 'params' => $this->getParams(),
410 'start_type' => $this->getStartType() != null ? $this->getStartType() : self::SERVICE_DEMAND_START,
411 'error_control' => $this->getErrorControl() != null ? $this->getErrorControl() : self::SERVER_ERROR_NORMAL,
412 );
413
414 Util::logTrace("win32_create_service parameters:");
415 foreach ($serviceParams as $key => $value) {
416 Util::logTrace("-> $key: $value");
417 }
418
419 $result = $this->callWin32Service( 'win32_create_service', $serviceParams, true );
420 // Ensure proper type conversion for PHP 8.2.3 compatibility
421 $resultInt = is_numeric($result) ? (int)$result : 0;
422 $create = $result !== null ? dechex( $resultInt ) : '0';
423 Util::logTrace("win32_create_service result code: " . $create);
424
425 $this->writeLog( 'Create service: ' . $create . ' (status: ' . $this->status() . ')' );
426 $this->writeLog( '-> service: ' . $this->getName() );
427 $this->writeLog( '-> display: ' . $this->getDisplayName() );
428 $this->writeLog( '-> description: ' . $this->getDisplayName() );
429 $this->writeLog( '-> path: ' . $this->getBinPath() );
430 $this->writeLog( '-> params: ' . $this->getParams() );
431 $this->writeLog( '-> start_type: ' . ($this->getStartType() != null ? $this->getStartType() : self::SERVICE_DEMAND_START) );
432 $this->writeLog( '-> service: ' . ($this->getErrorControl() != null ? $this->getErrorControl() : self::SERVER_ERROR_NORMAL) );
433
434 if ( $create != self::WIN32_NO_ERROR ) {
435 Util::logTrace("Service creation failed with error code: " . $create);
436 return false;
437 }
438 elseif ( !$this->isInstalled() ) {
439 Util::logTrace("Service created but not found as installed");
440 $this->latestError = self::WIN32_NO_ERROR;
441 return false;
442 }
443
444 Util::logTrace("Service created successfully: " . $this->getName());
445 return true;
446 }
447
453 public function delete(): bool
454 {
455 Util::logTrace("Starting Win32Service::delete for service: " . $this->getName());
456 Util::logTrace("Checking if service is installed: " . $this->getName());
457
458 if ( !$this->isInstalled() ) {
459 Util::logTrace("Service is not installed, skipping deletion: " . $this->getName());
460 return true;
461 }
462
463 Util::logTrace("Stopping service before deletion: " . $this->getName());
464 $this->stop();
465
466 if ( $this->getName() == BinPostgresql::SERVICE_NAME ) {
467 Util::logTrace("PostgreSQL service detected - using specialized uninstallation");
469 Util::logTrace("PostgreSQL service uninstallation " . ($result ? "succeeded" : "failed"));
470 return $result;
471 }
472
473 Util::logTrace("Calling win32_delete_service for service: " . $this->getName());
474 $result = $this->callWin32Service( 'win32_delete_service', $this->getName(), true );
475 // Ensure proper type conversion for PHP 8.2.3 compatibility
476 $resultInt = is_numeric($result) ? (int)$result : 0;
477 $delete = $result !== null ? dechex( $resultInt ) : '0';
478 Util::logTrace("Delete service result code: " . $delete);
479 $this->writeLog( 'Delete service ' . $this->getName() . ': ' . $delete . ' (status: ' . $this->status() . ')' );
480
481 if ( $delete != self::WIN32_NO_ERROR && $delete != self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST ) {
482 return false;
483 }
484 elseif ( $this->isInstalled() ) {
485 $this->latestError = self::WIN32_NO_ERROR;
486
487 return false;
488 }
489
490 return true;
491 }
492
498 public function reset(): bool
499 {
500 if ( $this->delete() ) {
501 usleep( self::SLEEP_TIME );
502
503 return $this->create();
504 }
505
506 return false;
507 }
508
514 public function start(): bool
515 {
516 global $bearsamppBins;
517
518 Util::logInfo('Attempting to start service: ' . $this->getName());
519
520 if ( $this->getName() == BinMysql::SERVICE_NAME ) {
521 $bearsamppBins->getMysql()->initData();
522 }
523 elseif ( $this->getName() == BinMailpit::SERVICE_NAME ) {
524 $bearsamppBins->getMailpit()->rebuildConf();
525 }
526 elseif ( $this->getName() == BinMemcached::SERVICE_NAME ) {
527 $bearsamppBins->getMemcached()->rebuildConf();
528 }
529 elseif ( $this->getName() == BinPostgresql::SERVICE_NAME ) {
530 $bearsamppBins->getPostgresql()->rebuildConf();
531 $bearsamppBins->getPostgresql()->initData();
532 }
533 elseif ( $this->getName() == BinXlight::SERVICE_NAME ) {
534 $bearsamppBins->getXlight()->rebuildConf();
535 }
536
537
538 $result = $this->callWin32Service( 'win32_start_service', $this->getName(), true );
539 // Ensure proper type conversion for PHP 8.2.3 compatibility
540 $resultInt = is_numeric($result) ? (int)$result : 0;
541 $start = $result !== null ? dechex( $resultInt ) : '0';
542 Util::logDebug( 'Start service ' . $this->getName() . ': ' . $start . ' (status: ' . $this->status() . ')' );
543
544 if ( $start != self::WIN32_NO_ERROR && $start != self::WIN32_ERROR_SERVICE_ALREADY_RUNNING ) {
545
546 // Write error to log
547 Util::logError('Failed to start service: ' . $this->getName() . ' with error code: ' . $start);
548
549 if ( $this->getName() == BinApache::SERVICE_NAME ) {
550 $cmdOutput = $bearsamppBins->getApache()->getCmdLineOutput( BinApache::CMD_SYNTAX_CHECK );
551 if ( !$cmdOutput['syntaxOk'] ) {
552 file_put_contents(
553 $bearsamppBins->getApache()->getErrorLog(),
554 '[' . date( 'Y-m-d H:i:s', time() ) . '] [error] ' . $cmdOutput['content'] . PHP_EOL,
555 FILE_APPEND
556 );
557 }
558 }
559 elseif ( $this->getName() == BinMysql::SERVICE_NAME ) {
560 $cmdOutput = $bearsamppBins->getMysql()->getCmdLineOutput( BinMysql::CMD_SYNTAX_CHECK );
561 if ( !$cmdOutput['syntaxOk'] ) {
562 file_put_contents(
563 $bearsamppBins->getMysql()->getErrorLog(),
564 '[' . date( 'Y-m-d H:i:s', time() ) . '] [error] ' . $cmdOutput['content'] . PHP_EOL,
565 FILE_APPEND
566 );
567 }
568 }
569 elseif ( $this->getName() == BinMariadb::SERVICE_NAME ) {
570 $cmdOutput = $bearsamppBins->getMariadb()->getCmdLineOutput( BinMariadb::CMD_SYNTAX_CHECK );
571 if ( !$cmdOutput['syntaxOk'] ) {
572 file_put_contents(
573 $bearsamppBins->getMariadb()->getErrorLog(),
574 '[' . date( 'Y-m-d H:i:s', time() ) . '] [error] ' . $cmdOutput['content'] . PHP_EOL,
575 FILE_APPEND
576 );
577 }
578 }
579
580 return false;
581 }
582 elseif ( !$this->isRunning() ) {
583 $this->latestError = self::WIN32_NO_ERROR;
584 Util::logError('Service ' . $this->getName() . ' is not running after start attempt.');
585 $this->latestError = null;
586 return false;
587 }
588
589 Util::logInfo('Service ' . $this->getName() . ' started successfully.');
590 return true;
591 }
592
598 public function stop(): bool
599 {
600 Util::logTrace("Starting Win32Service::stop for service: " . $this->getName());
601
602 Util::logTrace("Calling win32_stop_service for service: " . $this->getName());
603 $result = $this->callWin32Service( 'win32_stop_service', $this->getName(), true );
604
605 // Ensure proper type conversion for PHP 8.2.3 compatibility
606 $resultInt = is_numeric($result) ? (int)$result : 0;
607 $stop = $result !== null ? dechex( $resultInt ) : '0';
608 Util::logTrace("Stop service result code: " . $stop);
609
610 Util::logTrace("Checking current status after stop attempt");
611 $currentStatus = $this->status();
612 Util::logTrace("Current status: " . $currentStatus);
613
614 $this->writeLog( 'Stop service ' . $this->getName() . ': ' . $stop . ' (status: ' . $currentStatus . ')' );
615
616 if ( $stop != self::WIN32_NO_ERROR ) {
617 return false;
618 }
619 elseif ( !$this->isStopped() ) {
620 $this->latestError = self::WIN32_NO_ERROR;
621
622 return false;
623 }
624
625 return true;
626 }
627
633 public function restart(): bool
634 {
635 if ( $this->stop() ) {
636 return $this->start();
637 }
638
639 return false;
640 }
641
647 public function infos()
648 {
649 Util::logTrace("Starting Win32Service::infos for service: " . $this->getName());
650
651 try {
652 // Set a timeout for the entire operation
653 $startTime = microtime(true);
654 $timeout = 10; // 10 seconds timeout for the entire operation
655
656 if ($this->getNssm() instanceof Nssm) {
657 Util::logTrace("Using NSSM to get service info");
658 $result = $this->getNssm()->infos();
659 Util::logTrace("NSSM info retrieval completed in " . round(microtime(true) - $startTime, 2) . " seconds");
660 return $result;
661 }
662
663 Util::logTrace("Using VBS to get service info");
664
665 // Use set_time_limit to prevent PHP script timeout
666 $originalTimeout = ini_get('max_execution_time');
667 set_time_limit(15); // 15 seconds timeout
668
669 // Create a separate process to get service info with a timeout
671
672 // Reset the timeout
673 set_time_limit($originalTimeout);
674
675 // Check if we've exceeded our timeout
676 if (microtime(true) - $startTime > $timeout) {
677 Util::logTrace("Timeout exceeded in infos() method, returning false");
678 return false;
679 }
680
681 Util::logTrace("VBS info retrieval completed in " . round(microtime(true) - $startTime, 2) . " seconds");
682 return $result;
683 } catch (\Exception $e) {
684 Util::logTrace("Exception in infos() method: " . $e->getMessage() . ", returning false");
685 return false;
686 } catch (\Throwable $e) {
687 Util::logTrace("Throwable in infos() method: " . $e->getMessage() . ", returning false");
688 return false;
689 }
690 }
691
697 public function isInstalled(): bool
698 {
699 Util::logTrace("Checking if service is installed: " . $this->getName());
700
701 try {
702 // Set a timeout for the entire operation
703 $startTime = microtime(true);
704 $timeout = 15; // 15 seconds timeout for the entire operation
705
706 // Call status() with a try-catch to ensure we don't get stuck
707 $status = $this->status();
708
709 // Check if we've exceeded our timeout
710 if (microtime(true) - $startTime > $timeout) {
711 Util::logTrace("Timeout exceeded in isInstalled() method, assuming service is not installed");
712 $this->writeLog('isInstalled ' . $this->getName() . ': NO (timeout exceeded)');
713 return false;
714 }
715
716 $isInstalled = $status != self::WIN32_SERVICE_NA;
717
718 Util::logTrace("Service " . $this->getName() . " installation status: " . ($isInstalled ? "YES" : "NO") . " (status code: " . $status . ")");
719 $this->writeLog('isInstalled ' . $this->getName() . ': ' . ($isInstalled ? 'YES' : 'NO') . ' (status: ' . $status . ')');
720
721 return $isInstalled;
722 } catch (\Exception $e) {
723 Util::logTrace("Exception in isInstalled() method: " . $e->getMessage() . ", assuming service is not installed");
724 $this->writeLog('isInstalled ' . $this->getName() . ': NO (exception: ' . $e->getMessage() . ')');
725 return false;
726 } catch (\Throwable $e) {
727 Util::logTrace("Throwable in isInstalled() method: " . $e->getMessage() . ", assuming service is not installed");
728 $this->writeLog('isInstalled ' . $this->getName() . ': NO (throwable: ' . $e->getMessage() . ')');
729 return false;
730 }
731 }
732
738 public function isRunning(): bool
739 {
740 Util::logTrace("Checking if service is running: " . $this->getName());
741
742 $status = $this->status();
743 $isRunning = $status == self::WIN32_SERVICE_RUNNING;
744
745 Util::logTrace("Service " . $this->getName() . " running status: " . ($isRunning ? "YES" : "NO") . " (status code: " . $status . ")");
746 $this->writeLog( 'isRunning ' . $this->getName() . ': ' . ($isRunning ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
747
748 return $isRunning;
749 }
750
756 public function isStopped(): bool
757 {
758 Util::logTrace("Checking if service is stopped: " . $this->getName());
759
760 $status = $this->status();
761 $isStopped = $status == self::WIN32_SERVICE_STOPPED;
762
763 Util::logTrace("Service " . $this->getName() . " stopped status: " . ($isStopped ? "YES" : "NO") . " (status code: " . $status . ")");
764 $this->writeLog( 'isStopped ' . $this->getName() . ': ' . ($isStopped ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
765
766 return $isStopped;
767 }
768
774 public function isPaused(): bool
775 {
776 Util::logTrace("Checking if service is paused: " . $this->getName());
777
778 $status = $this->status();
779 $isPaused = $status == self::WIN32_SERVICE_PAUSED;
780
781 Util::logTrace("Service " . $this->getName() . " paused status: " . ($isPaused ? "YES" : "NO") . " (status code: " . $status . ")");
782 $this->writeLog( 'isPaused ' . $this->getName() . ': ' . ($isPaused ? 'YES' : 'NO') . ' (status: ' . $status . ')' );
783
784 return $isPaused;
785 }
786
794 public function isPending($status): bool
795 {
796 $isPending = $status == self::WIN32_SERVICE_START_PENDING || $status == self::WIN32_SERVICE_STOP_PENDING
797 || $status == self::WIN32_SERVICE_CONTINUE_PENDING || $status == self::WIN32_SERVICE_PAUSE_PENDING;
798
799 Util::logTrace("Checking if status is pending: " . $status . " - Result: " . ($isPending ? "YES" : "NO"));
800
801 if ($isPending) {
802 if ($status == self::WIN32_SERVICE_START_PENDING) {
803 Util::logTrace("Service is in START_PENDING state");
804 } else if ($status == self::WIN32_SERVICE_STOP_PENDING) {
805 Util::logTrace("Service is in STOP_PENDING state");
806 } else if ($status == self::WIN32_SERVICE_CONTINUE_PENDING) {
807 Util::logTrace("Service is in CONTINUE_PENDING state");
808 } else if ($status == self::WIN32_SERVICE_PAUSE_PENDING) {
809 Util::logTrace("Service is in PAUSE_PENDING state");
810 }
811 }
812
813 return $isPending;
814 }
815
823 private function getWin32ServiceStatusDesc($status): ?string
824 {
825 switch ( $status ) {
826 case self::WIN32_SERVICE_CONTINUE_PENDING:
827 return 'The service continue is pending.';
828
829 case self::WIN32_SERVICE_PAUSE_PENDING:
830 return 'The service pause is pending.';
831
832 case self::WIN32_SERVICE_PAUSED:
833 return 'The service is paused.';
834
835 case self::WIN32_SERVICE_RUNNING:
836 return 'The service is running.';
837
838 case self::WIN32_SERVICE_START_PENDING:
839 return 'The service is starting.';
840
841 case self::WIN32_SERVICE_STOP_PENDING:
842 return 'The service is stopping.';
843
844 case self::WIN32_SERVICE_STOPPED:
845 return 'The service is not running.';
846
847 case self::WIN32_SERVICE_NA:
848 return 'Cannot retrieve service status.';
849
850 default:
851 return null;
852 }
853 }
854
862 private function getWin32ErrorCodeDesc($code): ?string
863 {
864 switch ( $code ) {
865 case self::WIN32_ERROR_ACCESS_DENIED:
866 return 'The handle to the SCM database does not have the appropriate access rights.';
867 // ... other cases ...
868 default:
869 return null;
870 }
871 }
872
878 public function getName(): string
879 {
880 return $this->name;
881 }
882
888 public function setName($name): void
889 {
890 $this->name = $name;
891 }
892
898 public function getDisplayName(): string
899 {
900 return $this->displayName;
901 }
902
908 public function setDisplayName($displayName): void
909 {
910 $this->displayName = $displayName;
911 }
912
918 public function getBinPath(): string
919 {
920 return $this->binPath;
921 }
922
928 public function setBinPath($binPath): void
929 {
930 $this->binPath = str_replace( '"', '', Util::formatWindowsPath( $binPath ) );
931 }
932
938 public function getParams(): string
939 {
940 return $this->params;
941 }
942
948 public function setParams($params): void
949 {
950 $this->params = $params;
951 }
952
958 public function getStartType(): string
959 {
960 return $this->startType;
961 }
962
968 public function setStartType($startType): void
969 {
970 $this->startType = $startType;
971 }
972
978 public function getErrorControl(): string
979 {
980 return $this->errorControl;
981 }
982
988 public function setErrorControl($errorControl): void
989 {
990 $this->errorControl = $errorControl;
991 }
992
998 public function getNssm()
999 {
1000 return $this->nssm;
1001 }
1002
1008 public function setNssm($nssm)
1009 {
1010 if ( $nssm instanceof Nssm ) {
1011 $this->setDisplayName( $nssm->getDisplayName() );
1012 $this->setBinPath( $nssm->getBinPath() );
1013 $this->setParams( $nssm->getParams() );
1014 $this->setStartType( $nssm->getStart() );
1015 $this->nssm = $nssm;
1016 }
1017 }
1018
1024 public function getLatestStatus()
1025 {
1026 return $this->latestStatus;
1027 }
1028
1034 public function getLatestError()
1035 {
1036 return $this->latestError;
1037 }
1038
1044 public function getError()
1045 {
1046 global $bearsamppLang;
1047 if ( $this->latestError != self::WIN32_NO_ERROR ) {
1048 // Ensure proper type conversion for PHP 8.2.3 compatibility
1049 $errorInt = is_numeric($this->latestError) ? hexdec( $this->latestError ) : 0;
1050 return $bearsamppLang->getValue( Lang::ERROR ) . ' ' .
1051 $this->latestError . ' (' . $errorInt . ' : ' . $this->getWin32ErrorCodeDesc( $this->latestError ) . ')';
1052 }
1053 elseif ( $this->latestStatus != self::WIN32_SERVICE_NA ) {
1054 // Ensure proper type conversion for PHP 8.2.3 compatibility
1055 $statusInt = is_numeric($this->latestStatus) ? hexdec( $this->latestStatus ) : 0;
1056 return $bearsamppLang->getValue( Lang::STATUS ) . ' ' .
1057 $this->latestStatus . ' (' . $statusInt . ' : ' . $this->getWin32ServiceStatusDesc( $this->latestStatus ) . ')';
1058 }
1059
1060 return null;
1061 }
1062}
$result
global $bearsamppBins
global $bearsamppLang
global $bearsamppRoot
static installPostgresqlService()
static uninstallPostgresqlService()
const CMD_SYNTAX_CHECK
const SERVICE_NAME
const CMD_SYNTAX_CHECK
const ERROR
const STATUS
static logError($data, $file=null)
static logInitClass($classInstance)
static logTrace($data, $file=null)
static logInfo($data, $file=null)
static getAppBinsRegKey($fromRegistry=true)
static logDebug($data, $file=null)
static getNssmEnvPaths()
static formatWindowsPath($path)
static getServiceInfos($serviceName)
setErrorControl($errorControl)
const WIN32_SERVICE_START_PENDING
const WIN32_ERROR_SERVICE_DATABASE_LOCKED
const WIN32_ERROR_ACCESS_DENIED
callWin32Service($function, $param, $checkError=false)
const WIN32_ERROR_SERVICE_NOT_ACTIVE
const WIN32_ERROR_INVALID_LEVEL
const WIN32_ERROR_FAILED_SERVICE_CONTROLLER_CONNECT
const WIN32_ERROR_DUPLICATE_SERVICE_NAME
const WIN32_ERROR_INVALID_HANDLE
const WIN32_ERROR_SERVICE_MARKED_FOR_DELETE
const WIN32_ERROR_SERVICE_DEPENDENCY_DELETED
const WIN32_ERROR_DATABASE_DOES_NOT_EXIST
const WIN32_ERROR_CIRCULAR_DEPENDENCY
const WIN32_ERROR_INVALID_SERVICE_CONTROL
const WIN32_ERROR_SERVICE_CANNOT_ACCEPT_CTRL
const WIN32_ERROR_INVALID_PARAMETER
getWin32ServiceStatusDesc($status)
const WIN32_ERROR_SERVICE_ALREADY_RUNNING
status($timeout=true)
const WIN32_SERVICE_CONTINUE_PENDING
const WIN32_ERROR_SERVICE_DEPENDENCY_FAIL
const WIN32_ERROR_SERVICE_REQUEST_TIMEOUT
const WIN32_ERROR_SERVICE_NO_THREAD
const WIN32_ERROR_PATH_NOT_FOUND
const WIN32_ERROR_INSUFFICIENT_BUFFER
const WIN32_SERVICE_STOP_PENDING
setStartType($startType)
const WIN32_ERROR_SERVICE_DOES_NOT_EXIST
const WIN32_ERROR_SERVICE_DISABLED
const WIN32_ERROR_SHUTDOWN_IN_PROGRESS
setDisplayName($displayName)
const WIN32_ERROR_INVALID_SERVICE_ACCOUNT
const WIN32_ERROR_DEPENDENT_SERVICES_RUNNING
const WIN32_ERROR_SERVICE_EXISTS
const WIN32_SERVICE_PAUSE_PENDING
const WIN32_ERROR_SERVICE_LOGON_FAILED