122 self::VBS_DISPLAY_NAME,
123 self::VBS_DESCRIPTION,
141 if ( function_exists( $function ) ) {
142 if (!isset(self::$loggedFunctions[$function])) {
144 self::$loggedFunctions[$function] =
true;
148 if ($function ===
'win32_query_service_status') {
149 Util::logTrace(
"Using enhanced handling for win32_query_service_status");
152 $originalTimeout = ini_get(
'max_execution_time');
157 $result = call_user_func($function, $param);
160 set_time_limit($originalTimeout);
162 if ($checkError &&
$result !==
null) {
165 if (dechex($resultInt) != self::WIN32_NO_ERROR) {
166 $this->latestError = dechex($resultInt);
171 set_time_limit($originalTimeout);
173 Util::logTrace(
"Win32ServiceException caught: " . $e->getMessage());
176 if (strpos($e->getMessage(),
'service does not exist') !==
false) {
177 Util::logTrace(
"Service does not exist exception handled for: " . $param);
179 $result = hexdec(self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST);
182 Util::logTrace(
"Unhandled Win32ServiceException: " . $e->getMessage());
185 }
catch (\Exception $e) {
187 set_time_limit($originalTimeout);
190 Util::logTrace(
"Exception caught in callWin32Service: " . $e->getMessage());
192 }
catch (\Throwable $e) {
194 set_time_limit($originalTimeout);
197 Util::logTrace(
"Throwable caught in callWin32Service: " . $e->getMessage());
204 $result = call_user_func($function, $param);
205 if ($checkError &&
$result !==
null) {
208 if (dechex($resultInt) != self::WIN32_NO_ERROR) {
209 $this->latestError = dechex($resultInt);
213 Util::logTrace(
"Win32ServiceException caught: " . $e->getMessage());
216 if (strpos($e->getMessage(),
'service does not exist') !==
false) {
217 Util::logTrace(
"Service does not exist exception handled for: " . $param);
219 $result = hexdec(self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST);
222 Util::logTrace(
"Unhandled Win32ServiceException: " . $e->getMessage());
225 }
catch (\Exception $e) {
227 Util::logTrace(
"Exception caught in callWin32Service: " . $e->getMessage());
229 }
catch (\Throwable $e) {
231 Util::logTrace(
"Throwable caught in callWin32Service: " . $e->getMessage());
236 if (!isset(self::$loggedFunctions[$function])) {
238 self::$loggedFunctions[$function] =
true;
251 public function status($timeout =
true): string
253 usleep( self::SLEEP_TIME );
255 $this->latestStatus = self::WIN32_SERVICE_NA;
256 $maxtime = time() + self::PENDING_TIMEOUT;
258 Util::logTrace(
"Querying status for service: " . $this->
getName() .
" (timeout: " . ($timeout ?
"enabled" :
"disabled") .
")");
260 Util::logTrace(
"Max timeout time set to: " . date(
'Y-m-d H:i:s', $maxtime));
266 $startTime = microtime(
true);
269 while ( ($this->latestStatus == self::WIN32_SERVICE_NA || $this->
isPending( $this->latestStatus )) && $loopCount < $maxLoops ) {
271 Util::logTrace(
"Calling win32_query_service_status for service: " . $this->
getName() .
" (attempt " . $loopCount .
" of " . $maxLoops .
")");
274 if (microtime(
true) - $startTime > 10) {
275 Util::logTrace(
"Overall timeout reached before making service status call");
281 if ( is_array( $this->latestStatus ) && isset( $this->latestStatus[
'CurrentState'] ) ) {
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);
287 elseif ( $this->latestStatus !==
null ) {
289 $statusInt = is_numeric($this->latestStatus) ? (int)$this->latestStatus : 0;
290 $statusHex = dechex( $statusInt );
291 Util::logTrace(
"Service status returned as value: " . $statusHex);
293 if ( $statusHex == self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST ) {
294 $this->latestStatus = $statusHex;
301 if ($loopCount >= 2) {
302 Util::logTrace(
"Multiple null results, assuming service does not exist");
303 $this->latestStatus = self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST;
308 if ( $timeout && $maxtime < time() ) {
314 if ($loopCount < $maxLoops && ($this->latestStatus == self::WIN32_SERVICE_NA || $this->
isPending($this->latestStatus))) {
316 usleep(self::SLEEP_TIME);
319 }
catch (\Exception $e) {
320 Util::logTrace(
"Exception in status method: " . $e->getMessage());
322 $this->latestStatus = self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST;
323 }
catch (\Throwable $e) {
324 Util::logTrace(
"Throwable in status method: " . $e->getMessage());
326 $this->latestStatus = self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST;
329 if ($loopCount >= $maxLoops) {
333 $elapsedTime = microtime(
true) - $startTime;
334 Util::logTrace(
"Status check completed in " . round($elapsedTime, 2) .
" seconds after " . $loopCount .
" attempts");
336 if ( $this->latestStatus == self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST ) {
338 $this->latestStatus = self::WIN32_SERVICE_NA;
358 Util::logTrace(
"PostgreSQL service detected - using specialized installation");
377 Util::logTrace(
"NSSM environment path (with additional paths): " . $nssmEnvPath);
379 $nssmEnvPath .=
'%SystemRoot%/system32;';
380 $nssmEnvPath .=
'%SystemRoot%;';
381 $nssmEnvPath .=
'%SystemRoot%/system32/Wbem;';
382 $nssmEnvPath .=
'%SystemRoot%/system32/WindowsPowerShell/v1.0';
385 $this->
getNssm()->setEnvironmentExtra(
'PATH=' . $nssmEnvPath );
403 Util::logTrace(
"Using win32_create_service for service installation");
404 $serviceParams = array(
415 foreach ($serviceParams as $key => $value) {
422 $create =
$result !==
null ? dechex( $resultInt ) :
'0';
425 $this->
writeLog(
'Create service: ' . $create .
' (status: ' . $this->
status() .
')' );
434 if ( $create != self::WIN32_NO_ERROR ) {
435 Util::logTrace(
"Service creation failed with error code: " . $create);
440 $this->latestError = self::WIN32_NO_ERROR;
453 public function delete(): bool
467 Util::logTrace(
"PostgreSQL service detected - using specialized uninstallation");
477 $delete =
$result !==
null ? dechex( $resultInt ) :
'0';
479 $this->
writeLog(
'Delete service ' . $this->
getName() .
': ' . $delete .
' (status: ' . $this->
status() .
')' );
481 if ( $delete != self::WIN32_NO_ERROR && $delete != self::WIN32_ERROR_SERVICE_DOES_NOT_EXIST ) {
485 $this->latestError = self::WIN32_NO_ERROR;
500 if ( $this->
delete() ) {
501 usleep( self::SLEEP_TIME );
544 $start =
$result !==
null ? dechex( $resultInt ) :
'0';
547 if ( $start != self::WIN32_NO_ERROR && $start != self::WIN32_ERROR_SERVICE_ALREADY_RUNNING ) {
554 if ( !$cmdOutput[
'syntaxOk'] ) {
557 '[' . date(
'Y-m-d H:i:s', time() ) .
'] [error] ' . $cmdOutput[
'content'] . PHP_EOL,
564 if ( !$cmdOutput[
'syntaxOk'] ) {
567 '[' . date(
'Y-m-d H:i:s', time() ) .
'] [error] ' . $cmdOutput[
'content'] . PHP_EOL,
574 if ( !$cmdOutput[
'syntaxOk'] ) {
577 '[' . date(
'Y-m-d H:i:s', time() ) .
'] [error] ' . $cmdOutput[
'content'] . PHP_EOL,
586 $this->latestError = self::WIN32_NO_ERROR;
588 $this->latestError =
null;
610 $stop =
$result !==
null ? dechex( $resultInt ) :
'0';
614 $currentStatus = $this->
status();
617 $this->
writeLog(
'Stop service ' . $this->
getName() .
': ' . $stop .
' (status: ' . $currentStatus .
')' );
619 if ( $stop != self::WIN32_NO_ERROR ) {
623 $this->latestError = self::WIN32_NO_ERROR;
638 if ( $this->
stop() ) {
639 return $this->
start();
668 $options = [
'bypass_shell' =>
true];
670 $process = @proc_open($command, $descriptorspec, $pipes,
null,
null, $options);
672 if (!is_resource($process)) {
680 $output = stream_get_contents($pipes[1]);
684 $errors = stream_get_contents($pipes[2]);
688 proc_close($process);
691 if (!empty($errors)) {
692 $output .=
"\n" . $errors;
702 $startTime = microtime(
true);
706 $command =
'sc query "' . $this->
getName() .
'"';
710 $duration = round(microtime(
true) - $startTime, 3);
714 if ($output ===
null || $output ===
false) {
715 Util::logTrace(
"sc.exe returned null/false, service likely doesn't exist");
720 if (stripos($output,
'does not exist') !==
false ||
721 stripos($output,
'FAILED') !==
false ||
722 stripos($output,
'1060') !==
false) {
731 if (preg_match(
'/SERVICE_NAME:\s*(.+)/i', $output, $matches)) {
732 $serviceInfo[self::VBS_NAME] = trim($matches[1]);
736 if (preg_match(
'/DISPLAY_NAME:\s*(.+)/i', $output, $matches)) {
737 $serviceInfo[self::VBS_DISPLAY_NAME] = trim($matches[1]);
741 if (preg_match(
'/STATE\s*:\s*\d+\s+(\w+)/i', $output, $matches)) {
742 $state = trim($matches[1]);
743 $serviceInfo[self::VBS_STATE] = $state;
748 if (!empty($serviceInfo)) {
752 $configCommand =
'sc qc "' . $this->
getName() .
'"';
753 $configOutput = $this->
execHidden($configCommand);
755 if ($configOutput !==
null && $configOutput !==
false && preg_match(
'/BINARY_PATH_NAME\s*:\s*(.+)/i', $configOutput, $matches)) {
756 $serviceInfo[self::VBS_PATH_NAME] = trim($matches[1]);
757 Util::logTrace(
"Service path: " . $serviceInfo[self::VBS_PATH_NAME]);
761 if ($configOutput !==
null && $configOutput !==
false && preg_match(
'/DISPLAY_NAME\s*:\s*(.+)/i', $configOutput, $matches)) {
762 $serviceInfo[self::VBS_DESCRIPTION] = trim($matches[1]);
769 Util::logTrace(
"Could not parse service info from sc.exe output");
785 $startTime = microtime(
true);
791 Util::logTrace(
"NSSM info retrieval completed in " . round(microtime(
true) - $startTime, 2) .
" seconds");
799 if ($fastResult !==
false) {
800 $duration = round(microtime(
true) - $startTime, 3);
801 Util::logTrace(
"Fast service check succeeded in " . $duration .
"s (saved 5-10s)");
802 Util::logDebug(
"Performance: Fast service check used for " . $this->
getName() .
", saved 5-10 seconds");
807 if ($fastResult ===
false) {
808 $duration = round(microtime(
true) - $startTime, 3);
809 Util::logTrace(
"Fast service check determined service doesn't exist in " . $duration .
"s");
817 $originalTimeout = ini_get(
'max_execution_time');
824 set_time_limit($originalTimeout);
827 if (microtime(
true) - $startTime > $timeout) {
828 Util::logTrace(
"Timeout exceeded in infos() method, returning false");
832 Util::logTrace(
"VBS info retrieval completed in " . round(microtime(
true) - $startTime, 2) .
" seconds");
834 }
catch (\Exception $e) {
835 Util::logTrace(
"Exception in infos() method: " . $e->getMessage() .
", returning false");
837 }
catch (\Throwable $e) {
838 Util::logTrace(
"Throwable in infos() method: " . $e->getMessage() .
", returning false");
854 $startTime = microtime(
true);
858 $status = $this->
status();
861 if (microtime(
true) - $startTime > $timeout) {
862 Util::logTrace(
"Timeout exceeded in isInstalled() method, assuming service is not installed");
863 $this->
writeLog(
'isInstalled ' . $this->
getName() .
': NO (timeout exceeded)');
867 $isInstalled = $status != self::WIN32_SERVICE_NA;
869 Util::logTrace(
"Service " . $this->
getName() .
" installation status: " . ($isInstalled ?
"YES" :
"NO") .
" (status code: " . $status .
")");
870 $this->
writeLog(
'isInstalled ' . $this->
getName() .
': ' . ($isInstalled ?
'YES' :
'NO') .
' (status: ' . $status .
')');
873 }
catch (\Exception $e) {
874 Util::logTrace(
"Exception in isInstalled() method: " . $e->getMessage() .
", assuming service is not installed");
875 $this->
writeLog(
'isInstalled ' . $this->
getName() .
': NO (exception: ' . $e->getMessage() .
')');
877 }
catch (\Throwable $e) {
878 Util::logTrace(
"Throwable in isInstalled() method: " . $e->getMessage() .
", assuming service is not installed");
879 $this->
writeLog(
'isInstalled ' . $this->
getName() .
': NO (throwable: ' . $e->getMessage() .
')');
893 $status = $this->
status();
894 $isRunning = $status == self::WIN32_SERVICE_RUNNING;
896 Util::logTrace(
"Service " . $this->
getName() .
" running status: " . ($isRunning ?
"YES" :
"NO") .
" (status code: " . $status .
")");
897 $this->
writeLog(
'isRunning ' . $this->
getName() .
': ' . ($isRunning ?
'YES' :
'NO') .
' (status: ' . $status .
')' );
911 $status = $this->
status();
912 $isStopped = $status == self::WIN32_SERVICE_STOPPED;
914 Util::logTrace(
"Service " . $this->
getName() .
" stopped status: " . ($isStopped ?
"YES" :
"NO") .
" (status code: " . $status .
")");
915 $this->
writeLog(
'isStopped ' . $this->
getName() .
': ' . ($isStopped ?
'YES' :
'NO') .
' (status: ' . $status .
')' );
929 $status = $this->
status();
930 $isPaused = $status == self::WIN32_SERVICE_PAUSED;
932 Util::logTrace(
"Service " . $this->
getName() .
" paused status: " . ($isPaused ?
"YES" :
"NO") .
" (status code: " . $status .
")");
933 $this->
writeLog(
'isPaused ' . $this->
getName() .
': ' . ($isPaused ?
'YES' :
'NO') .
' (status: ' . $status .
')' );
947 $isPending = $status == self::WIN32_SERVICE_START_PENDING || $status == self::WIN32_SERVICE_STOP_PENDING
948 || $status == self::WIN32_SERVICE_CONTINUE_PENDING || $status == self::WIN32_SERVICE_PAUSE_PENDING;
950 Util::logTrace(
"Checking if status is pending: " . $status .
" - Result: " . ($isPending ?
"YES" :
"NO"));
953 if ($status == self::WIN32_SERVICE_START_PENDING) {
955 }
else if ($status == self::WIN32_SERVICE_STOP_PENDING) {
957 }
else if ($status == self::WIN32_SERVICE_CONTINUE_PENDING) {
959 }
else if ($status == self::WIN32_SERVICE_PAUSE_PENDING) {
977 case self::WIN32_SERVICE_CONTINUE_PENDING:
978 return 'The service continue is pending.';
980 case self::WIN32_SERVICE_PAUSE_PENDING:
981 return 'The service pause is pending.';
983 case self::WIN32_SERVICE_PAUSED:
984 return 'The service is paused.';
986 case self::WIN32_SERVICE_RUNNING:
987 return 'The service is running.';
989 case self::WIN32_SERVICE_START_PENDING:
990 return 'The service is starting.';
992 case self::WIN32_SERVICE_STOP_PENDING:
993 return 'The service is stopping.';
995 case self::WIN32_SERVICE_STOPPED:
996 return 'The service is not running.';
998 case self::WIN32_SERVICE_NA:
999 return 'Cannot retrieve service status.';
1016 case self::WIN32_ERROR_ACCESS_DENIED:
1017 return 'The handle to the SCM database does not have the appropriate access rights.';
1041 $this->name =
$name;
1166 $this->nssm =
$nssm;
1198 if ( $this->latestError != self::WIN32_NO_ERROR ) {
1200 $errorInt = is_numeric($this->latestError) ? hexdec( $this->latestError ) : 0;
1202 $this->latestError .
' (' . $errorInt .
' : ' . $this->
getWin32ErrorCodeDesc( $this->latestError ) .
')';
1204 elseif ( $this->latestStatus != self::WIN32_SERVICE_NA ) {
1206 $statusInt = is_numeric($this->latestStatus) ? hexdec( $this->latestStatus ) : 0;
static installPostgresqlService()
static uninstallPostgresqlService()
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 formatWindowsPath($path)
static getServiceInfos($serviceName)
setErrorControl($errorControl)
const WIN32_SERVICE_PAUSED
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_INVALID_NAME
const SERVER_ERROR_NORMAL
const WIN32_ERROR_INVALID_DATA
const SERVER_ERROR_IGNORE
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
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
const WIN32_ERROR_SERVICE_DOES_NOT_EXIST
const WIN32_SERVICE_RUNNING
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_SERVICE_STOPPED
const WIN32_ERROR_SERVICE_EXISTS
const WIN32_SERVICE_PAUSE_PENDING
const WIN32_ERROR_SERVICE_LOGON_FAILED
getWin32ErrorCodeDesc($code)
const SERVICE_DEMAND_START