Skip to content

Instantly share code, notes, and snippets.

@johnstevenson
Created May 6, 2020 17:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnstevenson/abb3c209a2ddf930ef1d4da33d2dc8b9 to your computer and use it in GitHub Desktop.
Save johnstevenson/abb3c209a2ddf930ef1d4da33d2dc8b9 to your computer and use it in GitHub Desktop.
Demo showing how composer self-update can elevate to
<?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