diff --git a/composer.json b/composer.json
index d296f17aa2bcc9006ce4770b4a782765b399c0a6..e79bd9ebc1a2c5942a3f42e03dfd0628fcdce164 100644
--- a/composer.json
+++ b/composer.json
@@ -12,6 +12,13 @@
   "support": {
     "issues": "https://www.drupal.org/project/issues/unused_modules"
   },
-  "license": "GPL-2.0+",
+  "license": "GPL-2.0-or-later",
+  "extra": {
+    "drush": {
+      "services": {
+        "drush.services.yml": "^9"
+      }
+    }
+  },
   "require": { }
 }
diff --git a/drush.services.yml b/drush.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..821d457f6a0806b6b1c3bafbd749c610e171aa41
--- /dev/null
+++ b/drush.services.yml
@@ -0,0 +1,6 @@
+services:
+  unused_modules.commands:
+    class: \Drupal\unused_modules\Commands\UnusedModulesCommands
+    arguments: ['@unused_modules.helper']
+    tags:
+      - { name: drush.command }
diff --git a/src/Commands/UnusedModulesCommands.php b/src/Commands/UnusedModulesCommands.php
new file mode 100644
index 0000000000000000000000000000000000000000..53bf78c316679ad3489cb68e03a18814f40f9f0b
--- /dev/null
+++ b/src/Commands/UnusedModulesCommands.php
@@ -0,0 +1,187 @@
+<?php
+
+namespace Drupal\unused_modules\Commands;
+
+use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
+use Drush\Commands\DrushCommands;
+use Drupal\unused_modules\UnusedModulesHelperService;
+
+/**
+ * A Drush commandfile.
+ *
+ * In addition to this file, you need a drush.services.yml
+ * in root of your module, and a composer.json file that provides the name
+ * of the services file to use.
+ *
+ * See these files for an example of injecting Drupal services:
+ *   - http://cgit.drupalcode.org/devel/tree/src/Commands/DevelCommands.php
+ *   - http://cgit.drupalcode.org/devel/tree/drush.services.yml
+ */
+class UnusedModulesCommands extends DrushCommands {
+
+  /**
+   * Unused modules helper service.
+   *
+   * @var \Drupal\unused_modules\UnusedModulesHelperService
+   */
+  protected $unusedModulesHelper;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(UnusedModulesHelperService $unusedModulesHelper) {
+    parent::__construct();
+    $this->unusedModulesHelper = $unusedModulesHelper;
+  }
+
+  /**
+   * Show unused modules or projects.
+   *
+   * @param string $type
+   *   Options "projects" and "modules". Show modules or projects.
+   * @param string $show
+   *   Options "all" and "disabled". Show only disabled modules.
+   *
+   * @usage drush unused-modules projects disabled
+   *   Show projects that are unused.
+   * @usage drush um
+   *   As above, shorthand.
+   * @usage drush unused-modules projects disabled
+   *   As above, include projects with enabled modules.
+   * @usage drush unused-modules modules disabled
+   *   Show modules that are unused.
+   * @usage drush unused-modules modules all
+   *   As above, include enabled modules.
+   *
+   * @table-style default
+   * @field-labels
+   *   project: Project
+   *   module: Module
+   *   enabled: Module enabled
+   *   has_modules: Project has Enabled Modules
+   *   path: Project Path
+   * @default-fields project,module,enabled,has_modules,path
+   *
+   * @command unused:modules
+   * @aliases um,unused_modules,unused-modules
+   *
+   * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
+   *   Rows with unused project/module information.
+   */
+  public function modules($type = "projects", $show = "disabled") {
+    // Print projects.
+    if ($type == 'projects') {
+      if ($show == 'all') {
+        return $this->showProjects('all');
+      }
+      elseif ($show == 'disabled') {
+        return $this->showProjects('disabled');
+      }
+      else {
+        throw new \Exception("unknown 'show' argument " . $show . ". See drush unused-modules --help");
+      }
+    }
+    // Print modules.
+    elseif ($type == 'modules') {
+      if ($show == 'all') {
+        return $this->showModules('all');
+      }
+      elseif ($show == 'disabled') {
+        return $this->showModules('disabled');
+      }
+      else {
+        throw new \Exception("unknown 'show' argument " . $show . ". See drush unused-modules --help");
+      }
+    }
+    else {
+      throw new \Exception("unknown 'type' argument " . $type . ". See drush unused-modules --help");
+    }
+  }
+
+  /**
+   * Drush callback.
+   *
+   * Prints a table with orphaned projects.
+   *
+   * @param string $op
+   *   Either 'all' or 'disabled'.
+   *
+   * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
+   *   Rows with unused project information.
+   */
+  private function showProjects($op = 'all') {
+    $modules = $this->unusedModulesHelper->getModulesByProject();
+
+    $rows = [];
+    foreach ($modules as $module) {
+      if ($op == 'all') {
+        $rows[$module->projectName] = [
+          'project' => $module->projectName,
+          'has_modules' => $module->projectHasEnabledModules ? dt("Yes") : dt("No"),
+          'path' => $module->projectPath,
+        ];
+      }
+      elseif ($op == 'disabled') {
+        if (!$module->projectHasEnabledModules) {
+          $rows[$module->projectName] = [
+            'project' => $module->projectName,
+            'has_modules' => $module->projectHasEnabledModules ? dt("Yes") : dt("No"),
+            'path' => $module->projectPath,
+          ];
+        }
+      }
+    }
+
+    if (!count($rows)) {
+      $this->output()->writeln("Hurray, no orphaned projects!");
+      return NULL;
+    }
+    return new RowsOfFields($rows);
+  }
+
+  /**
+   * Drush callback.
+   *
+   * Prints a table with orphaned modules.
+   *
+   * @param string $op
+   *   Either 'all' or 'disabled'.
+   *
+   * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
+   *   Rows with unused module information.
+   */
+  private function showModules($op = 'all') {
+    $modules = $this->unusedModulesHelper->getModulesByProject();
+
+    $rows = [];
+    foreach ($modules as $module) {
+      if ($op == 'all') {
+        $rows[$module->getName()] = [
+          'project' => $module->projectName,
+          'module' => $module->getName(),
+          'enabled' => $module->moduleIsEnabled ? dt("Yes") : dt("No"),
+          'has_modules' => $module->projectHasEnabledModules ? dt("Yes") : dt("No"),
+          'path' => $module->projectPath,
+        ];
+      }
+      elseif ($op == 'disabled') {
+        if (!$module->projectHasEnabledModules) {
+          $rows[$module->getName()] = [
+            'project' => $module->projectName,
+            'module' => $module->getName(),
+            'enabled' => $module->moduleIsEnabled ? dt("Yes") : dt("No"),
+            'has_modules' => $module->projectHasEnabledModules ? dt("Yes") : dt("No"),
+            'path' => $module->projectPath,
+          ];
+        }
+      }
+    }
+
+    if (!count($rows)) {
+      $this->output()->writeln("Hurray, no orphaned modules!");
+      return NULL;
+    }
+    return new RowsOfFields($rows);
+  }
+
+}
diff --git a/unused_modules.drush.inc b/unused_modules.drush.inc
index 5d80ceda2bfede71bb92cd140fd2455abe9055b2..b236fb4f682638b4235f7efa07c5c87f3c5624f6 100644
--- a/unused_modules.drush.inc
+++ b/unused_modules.drush.inc
@@ -21,7 +21,7 @@ function unused_modules_drush_command() {
       'As above, shorthand.' => 'drush um',
       'As above, include projects with enabled modules.' => 'drush unused-modules projects disabled',
       'Show modules that are unused.' => 'drush unused-modules modules disabled',
-      'As above, include enabled modules.' => 'drush unused-modules modules disabled',
+      'As above, include enabled modules.' => 'drush unused-modules modules all',
     ],
   ];
   return $items;