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 );
541 $start =
$result !==
null ? dechex( $resultInt ) :
'0';
544 if ( $start != self::WIN32_NO_ERROR && $start != self::WIN32_ERROR_SERVICE_ALREADY_RUNNING ) {
551 if ( !$cmdOutput[
'syntaxOk'] ) {
554 '[' . date(
'Y-m-d H:i:s', time() ) .
'] [error] ' . $cmdOutput[
'content'] . PHP_EOL,
561 if ( !$cmdOutput[
'syntaxOk'] ) {
564 '[' . date(
'Y-m-d H:i:s', time() ) .
'] [error] ' . $cmdOutput[
'content'] . PHP_EOL,
571 if ( !$cmdOutput[
'syntaxOk'] ) {
574 '[' . date(
'Y-m-d H:i:s', time() ) .
'] [error] ' . $cmdOutput[
'content'] . PHP_EOL,
583 $this->latestError = self::WIN32_NO_ERROR;
585 $this->latestError =
null;
607 $stop =
$result !==
null ? dechex( $resultInt ) :
'0';
611 $currentStatus = $this->
status();
614 $this->
writeLog(
'Stop service ' . $this->
getName() .
': ' . $stop .
' (status: ' . $currentStatus .
')' );
616 if ( $stop != self::WIN32_NO_ERROR ) {
620 $this->latestError = self::WIN32_NO_ERROR;
635 if ( $this->
stop() ) {
636 return $this->
start();
653 $startTime = microtime(
true);
659 Util::logTrace(
"NSSM info retrieval completed in " . round(microtime(
true) - $startTime, 2) .
" seconds");
666 $originalTimeout = ini_get(
'max_execution_time');
673 set_time_limit($originalTimeout);
676 if (microtime(
true) - $startTime > $timeout) {
677 Util::logTrace(
"Timeout exceeded in infos() method, returning false");
681 Util::logTrace(
"VBS info retrieval completed in " . round(microtime(
true) - $startTime, 2) .
" seconds");
683 }
catch (\Exception $e) {
684 Util::logTrace(
"Exception in infos() method: " . $e->getMessage() .
", returning false");
686 }
catch (\Throwable $e) {
687 Util::logTrace(
"Throwable in infos() method: " . $e->getMessage() .
", returning false");
703 $startTime = microtime(
true);
707 $status = $this->
status();
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)');
716 $isInstalled = $status != self::WIN32_SERVICE_NA;
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 .
')');
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() .
')');
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() .
')');
742 $status = $this->
status();
743 $isRunning = $status == self::WIN32_SERVICE_RUNNING;
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 .
')' );
760 $status = $this->
status();
761 $isStopped = $status == self::WIN32_SERVICE_STOPPED;
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 .
')' );
778 $status = $this->
status();
779 $isPaused = $status == self::WIN32_SERVICE_PAUSED;
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 .
')' );
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;
799 Util::logTrace(
"Checking if status is pending: " . $status .
" - Result: " . ($isPending ?
"YES" :
"NO"));
802 if ($status == self::WIN32_SERVICE_START_PENDING) {
804 }
else if ($status == self::WIN32_SERVICE_STOP_PENDING) {
806 }
else if ($status == self::WIN32_SERVICE_CONTINUE_PENDING) {
808 }
else if ($status == self::WIN32_SERVICE_PAUSE_PENDING) {
826 case self::WIN32_SERVICE_CONTINUE_PENDING:
827 return 'The service continue is pending.';
829 case self::WIN32_SERVICE_PAUSE_PENDING:
830 return 'The service pause is pending.';
832 case self::WIN32_SERVICE_PAUSED:
833 return 'The service is paused.';
835 case self::WIN32_SERVICE_RUNNING:
836 return 'The service is running.';
838 case self::WIN32_SERVICE_START_PENDING:
839 return 'The service is starting.';
841 case self::WIN32_SERVICE_STOP_PENDING:
842 return 'The service is stopping.';
844 case self::WIN32_SERVICE_STOPPED:
845 return 'The service is not running.';
847 case self::WIN32_SERVICE_NA:
848 return 'Cannot retrieve service status.';
865 case self::WIN32_ERROR_ACCESS_DENIED:
866 return 'The handle to the SCM database does not have the appropriate access rights.';
1015 $this->nssm =
$nssm;
1047 if ( $this->latestError != self::WIN32_NO_ERROR ) {
1049 $errorInt = is_numeric($this->latestError) ? hexdec( $this->latestError ) : 0;
1051 $this->latestError .
' (' . $errorInt .
' : ' . $this->
getWin32ErrorCodeDesc( $this->latestError ) .
')';
1053 elseif ( $this->latestStatus != self::WIN32_SERVICE_NA ) {
1055 $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