Bearsampp 2026.9.23
Loading...
Searching...
No Matches
class.batch.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
30class Batch
31{
32 const END_PROCESS_STR = 'FINISHED!';
33 const CATCH_OUTPUT_FALSE = 'bearsamppCatchOutputFalse';
34
38 public function __construct()
39 {
40 }
41
47 private static function writeLog($log)
48 {
49 global $bearsamppRoot;
51 }
52
60 public static function findExeByPid($pid)
61 {
62 // Sanitize PID to prevent command injection
63 $sanitizedPid = UtilInput::sanitizePID($pid);
64 if ($sanitizedPid === false) {
65 self::writeLog('Invalid PID provided to findExeByPid: ' . var_export($pid, true));
66
67 return false;
68 }
69
70 $result = self::exec('findExeByPid', 'TASKLIST /FO CSV /NH /FI "PID eq ' . $sanitizedPid . '"', 5);
71 if ($result !== false) {
72 $expResult = explode('","', $result[0]);
73 if (is_array($expResult) && count($expResult) > 2 && isset($expResult[0]) && !empty($expResult[0])) {
74 return substr($expResult[0], 1);
75 }
76 }
77
78 return false;
79 }
80
88 public static function getProcessUsingPort($port)
89 {
90 // Sanitize port to prevent command injection
91 $sanitizedPort = UtilInput::sanitizePort($port);
92 if ($sanitizedPort === false) {
93 self::writeLog('Invalid port provided to getProcessUsingPort: ' . var_export($port, true));
94
95 return null;
96 }
97
98 $result = self::exec('getProcessUsingPort', 'NETSTAT -aon', 4);
99 if ($result !== false) {
100 foreach ($result as $row) {
101 if (!UtilString::startWith($row, 'TCP')) {
102 continue;
103 }
104 $rowExp = explode(' ', preg_replace('/\s+/', ' ', $row));
105 if (count($rowExp) == 5 && UtilString::endWith($rowExp[1], ':' . $sanitizedPort) && $rowExp[3] == 'LISTENING') {
106 $pid = intval($rowExp[4]);
107 $exe = self::findExeByPid($pid);
108 if ($exe !== false) {
109 return $exe . ' (' . $pid . ')';
110 }
111
112 return $pid;
113 }
114 }
115 }
116
117 return null;
118 }
119
125 public static function exitApp($restart = false)
126 {
128
129 $content = 'PING 1.1.1.1 -n 1 -w 2000 > nul' . PHP_EOL;
130 $content .= '"' . Path::getExeFilePath() . '" -quit -id={bearsampp}' . PHP_EOL;
131 if ($restart) {
132 $basename = 'restartApp';
133 Log::info('Restart App');
134 $content .= '"' . Path::getPhpExe() . '" "' . Core::isRoot_FILE . '" "' . Action::RESTART . '"' . PHP_EOL;
135 } else {
136 $basename = 'exitApp';
137 Log::info('Exit App');
138 }
139
141 self::execStandalone($basename, $content);
142 }
143
147 public static function restartApp()
148 {
149 self::exitApp(true);
150 }
151
157 public static function getPearVersion()
158 {
159 global $bearsamppBins;
160
161 $result = self::exec('getPearVersion', 'CMD /C "' . $bearsamppBins->getPhp()->getPearExe() . '" -V', 5);
162 if (is_array($result)) {
163 foreach ($result as $row) {
164 if (UtilString::startWith($row, 'PEAR Version:')) {
165 $expResult = explode(' ', $row);
166 if (count($expResult) == 3) {
167 return trim($expResult[2]);
168 }
169 }
170 }
171 }
172
173 return null;
174 }
175
179 public static function refreshEnvVars()
180 {
183 }
184
190 public static function initializeMysql($path)
191 {
192 if (!file_exists($path . '/init.bat')) {
193 Log::warning($path . '/init.bat does not exist');
194
195 return;
196 }
197 self::exec('initializeMysql', 'CMD /C "' . $path . '/init.bat"', 60);
198 }
199
205 public static function installPostgresqlService()
206 {
207 global $bearsamppBins;
208
209 $cmd = '"' . Path::formatWindowsPath($bearsamppBins->getPostgresql()->getCtlExe()) . '" register -N "' . BinPostgresql::SERVICE_NAME . '"';
210 $cmd .= ' -U "LocalSystem" -D "' . Path::formatWindowsPath(Path::getModuleSymlinkPath($bearsamppBins->getPostgresql())) . '\\data"';
211 $cmd .= ' -l "' . Path::formatWindowsPath($bearsamppBins->getPostgresql()->getErrorLog()) . '" -w';
212 self::exec('installPostgresqlService', $cmd, true, false);
213
214 if (!$bearsamppBins->getPostgresql()->getService()->isInstalled()) {
215 return false;
216 }
217
218 self::setServiceDisplayName(BinPostgresql::SERVICE_NAME, $bearsamppBins->getPostgresql()->getService()->getDisplayName());
219 self::setServiceDescription(BinPostgresql::SERVICE_NAME, $bearsamppBins->getPostgresql()->getService()->getDisplayName());
221
222 return true;
223 }
224
230 public static function uninstallPostgresqlService()
231 {
232 global $bearsamppBins;
233
234 $cmd = '"' . Path::formatWindowsPath($bearsamppBins->getPostgresql()->getCtlExe()) . '" unregister -N "' . BinPostgresql::SERVICE_NAME . '"';
235 $cmd .= ' -l "' . Path::formatWindowsPath($bearsamppBins->getPostgresql()->getErrorLog()) . '" -w';
236 self::exec('uninstallPostgresqlService', $cmd, true, false);
237
238 return !$bearsamppBins->getPostgresql()->getService()->isInstalled();
239 }
240
246 public static function initializePostgresql($path)
247 {
248 if (!file_exists($path . '/init.bat')) {
249 Log::warning($path . '/init.bat does not exist');
250
251 return;
252 }
253 self::exec('initializePostgresql', 'CMD /C "' . $path . '/init.bat"', 15);
254 }
255
261 public static function initializeMariadb($path)
262 {
263 if (!file_exists($path . '/init.bat')) {
264 Log::warning($path . '/init.bat does not exist');
265
266 return;
267 }
268 self::exec('initializeMariadb', 'CMD /C "' . $path . '/init.bat"', 60);
269 }
270
277 public static function createSymlink($src, $dest)
278 {
279 global $bearsamppCore;
280 $src = Path::formatWindowsPath($src);
281 $dest = Path::formatWindowsPath($dest);
282 self::exec('createSymlink', '"' . Path::getLnExe() . '" --absolute --symbolic --traditional --1023safe "' . $src . '" ' . '"' . $dest . '"', true, false);
283 }
284
292 public static function removeSymlink($link)
293 {
294 if (!file_exists($link)) {
295 self::writeLog('-> removeSymlink: Link does not exist: ' . $link);
296
297 return true; // If the link doesn't exist, nothing to do
298 }
299
300 // Check if it's a directory symlink
301 $isDirectory = is_dir($link);
302 $formattedLink = Path::formatWindowsPath($link);
303
304 try {
305 // Use different commands based on whether it's a directory or file symlink
306 if ($isDirectory) {
307 // For directory symlinks
308 self::exec('removeSymlink', 'rmdir /Q "' . $formattedLink . '"', true, false);
309 } else {
310 // For file symlinks
311 self::exec('removeSymlink', 'del /F /Q "' . $formattedLink . '"', true, false);
312 }
313
314 // Check if removal was successful
315 if (file_exists($link)) {
316 self::writeLog('-> removeSymlink: Failed to remove symlink: ' . $link);
317
318 return false;
319 }
320
321 self::writeLog('-> removeSymlink: Successfully removed symlink: ' . $link);
322
323 return true;
324 } catch (Exception $e) {
325 self::writeLog('-> removeSymlink: Exception: ' . $e->getMessage());
326
327 return false;
328 }
329 }
330
336 public static function getOsInfo()
337 {
338 $result = self::exec('getOsInfo', 'ver', 5);
339 if (is_array($result)) {
340 foreach ($result as $row) {
341 if (UtilString::startWith($row, 'Microsoft')) {
342 return trim($row);
343 }
344 }
345 }
346
347 return '';
348 }
349
356 public static function setServiceDisplayName($serviceName, $displayName)
357 {
358 // Sanitize service name to prevent command injection
359 $sanitizedName = UtilInput::sanitizeServiceName($serviceName);
360 if ($sanitizedName === false) {
361 self::writeLog('Invalid service name provided to setServiceDisplayName: ' . $serviceName);
362
363 return;
364 }
365
366 // Remove quotes and dangerous characters from display name
367 $sanitizedDisplayName = str_replace('"', '', $displayName);
368 $sanitizedDisplayName = preg_replace('/[<>|&^]/', '', $sanitizedDisplayName);
369
370 $cmd = 'sc config ' . $sanitizedName . ' DisplayName= "' . $sanitizedDisplayName . '"';
371 self::exec('setServiceDisplayName', $cmd, true, false);
372 }
373
380 public static function setServiceDescription($serviceName, $desc)
381 {
382 // Sanitize service name to prevent command injection
383 $sanitizedName = UtilInput::sanitizeServiceName($serviceName);
384 if ($sanitizedName === false) {
385 self::writeLog('Invalid service name provided to setServiceDescription: ' . $serviceName);
386
387 return;
388 }
389
390 // Remove quotes and dangerous characters from description
391 $sanitizedDesc = str_replace('"', '', $desc);
392 $sanitizedDesc = preg_replace('/[<>|&^]/', '', $sanitizedDesc);
393
394 $cmd = 'sc description ' . $sanitizedName . ' "' . $sanitizedDesc . '"';
395 self::exec('setServiceDescription', $cmd, true, false);
396 }
397
404 public static function setServiceStartType($serviceName, $startType)
405 {
406 // Sanitize service name to prevent command injection
407 $sanitizedName = UtilInput::sanitizeServiceName($serviceName);
408 if ($sanitizedName === false) {
409 self::writeLog('Invalid service name provided to setServiceStartType: ' . $serviceName);
410
411 return;
412 }
413
414 // Validate start type (only allow known values)
415 $allowedStartTypes = ['auto', 'demand', 'disabled', 'delayed-auto'];
416 if (!in_array(strtolower($startType), $allowedStartTypes, true)) {
417 self::writeLog('Invalid start type provided: ' . $startType);
418
419 return;
420 }
421
422 $cmd = 'sc config ' . $sanitizedName . ' start= ' . strtolower($startType);
423 self::exec('setServiceStartType', $cmd, true, false);
424 }
425
435 public static function execStandalone($basename, $content, $silent = true)
436 {
437 return self::exec($basename, $content, false, false, true, $silent);
438 }
439
453 public static function exec($basename, $content, $timeout = true, $catchOutput = true, $standalone = false, $silent = true, $rebuild = true)
454 {
455 global $bearsamppConfig, $bearsamppWinbinder;
456 $result = false;
457
458 $resultFile = self::getTmpFile('.tmp', $basename);
459 $scriptPath = self::getTmpFile('.bat', $basename);
460 $checkFile = self::getTmpFile('.tmp', $basename);
461
462 // Redirect output
463 if ($catchOutput) {
464 $content = '(' . PHP_EOL . $content . PHP_EOL . ') > "' . $resultFile . '"' . (!UtilString::endWith($content, '2') ? ' 2>&1' : '');
465 }
466
467 // Header
468 $header = '@ECHO OFF' . PHP_EOL . PHP_EOL;
469
470 // Footer
471 $footer = PHP_EOL . (!$standalone ? PHP_EOL . 'ECHO ' . self::END_PROCESS_STR . ' > "' . $checkFile . '"' : '');
472
473 // Process
474 file_put_contents($scriptPath, $header . $content . $footer);
475
476 if (is_object($bearsamppWinbinder)) {
477 $bearsamppWinbinder->exec($scriptPath, null, $silent);
478 } else {
479 // Fallback to standard PHP execution if WinBinder is not available (e.g. on web interface)
480 $cmd = 'cmd /c "' . $scriptPath . '"';
481 if ($silent) {
482 // Use shell_exec with output redirection to NUL for silent background execution on Windows
483 @shell_exec('start /B "" ' . $cmd . ' > NUL 2>&1');
484 } else {
485 @shell_exec($cmd);
486 }
487 }
488
489 if (!$standalone) {
490 $timeout = is_numeric($timeout) ? $timeout : ($timeout === true ? $bearsamppConfig->getScriptsTimeout() : false);
491 $maxtime = time() + $timeout;
492 $noTimeout = $timeout === false;
493 while ($result === false || empty($result)) {
494 $finished = false;
495 if (file_exists($checkFile)) {
496 $check = file($checkFile);
497 if (!empty($check) && trim($check[0]) == self::END_PROCESS_STR) {
498 $finished = true;
499 if ($catchOutput && file_exists($resultFile)) {
500 $result = file($resultFile);
501 } else {
502 $result = self::CATCH_OUTPUT_FALSE;
503 }
504 }
505 }
506
507 // The script completed once the FINISHED! marker appears, even if it
508 // produced no output. Without this, empty output (e.g. mysqld
509 // --initialize-insecure) makes us spin until the timeout elapses.
510 if ($finished) {
511 break;
512 }
513
514 if ($maxtime < time() && !$noTimeout) {
515 break;
516 }
517 }
518 }
519
520 self::writeLog('Exec:');
521 self::writeLog('-> basename: ' . $basename);
522 self::writeLog('-> content: ' . str_replace(PHP_EOL, ' \\\\ ', $content));
523 self::writeLog('-> checkFile: ' . $checkFile);
524 self::writeLog('-> resultFile: ' . $resultFile);
525 self::writeLog('-> scriptPath: ' . $scriptPath);
526
527 if ($result !== false && !empty($result) && is_array($result)) {
528 if ($rebuild) {
529 $rebuildResult = array();
530 foreach ($result as $row) {
531 $row = trim($row);
532 if (!empty($row)) {
533 $rebuildResult[] = $row;
534 }
535 }
536 $result = $rebuildResult;
537 }
538 self::writeLog('-> result: ' . substr(implode(' \\\\ ', $result), 0, 2048));
539 } else {
540 self::writeLog('-> result: N/A');
541 }
542
543 return $result;
544 }
545
554 private static function getTmpFile($ext, $customName = null)
555 {
556 global $bearsamppCore;
557
558 return Path::formatWindowsPath(Path::getTmpPath() . '/' . (!empty($customName) ? $customName . '-' : '') . UtilString::random() . $ext);
559 }
560}
$result
global $bearsamppBins
global $bearsamppRoot
$port
global $bearsamppCore
const RESTART
__construct()
static writeLog($log)
static removeSymlink($link)
static installPostgresqlService()
const CATCH_OUTPUT_FALSE
static initializeMariadb($path)
static initializePostgresql($path)
static getProcessUsingPort($port)
static execStandalone($basename, $content, $silent=true)
static setServiceDisplayName($serviceName, $displayName)
static setServiceStartType($serviceName, $startType)
static uninstallPostgresqlService()
static refreshEnvVars()
static initializeMysql($path)
static createSymlink($src, $dest)
static getTmpFile($ext, $customName=null)
static exitApp($restart=false)
static getOsInfo()
static setServiceDescription($serviceName, $desc)
static getPearVersion()
static restartApp()
static exec($basename, $content, $timeout=true, $catchOutput=true, $standalone=false, $silent=true, $rebuild=true)
const END_PROCESS_STR
static findExeByPid($pid)
const isRoot_FILE
static info($data, $file=null)
static debug($data, $file=null)
static warning($data, $file=null)
static getExeFilePath($aetrayPath=false)
static getBatchLogFilePath($aetrayPath=false)
static getRootPath($aetrayPath=false)
static getSetEnvExe($aetrayPath=false)
static formatWindowsPath($path)
static getPhpExe($aetrayPath=false)
static getModuleSymlinkPath($module)
static getTmpPath($aetrayPath=false)
static getLnExe($aetrayPath=false)
const APP_PATH_REG_ENTRY
static sanitizeServiceName($serviceName)
static sanitizePID($pid)
static sanitizePort($port)
static startWith($string, $search)
static random($length=32, $withNumeric=true)
static endWith($string, $search)
static killBins($refreshProcs=false)
global $bearsamppConfig
Definition homepage.php:46