Created
May 6, 2020 17:57
-
-
Save johnstevenson/abb3c209a2ddf930ef1d4da33d2dc8b9 to your computer and use it in GitHub Desktop.
Demo showing how composer self-update can elevate to
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* This demo shows a method for elevating to an Admin on Windows and running | |
* a command in cmd.exe, from a PHP script. It demonstrates how Composer can | |
* self-update to an Admin location. | |
* | |
* Powershell (available on Windows 7 upwards) is used to invoke the UAC prompt, | |
* because it returns a value from the result of the prompt so we can see if the | |
* user cancelled. Note that it is impossible to obtain the exit code from the | |
* elevated process. | |
* | |
* Another way of doing this is to run a batch script that calls VBS ShellExecute, | |
* but this function does not return an exit code. Also, vbs scripts might not be | |
* runnable if the user's PathExt does not contain a .VBS entry. | |
* | |
* The actual command could have been run in Powershell itself, but this asks | |
* the user to authorize Powershell "to make changes to your device" which I | |
* felt might be confusing for people running unixy shells, whereas Windows | |
* Command Processor looks more native. | |
* | |
* The Powershell script is saved in the user's temp directory and is deleted | |
* (by itself) when it finishes. * | |
* | |
*/ | |
$localFilename = ''; // the location where composer.phar is to be installed | |
$newFilename = ''; // the location of the downloaded (or rollback) composer.phar | |
$interactive = true; // assume we are interactive | |
echo "Upgrading to version '3.0.0' (stable channel).", PHP_EOL; | |
// Assume rename($newFilename, $localFilename) fails | |
if (!$interactive) { | |
return 1; | |
} | |
$cygwin = preg_match('/cygwin/i', php_uname()); | |
if (!$cygwin && !defined('PHP_WINDOWS_VERSION_BUILD')) { | |
return 1; | |
} | |
// See if we are an admin | |
exec('cmd.exe /c fltmc.exe filters', $output, $exitCode); | |
if ($exitCode === 0) { | |
return 1; | |
} | |
echo 'Unable to write to the "C:\\ProgramData\\ComposerSetup\\bin" directory. Access is denied.', PHP_EOL; | |
$question = ' Complete this operation with Administrator priviledges [yes]? '; | |
$answer = readline($question); | |
$cancelledMsg = 'Operation cancelled. Please re-run the self-update command as an Administrator.'.PHP_EOL; | |
if (!preg_match('/^y/i', $answer)) { | |
echo $cancelledMsg; | |
return; | |
} | |
$tmpFile = @tempnam(sys_get_temp_dir(), ''); | |
$script = $tmpFile.'.ps1'; | |
rename($tmpFile, $script); | |
// Format the file names for cmd.exe | |
if ($cygwin) { | |
$localFilename = exec(sprintf("cygpath -w '%s'", $localFilename)); | |
$newFilename = exec(sprintf("cygpath -w '%s'", $newFilename)); | |
} else { | |
// cmd.exe move seems to be fussy about backslashes | |
$localFilename = str_replace('/', '\\', $localFilename); | |
$newFilename = str_replace('/', '\\', $newFilename); | |
} | |
$ps1 = <<<EOT | |
\$exitCode = 0 | |
try { | |
# Use cmd.exe to move new filename to local filename | |
#Start-Process -FilePath "\$env:comspec" -ArgumentList "/c move /y `"$newFilename`" `"$localFilename`"" -Verb RunAs -WindowStyle Hidden | |
# Demo only: open cmd.exe and keep open for 3 seconds | |
Start-Process -FilePath "\$env:comspec" -ArgumentList "/c timeout /t 3" -Verb RunAs -WindowStyle Hidden | |
} catch { | |
\$exitCode = 1 | |
} | |
Remove-Item -Path \$MyInvocation.MyCommand.Path -Force | |
exit \$exitCode | |
EOT; | |
file_put_contents($script, $ps1); | |
if ($cygwin) { | |
$winPath = exec(sprintf("cygpath -w '%s'", $tmpFile)); | |
$script = $winPath.'.ps1'; | |
} | |
$command = sprintf('powershell.exe -File "%s" -WindowStyle Hidden -ExecutionPolicy Bypass', $script); | |
exec($command, $output, $exitCode); | |
if ($exitCode === 0) { | |
echo 'The result of this operation is unobtainable. Run `composer --version` to check if it succeeded.', PHP_EOL; | |
usleep(500); | |
return 0; | |
} else { | |
echo $cancelledMsg; | |
return 1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment