I'm creating an admin page for my module that will allow an admin to trigger scripts from within the module configurations.

So far, I've created these routes:

  path: "/admin/config/development/my_module/scripts"
    _title: "Scripts"
    _controller: '\Drupal\my_module\Controller\ScriptsController::home'
    _permission: "administer site configuration"
  path: "/admin/config/development/my_module/scripts/run/{script}"
    _title: "Run Script"
    name: "none"
    _permission: "administer site configuration"

And then my Scripts Controller looks like so:


namespace Drupal\my_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Yaml\Yaml;
use Drupal\Core\Url;

 * Defines Configurations Controller class.
 * This class controller i
class ScriptsController extends ControllerBase {

   * Home
  function home() {
    $build['table'] = [
      '#type' => 'table',
      '#header' => [

    // Retrieve all scripts
    $scripts_config_file = file_get_contents(drupal_get_path('module', 'my_module') . '/my_module.scripts.yml');
    $script_config = Yaml::parse($scripts_config_file);

    // Create Table Rows and add to table
    $rows = [];
    foreach($script_config as $key => $script) {
      $rows[] = [
        'title' => $script['title'],
        'description' => $script['description'],
        'path' => $script['path'],
        'operations' => [
          'data' => [
            '#type' => 'link',
            '#title' => 'Run',
            '#url' => Url::fromRoute('my_module.scripts.run', ['script' => $key])
    $build['table']['#rows'] = $rows;

    return [
      '#type' => '#markup',
      '#markup' => render($build),

   * Run Script
  function runScript($script) {
    // Retrieve the script
    $module_path = drupal_get_path('module', 'my_module');
    $script_config = Yaml::parse(file_get_contents("$module_path/my_module.scripts.yml"));
    $script_data = $script_config[$script];

    $script_path = $module_path . '/' . $script_data['path'];

    $execute = exec("drush scr $script_path > /dev/null &");

    \Drupal::logger('ytp')->notice('<pre><code>' . print_r($execute, TRUE) . '</code></pre>');

    return new RedirectResponse(Url::fromRoute('ytp_integrations.scripts.home')->toString());

Structurally, this seems to all work, but when I print out the execute variable after running the drush command via exec, I get nothing and the code in my script isn't executed. I changed the command to pwd and it printed out the path like expected, but exec doesn't seem to know what drush is...

I would use drush_invoke_process, but I would like my drush command to be executed in the background. I'm expecting to get a pid back so that I am able to determine which scripts should have their run button disabled if the pid is running in the background.

Any reason why exec doesn't know what drush is or how I could kick off my drush script in the background running via Drush?

  • PHP won't exec in your shell so it probably just doesn't have drush in the PATH - try vendor/bin/drush instead – Clive Feb 22 at 23:38
  • Just tried that and received another blank response. If I remove the > /dev/null & and run any drush command like $execute = exec("vendor/bin/drush status");, I'm now getting the error ` thrown in /Users/meproject_root/vendor/webmozart/path-util/src/Path.php on line 257`. Any idea what that is? – BlondeSwan Feb 22 at 23:53
  • I'm guessing exec needs the full path to drush. – Kevin Feb 23 at 3:49
  • @Kevin I thought that as well, but received the same error I described in the previous comment... – BlondeSwan Feb 23 at 15:59
  • There's a lot to wade through here, bottom line is this isn't a good approach because your web server user probably doesn't have the right environment to run Drush properly. I can suggest an alternative: instead of running exec, write a file in a shared location on the disk containing the name of 1 or more scripts. Add a cron job to check the file in the shared location and run the appropriate drush scripts. This will run as your normal user, making drush more likely to work, and is still non-blocking. Plus it gives you an opportunity to implement a rudimentary cancel feature if you need it – Clive Feb 23 at 20:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.