diff --git a/composer.json b/composer.json
index c2fe30c..b7dc4ef 100644
--- a/composer.json
+++ b/composer.json
@@ -29,7 +29,9 @@
         "ext-com_dotnet": "*",
         "illuminate/contracts": "^5.8",
         "symfony/process": "^4.2",
-        "tightenco/collect": "^5.8"
+        "tightenco/collect": "^5.8",
+        "psr/simple-cache": "^1.0",
+        "nesbot/carbon": "^1.26.3"
     },
     "require-dev": {
         "fzaninotto/faker": "^1.4",
diff --git a/src/WmiScripting/Concerns/HasArrayableAttributes.php b/src/WmiScripting/Concerns/HasArrayableAttributes.php
index 6810410..a73af56 100644
--- a/src/WmiScripting/Concerns/HasArrayableAttributes.php
+++ b/src/WmiScripting/Concerns/HasArrayableAttributes.php
@@ -3,6 +3,7 @@
 namespace PhpWinTools\WmiScripting\Concerns;
 
 use Exception;
+use Illuminate\Support\Arr;
 use PhpWinTools\Support\StringModule;
 use PhpWinTools\WmiScripting\Contracts\Arrayable;
 use PhpWinTools\WmiScripting\Collections\ArrayCollection;
@@ -18,6 +19,11 @@ trait HasArrayableAttributes
 
     public function getAttribute($attribute, $default = null)
     {
+//        dump($attribute);
+//        if (strpos($attribute, '.') !== false) {
+//            dump(explode('.', $attribute));
+//        }
+
         if ($this->hasAttributeMethod($attribute)) {
             return $this->getAttributeMethodValue('get' . StringModule::studly($attribute) . 'Attribute', $attribute);
         }
@@ -187,7 +193,7 @@ protected function hasToArrayMethod($value)
         return false;
     }
 
-    protected function transformToArray($value)
+    protected function transformToArray($value, $key = null)
     {
         if (is_object($value) && ($value instanceof Arrayable || $value instanceof IlluminateArrayable)) {
             return $value->toArray();
@@ -202,24 +208,50 @@ protected function transformToArray($value)
         }
 
         if (is_array($value)) {
-            return $this->iterateAttributes($value);
+            $value = $this->reduceValueArray($value);
+        }
+
+        if (is_array($value)) {
+            return $this->iterateAttributes($value, $key);
         }
 
         return $value;
     }
 
-    protected function iterateAttributes(array $attributes)
+    protected function iterateAttributes(array $attributes, string $current_key = null)
     {
         $results = [];
 
         foreach ($attributes as $key => $value) {
+            $dot_key = is_null($current_key) ? $key : "{$current_key}.{$key}";
+
             if (class_has_trait(get_called_class(), HasHiddenAttributes::class) && $this->isHidden($key)) {
                 continue;
             }
 
-            $results[$this->replaceAttributeName($key)] = $this->transformToArray($this->getAttribute($key, $value));
+            $value = $this->getAttribute($dot_key, $value);
+
+            $results[$this->replaceAttributeName($key)] = $this->transformToArray($value, $dot_key);
         }
 
         return $results;
     }
+
+    /**
+     * @param $value
+     *
+     * @return mixed
+     */
+    protected function reduceValueArray($value)
+    {
+        if (!is_array($value)) {
+            return $value;
+        }
+
+        if (array_key_exists('value', $value)) {
+            $value = $value['value'];
+        }
+
+        return $value;
+    }
 }
diff --git a/src/WmiScripting/Concerns/HasHiddenAttributes.php b/src/WmiScripting/Concerns/HasHiddenAttributes.php
index 0a59541..644c2d7 100644
--- a/src/WmiScripting/Concerns/HasHiddenAttributes.php
+++ b/src/WmiScripting/Concerns/HasHiddenAttributes.php
@@ -15,9 +15,11 @@ trait HasHiddenAttributes
         'attribute_name_replacements',
         'unmapped_attributes',
 
+        'hidden_booted',
         'hidden_attributes',
         'merge_parent_hidden_attributes',
 
+        'casts_booted',
         'attribute_casting',
         'merge_parent_casting',
     ];
diff --git a/src/WmiScripting/Configuration/Config.php b/src/WmiScripting/Configuration/Config.php
index ffae3e4..622a82d 100644
--- a/src/WmiScripting/Configuration/Config.php
+++ b/src/WmiScripting/Configuration/Config.php
@@ -2,34 +2,44 @@
 
 namespace PhpWinTools\WmiScripting\Configuration;
 
+use Closure;
 use Illuminate\Support\Arr;
 use PhpWinTools\Support\COM\ComWrapper;
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
+use PhpWinTools\WmiScripting\Containers\Container;
 use PhpWinTools\WmiScripting\Containers\Connections;
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
 use PhpWinTools\WmiScripting\Connections\ComConnection;
+use function PhpWinTools\WmiScripting\Support\is_closure;
+use function PhpWinTools\WmiScripting\Support\if_not_null;
+use PhpWinTools\WmiScripting\Support\Events\EventProvider;
+use function PhpWinTools\WmiScripting\Support\is_not_closure;
+use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
+use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
 use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
 use PhpWinTools\WmiScripting\Exceptions\UnresolvableClassException;
 
-class Config
+class Config extends Container
 {
     /** @var Config|null */
     protected static $instance = null;
 
+    /** @var bool */
     protected static $test_mode = false;
 
-    protected $config;
-
+    /** @var Resolver */
     protected $resolver;
 
+    /** @var array */
     protected $resolve_stack = [];
 
     public function __construct(array $config = [], Resolver $resolver = null)
     {
+        static::$instance = $this;
+
         $this->resolver = $resolver ?? new Resolver($this);
         $this->boot($config);
-
-        static::$instance = $this;
     }
 
     /**
@@ -44,7 +54,7 @@ public function __construct(array $config = [], Resolver $resolver = null)
     public static function instance(array $items = [], Resolver $resolver = null)
     {
         if (static::$instance && !empty($items)) {
-            return static::newInstance(array_merge(static::$instance->config, $items), $resolver);
+            return static::newInstance(array_merge(static::$instance->container, $items), $resolver);
         }
 
         if (static::$instance && $resolver) {
@@ -135,6 +145,14 @@ public function __invoke(string $class = null, ...$parameters)
             return $this->resolveFromStack($class);
         }
 
+        if ($this->isProviderClass($class)) {
+            return $this->getBoundProvider($class);
+        }
+
+        if (($bound = $this->bindings($class, false)) !== false) {
+            return $bound;
+        }
+
         if (array_key_exists($class, $this->apiObjects())) {
             return $this->resolve()->make($this->getApiObject($class), ...$parameters);
         }
@@ -159,7 +177,7 @@ public function resolve()
      *
      * @return array
      */
-    public function resolveStack()
+    public function getResolveStack()
     {
         return $this->resolve_stack;
     }
@@ -189,12 +207,12 @@ public function resolveFromStack($abstract)
     /**
      * Add new resolvable to the end of the stack.
      *
-     * @param $abstract
-     * @param $concrete
+     * @param string $abstract
+     * @param        $concrete
      *
      * @return Config
      */
-    public function addResolvable($abstract, $concrete)
+    public function addResolvable(string $abstract, $concrete)
     {
         $this->resolve_stack[] = [$abstract => $concrete];
 
@@ -204,11 +222,11 @@ public function addResolvable($abstract, $concrete)
     /**
      * Check stack for resolvable. There may be a chance for caching pointers for resolvable abstracts.
      *
-     * @param $abstract
+     * @param string $abstract
      *
      * @return bool
      */
-    public function hasResolvable($abstract): bool
+    public function hasResolvable(string $abstract): bool
     {
         foreach ($this->resolve_stack as $key => $resolvable) {
             if (array_key_exists($abstract, $resolvable)) {
@@ -271,6 +289,164 @@ public function addComObject(string $abstract_class, $concrete_class)
         return $this;
     }
 
+    /**
+     * @return EventProvider
+     */
+    public function eventProvider()
+    {
+        return $this->getBoundProvider('event');
+    }
+
+    /**
+     * @return EventHistoryProvider
+     */
+    public function eventHistoryProvider()
+    {
+        return $this->getBoundProvider('event_history');
+    }
+
+    /**
+     * @return CommandBus
+     */
+    public function commandBus()
+    {
+        return $this->getBoundProvider('bus');
+    }
+
+    /**
+     * Returns the given binding if exists and provided, or all bindings if not given. If the value cannot be found
+     * it will return $default which can be a Closure that can be used to resolve the binding.
+     *
+     * @param string|null $abstract
+     * @param array       $default
+     *
+     * @return mixed
+     */
+    public function bindings(string $abstract = null, $default = [])
+    {
+        return $this->get('bindings' . if_not_null($abstract, ".{$abstract}"), $default);
+    }
+
+    /**
+     * Stores the given abstract with a reference to the given instance.
+     *
+     * @param string $abstract
+     * @param        $concrete
+     *
+     * @return Config
+     */
+    public function bind(string $abstract, $concrete)
+    {
+        if (is_object($concrete) && class_exists($abstract) === false && is_not_closure($concrete)) {
+            $this->set('bindings.' . get_class($concrete), $concrete);
+        }
+
+        if ($this->isProviderClass($abstract)) {
+            $this->set('bindings.' . $this->getProviderAlias($abstract), $concrete);
+        }
+
+        return $this->set("bindings.{$abstract}", $concrete);
+    }
+
+    /**
+     * Resolves the given concrete with either the given constructor arguments. If the first argument is a Closure
+     * it will be used to resolve the given concrete.
+     *
+     * @param       $class
+     * @param mixed ...$constructor
+     *
+     * @return mixed
+     */
+    public function make(string $class, ...$constructor)
+    {
+        if (isset($constructor[0]) && is_closure($constructor[0])) {
+            $make = Arr::pull($constructor, 0);
+        }
+
+        $make = $make ?? function ($class) use ($constructor) {
+            return new $class($this, ...$constructor);
+        };
+
+        return $make($class, $this);
+    }
+
+    /**
+     * @param string|null $abstract
+     * @param array       $default
+     *
+     * @return mixed
+     */
+    public function concreteProviders(string $abstract = null, $default = [])
+    {
+        if (is_null($abstract)) {
+            return $this->get('providers.concrete', $default);
+        }
+
+        if (class_exists($abstract) && array_key_exists($abstract, $this->get('providers.aliases'))) {
+            return $abstract;
+        }
+
+        return $this->get("providers.concrete.{$abstract}", $default);
+    }
+
+    /**
+     * Returns an already registered provider or instantiates it from configuration when $default is null.
+     *
+     * @param string     $alias
+     * @param null|mixed $default
+     *
+     * @return mixed
+     */
+    public function getBoundProvider($alias, $default = null)
+    {
+        $default = $default ?? function () use ($alias) {
+            return $this->registerProvider($alias);
+        };
+
+        return $this->bindings($alias, $default);
+    }
+
+    /**
+     * @param string              $abstract
+     * @param array|mixed|Closure ...$constructor
+     *
+     * @return mixed
+     */
+    public function makeProvider(string $abstract, ...$constructor)
+    {
+        if (($provider = $this->concreteProviders($abstract, false)) === false) {
+            throw new InvalidArgumentException("{$abstract} is not a valid provider.");
+        }
+
+        return $this->make($provider, ...$constructor);
+    }
+
+    public function getCacheDriver($provider = null)
+    {
+        $driver = $this->get(if_not_null($provider, "{$provider}.") . 'cache.driver');
+
+        return new $driver($this);
+    }
+
+    public function shouldTrackEvents()
+    {
+        return $this->get('event.track', false);
+    }
+
+    public function trackEvents()
+    {
+        $this->set('event.track', true);
+
+        return $this;
+    }
+
+    public function doNotTrackEvents()
+    {
+        $this->set('event.track', false);
+
+        return $this;
+    }
+
     /**
      * @return string
      */
@@ -362,23 +538,54 @@ public function setDefaultConnection(string $name)
             throw InvalidConnectionException::new($name);
         }
 
-        $this->set('wmi.connections.default', $name);
+        return $this->set('wmi.connections.default', $name);
+    }
+
+    public function registerProvider($alias, $instance = null)
+    {
+        if (is_null($instance) && ($bound = $this->getBoundProvider($alias, false)) !== false) {
+            return $bound;
+        }
 
-        return $this;
+        if (!is_object($instance)) {
+            $instance = $this->makeProvider($alias);
+        }
+
+        $this->bind("{$alias}", $instance);
+
+        return $instance;
     }
 
-    public function get($key, $default = null)
+    /**
+     * @param string $abstract
+     *
+     * @return bool
+     */
+    protected function isProvider(string $abstract): bool
     {
-        return Arr::get($this->config, $key, $default);
+        return array_key_exists($abstract, $this->concreteProviders())
+            || $this->concreteProviders($abstract, false)
+            || $this->getProviderAlias($abstract) !== false;
     }
 
-    public function set($key, $value = null)
+    /**
+     * @param string $abstract
+     *
+     * @return bool
+     */
+    protected function isProviderClass(string $abstract): bool
     {
-        $keys = is_array($key) ? $key : [$key => $value];
+        return class_exists($abstract) && $this->isProvider($abstract);
+    }
 
-        foreach ($keys as $key => $value) {
-            Arr::set($this->config, $key, $value);
-        }
+    /**
+     * @param string $abstract
+     *
+     * @return bool|false|int|string
+     */
+    protected function getProviderAlias(string $abstract)
+    {
+        return class_exists($abstract) ? array_search($abstract, $this->concreteProviders(), true) : false;
     }
 
     protected function boot(array $config)
@@ -391,20 +598,24 @@ protected function boot(array $config)
             $this->merge($config);
         }
 
-        $this->merge(include(__DIR__ . '/../config/bootstrap.php'));
-
-        $this->bootConnections();
+        $this->merge(include(__DIR__ . '/../config/bootstrap.php'))
+            ->registerProviders()
+            ->bootConnections();
     }
 
-    protected function merge(array $config)
+    protected function registerProviders()
     {
-        foreach (Arr::dot($config) as $key => $value) {
-            $this->config = Arr::add($this->config, $key, $value);
-        }
+        array_map(function ($alias) {
+            $this->registerProvider($alias);
+        }, array_keys($this->concreteProviders()));
+
+        return $this;
     }
 
     protected function bootConnections()
     {
-        Arr::set($this->config, 'wmi.connections.servers', new Connections($this));
+        Arr::set($this->container, 'wmi.connections.servers', new Connections($this));
+
+        return $this;
     }
 }
diff --git a/src/WmiScripting/Connections/ComConnection.php b/src/WmiScripting/Connections/ComConnection.php
index c5c6400..a6f4687 100644
--- a/src/WmiScripting/Connections/ComConnection.php
+++ b/src/WmiScripting/Connections/ComConnection.php
@@ -5,7 +5,7 @@
 use function PhpWinTools\WmiScripting\Support\resolve;
 use PhpWinTools\WmiScripting\Support\ApiObjects\Contracts\Services;
 
-class ComConnection
+class ComConnection implements Connection
 {
     const DEFAULT_SERVER = '.';
 
@@ -101,6 +101,14 @@ public function connect(): Services
         return $this->services;
     }
 
+    public function query($query, $model, $relationships)
+    {
+        return $this->connect()
+            ->resolvePropertySets($relationships)
+            ->execQuery($query)
+            ->get();
+    }
+
     /**
      * @return float
      */
diff --git a/src/WmiScripting/Connections/Connection.php b/src/WmiScripting/Connections/Connection.php
index 616a122..201ac0c 100644
--- a/src/WmiScripting/Connections/Connection.php
+++ b/src/WmiScripting/Connections/Connection.php
@@ -4,4 +4,5 @@
 
 interface Connection
 {
+    public function query($query, $model, $relationships);
 }
diff --git a/src/WmiScripting/Connections/ConnectionManager.php b/src/WmiScripting/Connections/ConnectionManager.php
new file mode 100644
index 0000000..9c54290
--- /dev/null
+++ b/src/WmiScripting/Connections/ConnectionManager.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Connections;
+
+use PhpWinTools\WmiScripting\Configuration\Config;
+
+class ConnectionManager
+{
+    /** @var Config */
+    protected $config;
+
+    public function __construct(Config $config = null)
+    {
+        $this->config = $config ?? Config::instance();
+    }
+}
diff --git a/src/WmiScripting/Connections/Response.php b/src/WmiScripting/Connections/Response.php
new file mode 100644
index 0000000..3b33de0
--- /dev/null
+++ b/src/WmiScripting/Connections/Response.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Connections;
+
+use PhpWinTools\WmiScripting\Containers\Collection;
+
+class Response extends Collection
+{
+
+}
diff --git a/src/WmiScripting/Containers/Collection.php b/src/WmiScripting/Containers/Collection.php
new file mode 100644
index 0000000..98189b3
--- /dev/null
+++ b/src/WmiScripting/Containers/Collection.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Containers;
+
+use Illuminate\Support\Collection as BaseCollection;
+
+class Collection extends BaseCollection
+{
+}
diff --git a/src/WmiScripting/Containers/Connections.php b/src/WmiScripting/Containers/Connections.php
index b12927b..c2fab5f 100644
--- a/src/WmiScripting/Containers/Connections.php
+++ b/src/WmiScripting/Containers/Connections.php
@@ -5,7 +5,7 @@
 use PhpWinTools\WmiScripting\Configuration\Config;
 use PhpWinTools\WmiScripting\Connections\ComConnection;
 
-class Connections extends Container
+class Connections extends Collection
 {
     public function __construct(Config $config)
     {
diff --git a/src/WmiScripting/Containers/Container.php b/src/WmiScripting/Containers/Container.php
index 549c483..780ada0 100644
--- a/src/WmiScripting/Containers/Container.php
+++ b/src/WmiScripting/Containers/Container.php
@@ -2,8 +2,56 @@
 
 namespace PhpWinTools\WmiScripting\Containers;
 
-use Illuminate\Support\Collection;
+use Illuminate\Support\Arr;
+use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
 
-class Container extends Collection
+class Container
 {
+    protected $container;
+
+    public function get($key, $default = null)
+    {
+        return Arr::get($this->container, $key, $default);
+    }
+
+    public function append($key, $value)
+    {
+        $index = null;
+
+        if (is_null($array = $this->get($key))) {
+            $index = 0;
+        }
+
+        if ($array && Arr::isAssoc($array)) {
+            throw new InvalidArgumentException("Cannot append to {$key} because it is associative.");
+        }
+
+        if ($array) {
+            $index = count($array) - 1;
+        }
+
+        $this->set("{$key}.{$index}", $value);
+
+        return $this;
+    }
+
+    public function set($key, $value = null)
+    {
+        $keys = is_array($key) ? $key : [$key => $value];
+
+        foreach ($keys as $key => $value) {
+            Arr::set($this->container, $key, $value);
+        }
+
+        return $this;
+    }
+
+    public function merge(array $array)
+    {
+        foreach (Arr::dot($array) as $key => $value) {
+            $this->container = Arr::add($this->container, $key, $value);
+        }
+
+        return $this;
+    }
 }
diff --git a/src/WmiScripting/Exceptions/CacheInvalidArgumentException.php b/src/WmiScripting/Exceptions/CacheInvalidArgumentException.php
new file mode 100644
index 0000000..b327ca2
--- /dev/null
+++ b/src/WmiScripting/Exceptions/CacheInvalidArgumentException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Exceptions;
+
+use Psr\SimpleCache\InvalidArgumentException as BaseException;
+
+class CacheInvalidArgumentException extends InvalidArgumentException implements BaseException
+{
+}
diff --git a/src/WmiScripting/Models/Win32Model.php b/src/WmiScripting/Models/Win32Model.php
index 52df451..19da511 100644
--- a/src/WmiScripting/Models/Win32Model.php
+++ b/src/WmiScripting/Models/Win32Model.php
@@ -19,7 +19,6 @@
 use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
 use PhpWinTools\WmiScripting\Exceptions\WmiClassNotFoundException;
 use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
-use PhpWinTools\WmiScripting\Support\ApiObjects\Contracts\ObjectPath;
 
 /**
  * @link https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-provider
@@ -33,8 +32,12 @@ class Win32Model implements Arrayable, Jsonable, HasAttributes, HidesAttributes,
     /** @var string */
     protected $uuid;
 
-    /** @var ObjectPath|null */
-    protected $objectPath;
+    /** @var array|null */
+    protected $objectPath = [];
+
+    protected $qualifiers = [];
+
+    protected $derivations = [];
 
     /** @var Builder|null */
     protected $queryBuilder;
@@ -55,24 +58,27 @@ class Win32Model implements Arrayable, Jsonable, HasAttributes, HidesAttributes,
 
     protected $wmi_class_name;
 
-    /* TODO: Remove ObjectPath dependency. Should be passed into attributes */
-    public function __construct(array $attributes = [], ObjectPath $objectPath = null)
+    public function __construct(array $attributes = [])
     {
         $this->mergeHiddenAttributes($this->hidden_attributes, $this->merge_parent_hidden_attributes);
         $this->fill($attributes);
-
-        $this->objectPath = $objectPath;
     }
 
     /**
-     * @param array           $attributes
-     * @param ObjectPath|null $objectPath
+     * @param array       $attributes
+     * @param string|null $connection
      *
      * @return Win32Model
      */
-    public static function newInstance(array $attributes = [], ObjectPath $objectPath = null)
+    public static function newInstance(array $attributes = [], string $connection = null)
     {
-        return new static($attributes, $objectPath);
+        $instance = new static($attributes);
+
+        if ($connection) {
+            $instance->setConnection($connection);
+        }
+
+        return $instance;
     }
 
     /**
@@ -115,6 +121,11 @@ public function getConnection($connection = null)
         return connection($connection, $this->connection);
     }
 
+    public function setConnection(string $connection)
+    {
+        $this->connection = $connection;
+    }
+
     /**
      * @return string
      */
@@ -217,20 +228,6 @@ protected function fill(array $attributes)
         }
     }
 
-    /**
-     * @param $value
-     *
-     * @return mixed
-     */
-    protected function reduceValueArray($value)
-    {
-        if (is_array($value) && array_key_exists('value', $value)) {
-            $value = $value['value'];
-        }
-
-        return $value;
-    }
-
     /**
      * @param Mappings|string $mapping_string_class
      * @param mixed           $constant
diff --git a/src/WmiScripting/Query/Builder.php b/src/WmiScripting/Query/Builder.php
index fab8d87..fd9971f 100644
--- a/src/WmiScripting/Query/Builder.php
+++ b/src/WmiScripting/Query/Builder.php
@@ -3,7 +3,7 @@
 namespace PhpWinTools\WmiScripting\Query;
 
 use PhpWinTools\WmiScripting\Models\Win32Model;
-use PhpWinTools\WmiScripting\Connections\ComConnection;
+use PhpWinTools\WmiScripting\Connections\Connection;
 use PhpWinTools\WmiScripting\Collections\ModelCollection;
 use PhpWinTools\WmiScripting\Support\ApiObjects\Contracts\ObjectSet;
 
@@ -11,63 +11,54 @@ class Builder
 {
     protected $model;
 
-    protected $from;
-
     protected $services;
 
+    protected $connection;
+
+    protected $from;
+
     protected $selects = ['*'];
 
     protected $wheres = [];
 
     protected $relationships = [];
 
-    public function __construct(Win32Model $model, ComConnection $connection)
+    public function __construct(Win32Model $model, Connection $connection)
     {
         $this->model = $model;
+        $this->connection = $connection;
         $this->from = $model->getWmiClassNameAttribute();
         $this->services = $connection->connect();
     }
 
     public function all()
     {
-        return $this->execQuery("select * from {$this->from}")->getSet();
+        return $this->hydrate($this->query("select * from {$this->from}"));
     }
 
-    /**
-     * @return ModelCollection|Win32Model[]
-     */
-    public function get()
+    public function hydrate(array $items = [])
     {
-        return $this->getModelCollection();
+        return new ModelCollection(array_map(function ($item) {
+            return $this->model::newInstance($item);
+        }, $items));
     }
 
     /**
      * @return ModelCollection|Win32Model[]
      */
-    public function getModelCollection()
-    {
-        return $this->getObjectSet()->getSet();
-    }
-
-    /**
-     * @return ObjectSet
-     */
-    public function getObjectSet(): ObjectSet
+    public function get()
     {
-        return $this->execQuery($this->queryString());
+        return $this->hydrate($this->query($this->queryString()));
     }
 
     /**
-     * @param string $query
+     * @param $query
      *
-     * @return ObjectSet
+     * @return mixed|ObjectSet
      */
-    public function execQuery($query): ObjectSet
+    public function query($query)
     {
-        return $this->services
-            ->resolvePropertySets($this->relationships)
-            ->execQuery($query)
-            ->instantiateModels($this->model);
+        return $this->connection->query($query, $this->model, $this->relationships);
     }
 
     /**
diff --git a/src/WmiScripting/Support/ApiObjects/Contracts/ObjectItem.php b/src/WmiScripting/Support/ApiObjects/Contracts/ObjectItem.php
index 5870fdd..44bc20c 100644
--- a/src/WmiScripting/Support/ApiObjects/Contracts/ObjectItem.php
+++ b/src/WmiScripting/Support/ApiObjects/Contracts/ObjectItem.php
@@ -11,5 +11,7 @@ interface ObjectItem extends WbemObject
      */
     public function getModel(): Win32Model;
 
+    public function get();
+
     public function instantiateWin32Model(Win32Model $model = null): Win32Model;
 }
diff --git a/src/WmiScripting/Support/ApiObjects/Contracts/ObjectSet.php b/src/WmiScripting/Support/ApiObjects/Contracts/ObjectSet.php
index c98ed74..5b44bf8 100644
--- a/src/WmiScripting/Support/ApiObjects/Contracts/ObjectSet.php
+++ b/src/WmiScripting/Support/ApiObjects/Contracts/ObjectSet.php
@@ -10,6 +10,8 @@ interface ObjectSet extends WbemObject, ArrayAccess
 {
     public function count();
 
+    public function get();
+
     /**
      * @return Win32Model[]|ModelCollection
      */
diff --git a/src/WmiScripting/Support/ApiObjects/SWbemObject.php b/src/WmiScripting/Support/ApiObjects/SWbemObject.php
index 0252c3a..fc34776 100644
--- a/src/WmiScripting/Support/ApiObjects/SWbemObject.php
+++ b/src/WmiScripting/Support/ApiObjects/SWbemObject.php
@@ -22,6 +22,8 @@ class SWbemObject extends AbstractWbemObject implements ObjectItem
 
     protected $qualifierSet;
 
+    protected $mergedProperties = [];
+
     protected $path;
 
     /** @var VariantWrapper|ObjectVariant */
@@ -37,6 +39,13 @@ public function __construct(VariantWrapper $variant, array $resolve_property_set
         $this->qualifierSet = resolve()->qualifierSet($this->object->Qualifiers_);
         $this->path = resolve()->objectPath($this->object->Path_);
 
+        $this->mergedProperties = array_merge(
+            $this->propertySet->toArray(),
+            ['qualifiers' => $this->qualifierSet->toArray()['qualifiers']],
+            ['objectPath' => $this->path->toArray()],
+            ['derivations' => $this->derivations]
+        );
+
         $this->instantiateWin32Model();
     }
 
@@ -48,6 +57,11 @@ public function getModel(): Win32Model
         return $this->getAttribute('win32Model');
     }
 
+    public function get()
+    {
+        return $this->mergedProperties;
+    }
+
     public function instantiateWin32Model(Win32Model $model = null): Win32Model
     {
         $model = $model ?? Classes::getClass($this->path->getAttribute('class'));
@@ -56,7 +70,7 @@ public function instantiateWin32Model(Win32Model $model = null): Win32Model
             return $this->win32Model;
         }
 
-        return $this->win32Model = new $model($this->propertySet->toArray(), $this->path);
+        return $this->win32Model = new $model($this->mergedProperties);
     }
 
     protected function buildDerivations()
diff --git a/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php b/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php
index 021cf40..88a7071 100644
--- a/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php
+++ b/src/WmiScripting/Support/ApiObjects/SWbemObjectSet.php
@@ -6,6 +6,7 @@
 use ArrayAccess;
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
+use PhpWinTools\WmiScripting\Connections\Response;
 use PhpWinTools\WmiScripting\Models\Win32Model;
 use function PhpWinTools\WmiScripting\Support\resolve;
 use PhpWinTools\WmiScripting\Collections\ModelCollection;
@@ -49,6 +50,13 @@ public function count(): int
         return $this->count;
     }
 
+    public function get()
+    {
+        return $this->set->map(function (ObjectItem $item) {
+            return $item->get();
+        })->toArray();
+    }
+
     /**
      * @return Win32Model[]|ModelCollection
      */
@@ -61,7 +69,6 @@ public function instantiateModels(Win32Model $model): ObjectSet
     {
         $this->set = $this->set->map(function (ObjectItem $item) use ($model) {
             $item->instantiateWin32Model($model);
-
             return $item;
         });
 
diff --git a/src/WmiScripting/Support/Bus/CommandBus.php b/src/WmiScripting/Support/Bus/CommandBus.php
new file mode 100644
index 0000000..41afeb1
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/CommandBus.php
@@ -0,0 +1,203 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus;
+
+use Closure;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PostMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\MiddlewareStack;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PostMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\CommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Events\FailureMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\SuccessMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\FailureMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\SuccessMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
+
+class CommandBus
+{
+    /** @var self|null */
+    protected static $instance = null;
+
+    /** @var Config */
+    protected $config;
+
+    /** @var array|CommandHandler[] */
+    protected $handlers = [];
+
+    /** @var MiddlewareStack */
+    protected $preMiddleware;
+
+    /** @var MiddlewareStack */
+    protected $postMiddleware = [];
+
+    /** @var MiddlewareStack */
+    protected $failureMiddleware = [];
+
+    /** @var MiddlewareStack */
+    protected $successMiddleware = [];
+
+    public function __construct(Config $config = null)
+    {
+        $this->config = $config ?? Config::instance();
+
+        $this->bootMiddlewareStack();
+
+        static::$instance = $this->config->registerProvider('bus', $this);
+    }
+
+    public static function instance(Config $config = null)
+    {
+        return static::$instance ?? new static($config ?? Config::instance());
+    }
+
+    public function getConfig()
+    {
+        return $this->config;
+    }
+
+    public function assignHandler($command, CommandHandler $commandHandler)
+    {
+        if ($this->invalidCommand($command)) {
+            throw new InvalidArgumentException("{$command} must extend " . Command::class);
+        }
+
+        $this->handlers[$command] = $commandHandler;
+
+        return $this;
+    }
+
+    public function registerMiddleware($middleware, $command, string $stack = PostCommandMiddleware::class)
+    {
+        if ($this->invalidCommand($command)) {
+            throw new InvalidArgumentException("{$command} must extend " . Command::class);
+        }
+
+        if ($this->invalidMiddleware($middleware)) {
+            throw new InvalidArgumentException("{$middleware} must extend " . CommandMiddleware::class);
+        }
+
+        $parents = class_parents($middleware);
+
+        if ($middleware instanceof Closure) {
+            $parents = [$stack => $stack];
+        }
+
+        if (array_key_exists(PreCommandMiddleware::class, $parents)) {
+            $this->preMiddleware->add($middleware, $command);
+
+            return $this;
+        }
+
+        if (array_key_exists(SuccessCommandMiddleware::class, $parents)) {
+            $this->successMiddleware->add($middleware, $command);
+
+            return $this;
+        }
+
+        if (array_key_exists(FailureCommandMiddleware::class, $parents)) {
+            $this->failureMiddleware->add($middleware, $command);
+
+            return $this;
+        }
+
+        $this->postMiddleware->add($middleware, $command);
+
+        return $this;
+    }
+
+    public function handle(Command $command, Closure $core_callback = null)
+    {
+        $result = null;
+        $command_failure = true;
+        $command_name = get_class($command);
+
+        $pre = $this->preMiddleware->process($command, $core_callback);
+
+        if (array_key_exists($command_name, $this->handlers)) {
+            $handler = $this->handlers[$command_name];
+
+            $this->fireEvent(new CommandHandlerStarted($this, $command, $handler));
+            $result = $handler->handle($pre);
+            $command_failure = $handler->getFailureResult() === $result;
+            $this->fireEvent(new CommandHandlerEnded($this, $command, $handler));
+        }
+
+        $result = $this->postMiddleware->process($command, $core_callback);
+
+        if ($command_failure) {
+            $result = $this->failureMiddleware->process($result, $core_callback);
+        } else {
+            $result = $this->successMiddleware->process($result, $core_callback);
+        }
+
+        return $result;
+    }
+
+    protected function fireEvent(Event $event)
+    {
+        $this->getConfig()->eventProvider()->fire($event);
+    }
+
+    protected function bootMiddlewareStack()
+    {
+        $this->preMiddleware = new MiddlewareStack(
+            $this->getConfig(),
+            PreMiddlewareStarted::class,
+            PreMiddlewareEnded::class
+        );
+
+        $this->postMiddleware = new MiddlewareStack(
+            $this->getConfig(),
+            PostMiddlewareStarted::class,
+            PostMiddlewareEnded::class
+        );
+
+        $this->failureMiddleware = new MiddlewareStack(
+            $this->getConfig(),
+            FailureMiddlewareStarted::class,
+            FailureMiddlewareEnded::class
+        );
+
+        $this->successMiddleware = new MiddlewareStack(
+            $this->getConfig(),
+            SuccessMiddlewareStarted::class,
+            SuccessMiddlewareEnded::class
+        );
+    }
+
+    protected function validCommand($command)
+    {
+        return $command === '*' || $this->classExtends($command, Command::class);
+    }
+
+    protected function invalidCommand($command)
+    {
+        return $this->validCommand($command) === false;
+    }
+
+    protected function validMiddleware($middleware)
+    {
+        return $middleware instanceof Closure || $this->classExtends($middleware, CommandMiddleware::class);
+    }
+
+    protected function invalidMiddleware($middleware)
+    {
+        return $this->validMiddleware($middleware) === false;
+    }
+
+    protected function classExtends($class, $parent)
+    {
+        return array_key_exists($parent, class_parents($class));
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/CommandHandler.php b/src/WmiScripting/Support/Bus/CommandHandler.php
new file mode 100644
index 0000000..e619428
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/CommandHandler.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus;
+
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+
+abstract class CommandHandler
+{
+    protected $failure_result = null;
+
+    abstract public function handle(Command $command);
+
+    public function getFailureResult()
+    {
+        return $this->failure_result;
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Commands/Command.php b/src/WmiScripting/Support/Bus/Commands/Command.php
new file mode 100644
index 0000000..b1f4e7e
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Commands/Command.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Commands;
+
+abstract class Command
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php b/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
new file mode 100644
index 0000000..a153e27
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/CommandBusEvent.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Events\Payload;
+
+class CommandBusEvent extends Event
+{
+    protected $bus;
+
+    protected $command;
+
+    public function __construct(CommandBus $bus, $command = null)
+    {
+        $this->bus = $bus;
+        $this->command = $command;
+
+        parent::__construct((new Payload())->add('bus', $this->bus)->add('subject', $this->command));
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Events/CommandHandlerEnded.php b/src/WmiScripting/Support/Bus/Events/CommandHandlerEnded.php
new file mode 100644
index 0000000..137740e
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/CommandHandlerEnded.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class CommandHandlerEnded extends CommandHandlerEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php b/src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php
new file mode 100644
index 0000000..160ef30
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/CommandHandlerEvent.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Bus\CommandHandler;
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+
+class CommandHandlerEvent extends CommandBusEvent
+{
+    public function __construct(CommandBus $bus, Command $command, CommandHandler $commandHandler)
+    {
+        parent::__construct($bus, $command);
+
+        $this->payload->add('handler', $commandHandler);
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Events/CommandHandlerStarted.php b/src/WmiScripting/Support/Bus/Events/CommandHandlerStarted.php
new file mode 100644
index 0000000..5bbbea9
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/CommandHandlerStarted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class CommandHandlerStarted extends CommandHandlerEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/CommandMiddlewareEvent.php b/src/WmiScripting/Support/Bus/Events/CommandMiddlewareEvent.php
new file mode 100644
index 0000000..415682c
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/CommandMiddlewareEvent.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class CommandMiddlewareEvent extends CommandBusEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/FailureMiddlewareEnded.php b/src/WmiScripting/Support/Bus/Events/FailureMiddlewareEnded.php
new file mode 100644
index 0000000..ebad9c9
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/FailureMiddlewareEnded.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class FailureMiddlewareEnded extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/FailureMiddlewareStarted.php b/src/WmiScripting/Support/Bus/Events/FailureMiddlewareStarted.php
new file mode 100644
index 0000000..f8ac1e1
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/FailureMiddlewareStarted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class FailureMiddlewareStarted extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/PostMiddlewareEnded.php b/src/WmiScripting/Support/Bus/Events/PostMiddlewareEnded.php
new file mode 100644
index 0000000..889f081
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/PostMiddlewareEnded.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class PostMiddlewareEnded extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/PostMiddlewareEvent.php b/src/WmiScripting/Support/Bus/Events/PostMiddlewareEvent.php
new file mode 100644
index 0000000..1d0064a
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/PostMiddlewareEvent.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class PostMiddlewareEvent extends CommandMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/PostMiddlewareStarted.php b/src/WmiScripting/Support/Bus/Events/PostMiddlewareStarted.php
new file mode 100644
index 0000000..ba1e713
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/PostMiddlewareStarted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class PostMiddlewareStarted extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php b/src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php
new file mode 100644
index 0000000..e121044
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/PreMiddlewareEnded.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class PreMiddlewareEnded extends CommandMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php b/src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php
new file mode 100644
index 0000000..42371f6
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/PreMiddlewareStarted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class PreMiddlewareStarted extends CommandMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareEnded.php b/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareEnded.php
new file mode 100644
index 0000000..bd36a9d
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareEnded.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class SuccessMiddlewareEnded extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareStarted.php b/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareStarted.php
new file mode 100644
index 0000000..59f7d93
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Events/SuccessMiddlewareStarted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Events;
+
+class SuccessMiddlewareStarted extends PostMiddlewareEvent
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
new file mode 100644
index 0000000..16c6e2c
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/CommandMiddleware.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+use Closure;
+
+class CommandMiddleware
+{
+    public function handle($subject, Closure $next)
+    {
+        return $next($subject);
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/FailureCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/FailureCommandMiddleware.php
new file mode 100644
index 0000000..604c1ff
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/FailureCommandMiddleware.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+class FailureCommandMiddleware extends PostCommandMiddleware
+{
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php b/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
new file mode 100644
index 0000000..9f11974
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/MiddlewareStack.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+use Closure;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Exceptions\InvalidArgumentException;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandMiddlewareEvent;
+
+class MiddlewareStack
+{
+    protected $config;
+
+    protected $pre_event_class;
+
+    protected $post_event_class;
+
+    protected $stack = [];
+
+    public function __construct(Config $config = null, $pre_event_class = null, $post_event_class = null)
+    {
+        $this->config = $config ?? Config::instance();
+        $this->pre_event_class = $this->validateEventClass($pre_event_class);
+        $this->post_event_class = $this->validateEventClass($post_event_class);
+    }
+
+    public function add($middleware, string $subject)
+    {
+        $this->stack[$subject][] = $middleware;
+
+        return $this;
+    }
+
+    public function get($subject)
+    {
+        $subject = is_object($subject) ? get_class($subject) : $subject;
+
+        $wildcard = $this->prep($this->stack['*'] ?? []);
+
+        return array_merge($this->prep($this->stack[$subject] ?? []), $wildcard);
+    }
+
+    public function process($subject, Closure $core = null)
+    {
+        if ($this->pre_event_class) {
+            $this->fireEvent(new $this->pre_event_class($this->config->commandBus(), $subject));
+        }
+
+        $core = $core ?? function ($subject) {
+            return $subject;
+        };
+
+        $stack = array_reduce($this->get($subject), function ($next, $middleware) {
+            if ($middleware instanceof Closure) {
+                return function ($command) use ($next, $middleware) {
+                    return $middleware($command, $next);
+                };
+            }
+
+            /** @var CommandMiddleware $middleware */
+            $middleware = new $middleware();
+
+            return function ($command) use ($next, $middleware) {
+                return $middleware->handle($command, $next);
+            };
+        }, $core);
+
+        $result = $stack($subject);
+
+        if ($this->post_event_class) {
+            $this->fireEvent(new $this->post_event_class($this->config->commandBus(), $subject));
+        }
+
+        return $result;
+    }
+
+    protected function fireEvent(CommandMiddlewareEvent $event)
+    {
+        $this->config->eventProvider()->fire($event);
+    }
+
+    protected function validateEventClass($event_class)
+    {
+        if (!array_key_exists(CommandMiddlewareEvent::class, class_parents($event_class))) {
+            throw new InvalidArgumentException('Events must extend ' . CommandMiddlewareEvent::class);
+        }
+
+        return $event_class;
+    }
+
+    protected function prep(array $stack)
+    {
+        return array_reverse($stack);
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php
new file mode 100644
index 0000000..ac8f642
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/PostCommandMiddleware.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+use Closure;
+
+class PostCommandMiddleware extends CommandMiddleware
+{
+    public function handle($handled_result, Closure $next)
+    {
+        return parent::handle($handled_result, $next);
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php
new file mode 100644
index 0000000..b732dc5
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/PreCommandMiddleware.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+use Closure;
+
+class PreCommandMiddleware extends CommandMiddleware
+{
+    public function handle($command, Closure $next)
+    {
+        return parent::handle($command, $next);
+    }
+}
diff --git a/src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php b/src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php
new file mode 100644
index 0000000..49ea6df
--- /dev/null
+++ b/src/WmiScripting/Support/Bus/Middleware/SuccessCommandMiddleware.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Bus\Middleware;
+
+class SuccessCommandMiddleware extends PostCommandMiddleware
+{
+}
diff --git a/src/WmiScripting/Support/Cache/ArrayDriver.php b/src/WmiScripting/Support/Cache/ArrayDriver.php
new file mode 100644
index 0000000..50d1059
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/ArrayDriver.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache;
+
+use Carbon\Carbon;
+use Illuminate\Support\Arr;
+use Psr\SimpleCache\CacheInterface;
+
+class ArrayDriver extends CacheDriver implements CacheInterface
+{
+    /** @var array */
+    protected $store = [];
+
+    public function get($key, $default = null)
+    {
+        return Arr::get($this->store, "{$this->validateKey($key)}.value", $default);
+    }
+
+    public function set($key, $value, $ttl = null)
+    {
+        return is_array(Arr::set(
+            $this->store,
+            $this->validateKey($key),
+            [
+                'value' => $value,
+                'expires' => is_null($ttl) ? null : Carbon::now()->addSeconds($ttl)
+            ]
+        ));
+    }
+
+    public function delete($key)
+    {
+        if ($this->has($key)) {
+            Arr::forget($this->store, $key);
+            return true;
+        }
+
+        return false;
+    }
+
+    public function clear()
+    {
+        $this->store = [];
+
+        return true;
+    }
+
+    public function getMultiple($keys, $default = null)
+    {
+        $result = [];
+
+        foreach ($keys as $key) {
+            $result[$key] = $this->get($key, $default);
+        }
+
+        return $result;
+    }
+
+    public function setMultiple($values, $ttl = null)
+    {
+        foreach ($values as $key => $value) {
+            $this->set($key, $value, $ttl);
+        }
+
+        return true;
+    }
+
+    public function deleteMultiple($keys)
+    {
+        foreach ($keys as $key) {
+            if ($this->delete($key)) {
+                continue;
+            } else {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public function expired($key): bool
+    {
+        $expires = Arr::get($this->store, "{$key}.expires");
+
+        return is_null($expires) ? false : Carbon::now()->greaterThan($expires);
+    }
+
+    public function empty(): bool
+    {
+        return empty($this->store);
+    }
+
+    public function has($key)
+    {
+        return Arr::has($this->store, $this->validateKey($key));
+    }
+}
diff --git a/src/WmiScripting/Support/Cache/CacheDriver.php b/src/WmiScripting/Support/Cache/CacheDriver.php
new file mode 100644
index 0000000..642588d
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/CacheDriver.php
@@ -0,0 +1,230 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache;
+
+use Closure;
+use Psr\SimpleCache\CacheInterface;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
+
+abstract class CacheDriver implements CacheInterface
+{
+    /** @var Config */
+    protected $config;
+
+    /** @var mixed */
+    protected $store;
+
+    /** @var string */
+    protected $name = null;
+
+    public function __construct(Config $config = null, string $name = null)
+    {
+        $this->config = $config ?? Config::instance();
+        $this->name = $name ?? get_called_class();
+    }
+
+    /**
+     * @param string             $key
+     * @param null|Closure|mixed $default
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return mixed
+     */
+    abstract public function get($key, $default = null);
+
+    /**
+     * @param iterable           $keys
+     * @param null|Closure|mixed $default
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return iterable
+     */
+    abstract public function getMultiple($keys, $default = null);
+
+    /**
+     * @param string         $key
+     * @param mixed          $value
+     * @param null|int|mixed $ttl
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return bool
+     */
+    abstract public function set($key, $value, $ttl = null);
+
+    /**
+     * @param iterable       $values
+     * @param null|int|mixed $ttl
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return bool
+     */
+    abstract  public function setMultiple($values, $ttl = null);
+
+    /**
+     * @param string $key
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return bool
+     */
+    abstract public function delete($key);
+
+    /**
+     * @param iterable $keys
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return bool
+     */
+    abstract public function deleteMultiple($keys);
+
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     *
+     * @throws CacheInvalidArgumentException
+     */
+    abstract public function has($key);
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    abstract public function expired($key): bool;
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function notExpired($key)
+    {
+        return $this->expired($key) === false;
+    }
+
+    /**
+     * @return bool
+     */
+    abstract public function empty(): bool;
+
+    /**
+     * @return bool
+     */
+    public function notEmpty(): bool
+    {
+        return $this->empty() === false;
+    }
+
+    /**
+     * @return string
+     */
+    public function name(): string
+    {
+        return $this->name;
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function canGet($key): bool
+    {
+        return $this->isValidKey($key) && $this->has($key) && $this->notExpired($key);
+    }
+
+    /**
+     * @param string         $key
+     * @param mixed          $value
+     * @param null|int|mixed $ttl
+     *
+     * @return bool
+     */
+    public function canSet($key, $value, $ttl = null): bool
+    {
+        return $this->isValidKey($key) && $this->isValidValue($value) && $this->isValidTtl($ttl);
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function canDelete($key): bool
+    {
+        return $this->isValidKey($key) && $this->has($key);
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    protected function isValidKey($key): bool
+    {
+        return is_string($key);
+    }
+
+    /**
+     * @param mixed $value
+     *
+     * @return bool
+     */
+    protected function isValidValue($value): bool
+    {
+        return $value === $value;
+    }
+
+    /**
+     * @param null|int|mixed $ttl
+     *
+     * @return bool
+     */
+    protected function isValidTtl($ttl): bool
+    {
+        return is_null($ttl) || is_int($ttl);
+    }
+
+    /**
+     * @param mixed $key
+     *
+     * @throws CacheInvalidArgumentException
+     *
+     * @return string
+     */
+    protected function validateKey($key)
+    {
+        if ($this->isValidKey($key)) {
+            return $key;
+        }
+
+        throw new CacheInvalidArgumentException("'{$this->coerceToString($key)}' is not a valid key.");
+    }
+
+
+    /**
+     * @param mixed $value
+     *
+     * @return string
+     */
+    protected function coerceToString($value): string
+    {
+        if (is_object($value)) {
+            $value = get_class($value);
+        }
+
+        if (is_array($value)) {
+            $value = 'array';
+        }
+
+        return (string) $value;
+    }
+}
diff --git a/src/WmiScripting/Support/Cache/CacheProvider.php b/src/WmiScripting/Support/Cache/CacheProvider.php
new file mode 100644
index 0000000..3792073
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/CacheProvider.php
@@ -0,0 +1,316 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache;
+
+use Closure;
+use Psr\SimpleCache\CacheInterface;
+use Psr\SimpleCache\InvalidArgumentException;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Events\EventProvider;
+use function PhpWinTools\WmiScripting\Support\reduce_value;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheHit;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheMissed;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheCleared;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyStored;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyDeleted;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyExpired;
+use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
+
+class CacheProvider implements CacheInterface
+{
+    /** @var Config */
+    protected $config;
+
+    /** @var CacheDriver */
+    protected $driver;
+
+    /** @var EventProvider */
+    protected $events;
+
+    /** @var array|Event[] */
+    protected $delayedEvents = [];
+
+    public function __construct(Config $config = null, CacheDriver $driver = null, EventProvider $events = null)
+    {
+        $this->config = $config ?? Config::instance();
+        $this->driver = $driver ?? $this->config->getCacheDriver();
+        $this->events = $events ?? $this->config->eventProvider();
+    }
+
+    /**
+     * @param string|array       $key
+     * @param Closure|null|mixed $default
+     *
+     * @throws CacheInvalidArgumentException|InvalidArgumentException
+     *
+     * @return iterable|mixed|null
+     */
+    public function get($key, $default = null)
+    {
+        if (is_array($key)) {
+            return $this->getMultiple($key, $default);
+        }
+
+        $value = $this->driver()->get($key, null);
+
+        if ($this->driver()->expired($key)) {
+            $this->removeExpired($key, $value);
+            $value = null;
+        }
+
+        if (is_null($value)) {
+            $value = reduce_value($default);
+            $this->fire(new CacheMissed($this->driver(), $key));
+        } else {
+            $this->fire(new CacheHit($this->driver(), $key, $value));
+        }
+
+        return $value;
+    }
+
+    /**
+     * Accepts either a string or a key-value pair array. If given an array then the second value is used as the
+     * ttl, unless $ttl is explicitly set.
+     *
+     * @param string|array $key
+     * @param mixed        $value
+     * @param null|int     $ttl
+     *
+     * @throws CacheInvalidArgumentException|InvalidArgumentException
+     *
+     * @return bool
+     */
+    public function set($key, $value = null, $ttl = null)
+    {
+        if (is_array($key)) {
+            return $this->setMultiple($key, $ttl ?? $value);
+        }
+
+        $result = $this->driver()->set($key, $value, $ttl);
+
+        if ($result) {
+            $this->fire(new CacheKeyStored($this->driver(), $key, $value, $ttl));
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param string|array $key
+     *
+     * @throws CacheInvalidArgumentException|InvalidArgumentException
+     *
+     * @return bool
+     */
+    public function delete($key)
+    {
+        if (is_array($key)) {
+            return $this->deleteMultiple($key);
+        }
+
+        $result = $this->driver()->delete($key);
+
+        if ($result) {
+            $this->fire(new CacheKeyDeleted($this->driver(), $key));
+        }
+
+        return $result;
+    }
+
+    /**
+     * @return bool
+     */
+    public function clear()
+    {
+        $result = $this->driver->clear();
+
+        if ($result) {
+            $this->fire(new CacheCleared($this->driver));
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param iterable $keys
+     * @param null     $default
+     *
+     * @throws InvalidArgumentException|CacheInvalidArgumentException
+     *
+     * @return iterable
+     */
+    public function getMultiple($keys, $default = null)
+    {
+        $hit = [];
+
+        foreach ($keys as $key) {
+            if ($this->driver()->expired($key)) {
+                $this->removeExpired($key);
+            }
+
+            if ($this->driver()->canGet($key)) {
+                $hit[$key] = $key;
+            }
+        }
+
+        $results = $this->driver()->getMultiple($keys, $default);
+
+        foreach ($results as $key => $value) {
+            if (array_key_exists($key, $hit)) {
+                $this->fire(new CacheHit($this->driver(), $key, $value));
+            } else {
+                $this->fire(new CacheMissed($this->driver(), $key));
+            }
+        }
+
+        return $results;
+    }
+
+    /**
+     * @param iterable $values
+     * @param null     $ttl
+     *
+     * @return bool
+     */
+    public function setMultiple($values, $ttl = null)
+    {
+        return $this->multiple('set', $values, $ttl) ? $this->driver()->setMultiple($values, $ttl) : false;
+    }
+
+    /**
+     * @param iterable $keys
+     *
+     * @return bool
+     */
+    public function deleteMultiple($keys)
+    {
+        return $this->multiple('delete', $keys) ? $this->driver()->deleteMultiple($keys) : false;
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return bool
+     */
+    public function has($key)
+    {
+        return $this->driver()->has($key);
+    }
+
+    /**
+     * @return bool
+     */
+    public function empty(): bool
+    {
+        return $this->driver()->empty();
+    }
+
+    /**
+     * @return bool
+     */
+    public function notEmpty(): bool
+    {
+        return $this->driver()->notEmpty();
+    }
+
+    /**
+     * @return CacheDriver
+     */
+    public function driver()
+    {
+        return $this->driver;
+    }
+
+    /**
+     * @param string         $method
+     * @param iterable       $keys
+     * @param null|int|mixed $ttl
+     *
+     * @return bool
+     */
+    protected function multiple(string $method, $keys, $ttl = null): bool
+    {
+        foreach ($keys as $key => $value) {
+            $result = false;
+
+            if ($method === 'set' && $result = $this->driver()->canSet($key, $value)) {
+                $this->delayedFire(new CacheKeyStored($this->driver, $key, $value, $ttl));
+            }
+
+            if ($method === 'delete' && $result = $this->driver()->canDelete($value)) {
+                $this->delayedFire(new CacheKeyDeleted($this->driver, $value));
+            }
+
+            if ($result === false) {
+                $this->flushDelayedEvents(false);
+                return false;
+            }
+        }
+
+        $this->flushDelayedEvents();
+
+        return true;
+    }
+
+    /**
+     * @param      $key
+     * @param null $value
+     *
+     * @throws CacheInvalidArgumentException|InvalidArgumentException
+     *
+     * @return self
+     */
+    protected function removeExpired($key, $value = null): self
+    {
+        if ($this->driver()->expired($key)) {
+            $this->fire(new CacheKeyExpired($this->driver(), $key, $value));
+            $this->delete($key);
+        }
+
+
+        return $this;
+    }
+
+    /**
+     * @param Event $event
+     *
+     * @return self
+     */
+    protected function fire(Event $event): self
+    {
+        $this->events->fire($event);
+
+        return $this;
+    }
+
+    /**
+     * @param bool $fire
+     *
+     * @return self
+     */
+    protected function flushDelayedEvents(bool $fire = true): self
+    {
+        if ($fire) {
+            array_map(function (Event $event) {
+                $this->fire($event);
+            }, $this->delayedEvents);
+        }
+
+        $this->delayedEvents = [];
+
+        return $this;
+    }
+
+    /**
+     * @param Event $event
+     *
+     * @return $this
+     */
+    protected function delayedFire(Event $event): self
+    {
+        $this->delayedEvents[] = $event;
+
+        return $this;
+    }
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheCleared.php b/src/WmiScripting/Support/Cache/Events/CacheCleared.php
new file mode 100644
index 0000000..23ca6dc
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheCleared.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheCleared extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheEvent.php b/src/WmiScripting/Support/Cache/Events/CacheEvent.php
new file mode 100644
index 0000000..ab084d6
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheEvent.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Events\Payload;
+use PhpWinTools\WmiScripting\Support\Cache\CacheDriver;
+
+class CacheEvent extends Event
+{
+    public function __construct(CacheDriver $driver, $key = null, $value = null, $ttl = null)
+    {
+        parent::__construct(
+            (new Payload())
+                ->add('driver', $driver)
+                ->add('name', $driver->name())
+                ->add('key', $key)
+                ->add('value', $value)
+                ->add('ttl', $ttl)
+        );
+    }
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheHit.php b/src/WmiScripting/Support/Cache/Events/CacheHit.php
new file mode 100644
index 0000000..743d485
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheHit.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheHit extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheKeyDeleted.php b/src/WmiScripting/Support/Cache/Events/CacheKeyDeleted.php
new file mode 100644
index 0000000..c65bd66
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheKeyDeleted.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheKeyDeleted extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php b/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
new file mode 100644
index 0000000..20e45fa
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheKeyExpired.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheKeyExpired extends CacheKeyDeleted
+{
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheKeyStored.php b/src/WmiScripting/Support/Cache/Events/CacheKeyStored.php
new file mode 100644
index 0000000..a4f2a34
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheKeyStored.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheKeyStored extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Cache/Events/CacheMissed.php b/src/WmiScripting/Support/Cache/Events/CacheMissed.php
new file mode 100644
index 0000000..5261afb
--- /dev/null
+++ b/src/WmiScripting/Support/Cache/Events/CacheMissed.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Cache\Events;
+
+class CacheMissed extends CacheEvent
+{
+}
diff --git a/src/WmiScripting/Support/Events/Event.php b/src/WmiScripting/Support/Events/Event.php
new file mode 100644
index 0000000..d3c8f22
--- /dev/null
+++ b/src/WmiScripting/Support/Events/Event.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Events;
+
+class Event
+{
+    protected $payload;
+
+    protected $called_class;
+
+    public function __construct(Payload $payload)
+    {
+        $this->payload = $payload;
+        $this->called_class = get_called_class();
+    }
+
+    public static function new(Payload $payload)
+    {
+        return new static($payload);
+    }
+
+    public function payload(): Payload
+    {
+        return $this->payload;
+    }
+
+    public function actual(): string
+    {
+        return $this->called_class;
+    }
+}
diff --git a/src/WmiScripting/Support/Events/EventHistoryProvider.php b/src/WmiScripting/Support/Events/EventHistoryProvider.php
new file mode 100644
index 0000000..b1092b0
--- /dev/null
+++ b/src/WmiScripting/Support/Events/EventHistoryProvider.php
@@ -0,0 +1,220 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Events;
+
+use Closure;
+use Countable;
+use PhpWinTools\WmiScripting\Configuration\Config;
+
+class EventHistoryProvider implements Countable
+{
+    /** @var Config */
+    protected $config;
+
+    /** @var array|FiredEvent[] */
+    protected $fired_events = [];
+
+    /** @var array */
+    protected $event_cache = [
+        'actual'    => [],
+        'ancestors' => [],
+        'listeners' => [],
+    ];
+
+    public function __construct(Config $config = null)
+    {
+        $this->config = $config ?? Config::instance();
+    }
+
+
+    /**
+     * @param Event            $event
+     * @param array|Listener[] $listeners
+     *
+     * @return self
+     */
+    public function add(Event $event, array $listeners = []): self
+    {
+        if ($this->shouldNotTrack()) {
+            return $this;
+        }
+
+        $this->fired_events[] = new FiredEvent($event, $listeners);
+
+        return $this->cacheEvent($event, $listeners);
+    }
+
+    /**
+     * @return array|FiredEvent[]
+     */
+    public function all()
+    {
+        return $this->fired_events;
+    }
+
+    /**
+     * @param string              $event
+     * @param array|Closure|mixed $default
+     *
+     * @return array|FiredEvent[]|mixed
+     */
+    public function get(string $event, $default = [])
+    {
+        return $this->findEvents($this->getFiredEventKeys($event), $default);
+    }
+
+    /**
+     * @param string              $listener
+     * @param array|Closure|mixed $default
+     *
+     * @return array|FiredEvent[]|mixed
+     */
+    public function getFromListener(string $listener, $default = [])
+    {
+        return $this->findEvents($this->event_cache['listeners'][$listener] ?? [], $default);
+    }
+
+    /**
+     * @param string $event
+     *
+     * @return array|mixed
+     */
+    public function getFiredEventKeys(string $event)
+    {
+        $keys = [];
+
+        if ($this->wasFiredByName($event)) {
+            $keys = $this->event_cache['actual'][$event];
+        }
+
+        if ($this->wasFiredByDescendant($event)) {
+            $keys = array_merge($keys, $this->event_cache['ancestors'][$event]);
+        }
+
+        return $keys;
+    }
+
+    /**
+     * @return int
+     */
+    public function count(): int
+    {
+        return count($this->fired_events);
+    }
+
+    /**
+     * @param string|null $event
+     *
+     * @return int
+     */
+    public function eventCount(string $event = null): int
+    {
+        return is_null($event) ? $this->count() : count($this->get($event));
+    }
+
+    /**
+     * @return int
+     */
+    public function lastIndex(): int
+    {
+        return $this->count() - 1;
+    }
+
+    /**
+     * @param string $event
+     *
+     * @return bool
+     */
+    public function hasFired(string $event): bool
+    {
+        return $this->wasFiredByName($event) || $this->wasFiredByDescendant($event);
+    }
+
+    /**
+     * @param string $event
+     *
+     * @return bool
+     */
+    public function hasNotFired(string $event): bool
+    {
+        return $this->hasFired($event) === false;
+    }
+
+    /**
+     * @param string $event
+     *
+     * @return bool
+     */
+    public function wasFiredByName(string $event): bool
+    {
+        return array_key_exists($event, $this->event_cache['actual']);
+    }
+
+    /**
+     * @param string $event
+     *
+     * @return bool
+     */
+    public function wasFiredByDescendant(string $event): bool
+    {
+        return array_key_exists($event, $this->event_cache['ancestors']);
+    }
+
+    /**
+     * @param array               $event_keys
+     * @param array|Closure|mixed $default
+     *
+     * @return array|FiredEvent[]|mixed
+     */
+    protected function findEvents(array $event_keys = [], $default = [])
+    {
+        $events = array_values(array_map(function ($event_key) {
+            return $this->fired_events[$event_key];
+        }, $event_keys));
+
+        if (empty($events)) {
+            return is_callable($default) ? $default() : $default;
+        }
+
+        return $events;
+    }
+
+    /**
+     * @param Event            $event
+     * @param array|Listener[] $listeners
+     *
+     * @return self
+     */
+    protected function cacheEvent(Event $event, array $listeners = []): self
+    {
+        $this->event_cache['actual'][get_class($event)][] = $this->lastIndex();
+
+        return $this->cacheSet($event, 'ancestors', class_parents($event))->cacheSet($event, 'listeners', $listeners);
+    }
+
+    protected function cacheSet(Event $event, string $set_name, array $set)
+    {
+        array_map(function ($item) use ($set_name) {
+            $item = is_object($item) ? get_class($item) : $item;
+            $this->event_cache[$set_name][$item][] = $this->lastIndex();
+        }, $set);
+
+        return $this;
+    }
+
+    /**
+     * @return bool
+     */
+    protected function shouldTrack(): bool
+    {
+        return $this->config->shouldTrackEvents();
+    }
+
+    /**
+     * @return bool
+     */
+    protected function shouldNotTrack(): bool
+    {
+        return $this->shouldTrack() === false;
+    }
+}
diff --git a/src/WmiScripting/Support/Events/EventProvider.php b/src/WmiScripting/Support/Events/EventProvider.php
new file mode 100644
index 0000000..979bd37
--- /dev/null
+++ b/src/WmiScripting/Support/Events/EventProvider.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Events;
+
+use PhpWinTools\WmiScripting\Configuration\Config;
+use function PhpWinTools\WmiScripting\Support\resolve;
+
+class EventProvider
+{
+    /** @var self|null */
+    protected static $instance;
+
+    /** @var Config */
+    protected $config;
+
+    /** @var array|Listener[][] */
+    protected $listeners = [];
+
+    /** @var EventHistoryProvider */
+    protected $history;
+
+    public function __construct(Config $config = null)
+    {
+        $this->config = $config ?? Config::instance();
+
+        $this->history = resolve(EventHistoryProvider::class);
+
+        static::$instance = $this->config->registerProvider(EventProvider::class, $this);
+    }
+
+    public static function instance(Config $config = null)
+    {
+        if (static::$instance && $config) {
+            static::$instance->config = $config;
+
+            return static::$instance;
+        }
+
+        return new static($config);
+    }
+
+    public function subscribe($event, Listener $listener)
+    {
+        $this->listeners[$event][] = $listener;
+
+        return $this;
+    }
+
+    public function fire(Event $event): void
+    {
+        $listeners = array_merge(
+            $this->listeners[get_class($event)] ?? [],
+            $this->getAncestorListeners($event)
+        );
+
+        $this->history->add($event, $listeners);
+
+        array_map(function (Listener $listener) use ($event) {
+            $listener->react($event);
+        }, $listeners);
+    }
+
+    public function trackEvents()
+    {
+        $this->config->trackEvents();
+
+        return $this;
+    }
+
+    public function doNotTrackEvents()
+    {
+        $this->config->doNotTrackEvents();
+
+        return $this;
+    }
+
+    public function history()
+    {
+        return $this->history;
+    }
+
+    protected function getAncestorListeners(Event $event)
+    {
+        $ancestors = class_parents($event);
+        $listeners = [];
+
+        foreach ($ancestors as $ancestor) {
+            if (array_key_exists($ancestor, $this->listeners)) {
+                $listeners = array_merge($listeners, $this->listeners[$ancestor]);
+            }
+        }
+
+        return $listeners;
+    }
+}
diff --git a/src/WmiScripting/Support/Events/FiredEvent.php b/src/WmiScripting/Support/Events/FiredEvent.php
new file mode 100644
index 0000000..5964954
--- /dev/null
+++ b/src/WmiScripting/Support/Events/FiredEvent.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Events;
+
+class FiredEvent
+{
+    protected $event;
+
+    /** @var array|Listener[] */
+    protected $listeners = [];
+
+    public function __construct(Event $event, array $listeners = [])
+    {
+        $this->event = $event;
+        $this->listeners = $listeners;
+    }
+
+    public function event()
+    {
+        return $this->event;
+    }
+
+    public function eventType()
+    {
+        return get_class($this->event());
+    }
+
+    public function listeners()
+    {
+        return $this->listeners;
+    }
+
+    public function listenerTypes()
+    {
+        return array_map(function (Listener $listener) {
+            return get_class($listener);
+        }, $this->listeners());
+    }
+}
diff --git a/src/WmiScripting/Support/Events/Listener.php b/src/WmiScripting/Support/Events/Listener.php
new file mode 100644
index 0000000..407ce5a
--- /dev/null
+++ b/src/WmiScripting/Support/Events/Listener.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Events;
+
+abstract class Listener
+{
+    abstract public function react(Event $event);
+}
diff --git a/src/WmiScripting/Support/Events/Payload.php b/src/WmiScripting/Support/Events/Payload.php
new file mode 100644
index 0000000..d40c6b2
--- /dev/null
+++ b/src/WmiScripting/Support/Events/Payload.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace PhpWinTools\WmiScripting\Support\Events;
+
+class Payload
+{
+    protected $context = [];
+
+    public function add($key, $value)
+    {
+        $this->context[$key] = $value;
+
+        return $this;
+    }
+
+    public function get($key, $default = null)
+    {
+        if ($this->doesNotExist($key)) {
+            return $default;
+        }
+
+        return $this->context[$key] ?? $default;
+    }
+
+    public function exists($key)
+    {
+        return array_key_exists($key, $this->context);
+    }
+
+    public function doesNotExist($key)
+    {
+        return $this->exists($key) === false;
+    }
+}
diff --git a/src/WmiScripting/Support/functions.php b/src/WmiScripting/Support/functions.php
index ce44b01..0eb54c4 100644
--- a/src/WmiScripting/Support/functions.php
+++ b/src/WmiScripting/Support/functions.php
@@ -2,6 +2,7 @@
 
 namespace PhpWinTools\WmiScripting\Support {
 
+    use Closure;
     use ReflectionClass;
     use PhpWinTools\WmiScripting\Configuration\Config;
     use PhpWinTools\WmiScripting\Configuration\Resolver;
@@ -11,13 +12,63 @@
     use PhpWinTools\WmiScripting\Exceptions\InvalidConnectionException;
 
     /**
-     * @param Config|null $config
+     * Checks for the existence of a property.
      *
-     * @return Config
+     * @param $class
+     * @param $property_name
+     *
+     * @throws InvalidArgumentException
+     *
+     * @return bool
      */
-    function core(Config $config = null)
+    function class_has_property($class, $property_name): bool
     {
-        return $config ?? Config::instance();
+        $class = is_object($class) ? get_class($class) : $class;
+
+        try {
+            return (new ReflectionClass($class))->hasProperty($property_name);
+        } catch (\Exception $exception) {
+            throw new InvalidArgumentException("Unable to reflect on {$class}.", null, $exception);
+        }
+    }
+
+    /**
+     * Expects either a class name or instance and will return if the given trait exists on the class.
+     *
+     * @param $class
+     * @param $trait
+     *
+     * @return bool
+     */
+    function class_has_trait($class, $trait): bool
+    {
+        return array_key_exists($trait, class_traits(is_object($class) ? get_class($class) : $class));
+    }
+
+    /**
+     * Returns all traits of the given class or instance. It also checks the traits for any used traits.
+     *
+     * @param $class
+     *
+     * @return array
+     */
+    function class_traits($class)
+    {
+        $class = is_object($class) ? get_class($class) : $class;
+
+        $traits = class_uses($class);
+
+        while ($class = get_parent_class($class)) {
+            $traits += class_uses($class);
+        }
+
+        $trait_traits = [];
+
+        foreach ($traits as $trait) {
+            $trait_traits += trait_traits($trait);
+        }
+
+        return array_merge($trait_traits, $traits);
     }
 
     /**
@@ -49,35 +100,13 @@ function connection($connection = null, $default = null, Config $config = null)
     }
 
     /**
-     * @param string|null $class
-     * @param mixed       $parameters
-     *
-     * @return Resolver|mixed|null
-     */
-    function resolve(string $class = null, ...$parameters)
-    {
-        return core()($class, $parameters);
-    }
-
-    /**
-     * Checks for the existence of a property.
-     *
-     * @param $class
-     * @param $property_name
-     *
-     * @throws InvalidArgumentException
+     * @param Config|null $config
      *
-     * @return bool
+     * @return Config
      */
-    function class_has_property($class, $property_name): bool
+    function core(Config $config = null)
     {
-        $class = is_object($class) ? get_class($class) : $class;
-
-        try {
-            return (new ReflectionClass($class))->hasProperty($property_name);
-        } catch (\Exception $exception) {
-            throw new InvalidConnectionException("Unable to reflect on {$class}.", null, $exception);
-        }
+        return $config ?? Config::instance();
     }
 
     /**
@@ -98,44 +127,81 @@ function get_ancestor_property($class, $property_name)
     }
 
     /**
-     * Expects either a class name or instance and will return if the given trait exists on the class.
+     * Simple null check and replace only when $value is not null. Created to make string concatenations
+     * a little cleaner to avoid having to have an extra variable.
      *
-     * @param $class
-     * @param $trait
+     * @param mixed      $value
+     * @param mixed      $then
+     * @param mixed|null $else
      *
-     * @return bool
+     * @return mixed
      */
-    function class_has_trait($class, $trait): bool
+    function if_not_null($value, $then, $else = null)
     {
-        return array_key_exists($trait, class_traits(is_object($class) ? get_class($class) : $class));
+        return !is_null($value) ? $then : $else;
     }
 
     /**
-     * Returns all traits of the given class or instance. It also checks the traits for any used traits.
+     * @param string       $class
+     * @param mixed|object $instance
      *
-     * @param $class
-     *
-     * @return array
+     * @return bool
      */
-    function class_traits($class)
+    function is(string $class, $instance): bool
     {
-        $class = is_object($class) ? get_class($class) : $class;
+        return class_exists($class) && $instance instanceof $class;
+    }
 
-        $traits = class_uses($class);
+    function is_closure($instance): bool
+    {
+        return is(Closure::class, $instance);
+    }
 
-        while ($class = get_parent_class($class)) {
-            $traits += class_uses($class);
-        }
+    /**
+     * @param string       $class
+     * @param mixed|object $instance
+     *
+     * @return bool
+     */
+    function is_not(string $class, $instance): bool
+    {
+        return is($class, $instance) === false;
+    }
 
-        $trait_traits = [];
+    /**
+     * @param $instance
+     *
+     * @return bool
+     */
+    function is_not_closure($instance): bool
+    {
+        return is_closure($instance) === false;
+    }
 
-        foreach ($traits as $trait) {
-            $trait_traits += trait_traits($trait);
-        }
+    /**
+     * Reduce value if given a Closure otherwise return value as is.
+     *
+     * @param $value
+     *
+     * @return mixed
+     */
+    function reduce_value($value)
+    {
+        return is_closure($value) ? $value() : $value;
+    }
 
-        return array_merge($trait_traits, $traits);
+    /**
+     * @param string|null $class
+     * @param mixed       $parameters
+     *
+     * @return Resolver|mixed|null
+     */
+    function resolve(string $class = null, ...$parameters)
+    {
+        return core()($class, $parameters);
     }
 
+
     /**
      * Returns all of the traits that a trait uses including it's ancestors.
      *
diff --git a/src/WmiScripting/config/bootstrap.php b/src/WmiScripting/config/bootstrap.php
index e1517c0..b1293d8 100644
--- a/src/WmiScripting/config/bootstrap.php
+++ b/src/WmiScripting/config/bootstrap.php
@@ -3,8 +3,15 @@
 use PhpWinTools\Support\COM\ComWrapper;
 use PhpWinTools\Support\COM\VariantWrapper;
 use PhpWinTools\Support\COM\ComVariantWrapper;
+use PhpWinTools\WmiScripting\Connections\ComConnection;
+use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
 
 return [
+    'cache' => [
+        'driver' => ArrayDriver::class,
+        'ttl' => 60 * 5,
+    ],
+
     'com' => [
         'com_class'                 => COM::class,
         'variant_class'             => VARIANT::class,
@@ -13,5 +20,15 @@
         VariantWrapper::class       => VariantWrapper::class,
     ],
 
+    'connection' => [
+        'default_driver' => ComConnection::class,
+    ],
+
+    'event' => [
+        'track'  => false,
+    ],
+
+    'providers' => include('providers.php'),
+
     'wmi' => include('wmi.php'),
 ];
diff --git a/src/WmiScripting/config/connections.php b/src/WmiScripting/config/connections.php
index 136479c..7f45a4c 100644
--- a/src/WmiScripting/config/connections.php
+++ b/src/WmiScripting/config/connections.php
@@ -1,10 +1,13 @@
 <?php
 
+use PhpWinTools\WmiScripting\Connections\ComConnection;
+
 return [
     'default' => 'local',
 
     'servers' => [
         'local' => [
+            'type'            => ComConnection::class,
             'server'          => '.',
             'namespace'       => 'Root\CIMv2',
             'user'            => null,
diff --git a/src/WmiScripting/config/providers.php b/src/WmiScripting/config/providers.php
new file mode 100644
index 0000000..84f97a5
--- /dev/null
+++ b/src/WmiScripting/config/providers.php
@@ -0,0 +1,25 @@
+<?php
+
+use PhpWinTools\WmiScripting\Connections\ConnectionManager;
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Cache\CacheProvider;
+use PhpWinTools\WmiScripting\Support\Events\EventProvider;
+use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
+
+return [
+    'concrete' => [
+        'bus'           => CommandBus::class,
+        'cache'         => CacheProvider::class,
+        'connection'    => ConnectionManager::class,
+        'event'         => EventProvider::class,
+        'event_history' => EventHistoryProvider::class,
+    ],
+
+    'aliases' => [
+        CommandBus::class           => 'bus',
+        CacheProvider::class        => 'cache',
+        ConnectionManager::class    => 'connection',
+        EventProvider::class        => 'event',
+        EventHistoryProvider::class => 'event_history',
+    ],
+];
diff --git a/tests/WmiScripting/BuilderTest.php b/tests/WmiScripting/BuilderTest.php
new file mode 100644
index 0000000..740736f
--- /dev/null
+++ b/tests/WmiScripting/BuilderTest.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Tests\WmiScripting;
+
+use Tests\TestCase;
+
+class BuilderTest extends TestCase
+{
+    /** @test */
+    public function it_does_stuff()
+    {
+        $this->assertTrue(true);
+    }
+}
diff --git a/tests/WmiScripting/Integration/ScriptingTest.php b/tests/WmiScripting/Integration/ScriptingTest.php
index 2a86d4c..3c91e21 100644
--- a/tests/WmiScripting/Integration/ScriptingTest.php
+++ b/tests/WmiScripting/Integration/ScriptingTest.php
@@ -30,5 +30,7 @@ public function it_can_actually_return_a_logical_disk()
         $this->assertInstanceOf(LogicalDisk::class, $disks->first(function (LogicalDisk $disk) {
             return $disk->getAttribute('deviceID') === 'C:';
         }));
+
+//        dd($disks->first()->toArray());
     }
 }
diff --git a/tests/WmiScripting/Support/Bus/CommandBusEventTest.php b/tests/WmiScripting/Support/Bus/CommandBusEventTest.php
new file mode 100644
index 0000000..37a5f8d
--- /dev/null
+++ b/tests/WmiScripting/Support/Bus/CommandBusEventTest.php
@@ -0,0 +1,197 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Bus;
+
+use Closure;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PostMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PreMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandHandlerStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\PostMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\FailureMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\SuccessMiddlewareEnded;
+use PhpWinTools\WmiScripting\Support\Bus\Events\FailureMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Events\SuccessMiddlewareStarted;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
+
+class CommandBusEventTest extends CommandBusTest
+{
+    /** @test */
+    public function it_fires_events_before_and_after_pre_middleware_but_before_command()
+    {
+        $command = $this->makeCommand();
+
+        $middleware = new class extends PreCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'middleware';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler())
+            ->registerMiddleware(get_class($middleware), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(PreMiddlewareStarted::class, $this->makeListener('pre_started', $command))
+            ->subscribe(PreMiddlewareEnded::class, $this->makeListener('pre_ended', $command));
+
+        $this->assertSame(['pre_started', 'middleware', 'pre_ended', 'command'], $this->bus->handle($command)->order);
+    }
+
+    /** @test */
+    public function it_fires_events_before_and_after_command_handle_but_before_post_middleware()
+    {
+        $command = $this->makeCommand();
+
+        $pre = new class extends PreCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'pre';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $post = new class extends PostCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'post';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler())
+            ->registerMiddleware(get_class($post), get_class($command))
+            ->registerMiddleware(get_class($pre), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(CommandHandlerStarted::class, $this->makeListener('command_started', $command))
+            ->subscribe(CommandHandlerEnded::class, $this->makeListener('command_ended', $command));
+
+        $this->assertSame(
+            ['pre', 'command_started', 'command', 'command_ended', 'post'], $this->bus->handle($command)->order
+        );
+    }
+
+    /** @test */
+    public function it_fires_events_before_and_after_post_middleware_but_before_success_middleware()
+    {
+        $command = $this->makeCommand();
+
+        $post = new class extends PostCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'post';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $success = new class extends SuccessCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'success';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler())
+            ->registerMiddleware(get_class($post), get_class($command))
+            ->registerMiddleware(get_class($success), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(PostMiddlewareStarted::class, $this->makeListener('post_started', $command))
+            ->subscribe(PostMiddlewareEnded::class, $this->makeListener('post_ended', $command));
+
+        $this->assertSame(
+            ['command', 'post_started', 'post', 'post_ended', 'success'], $this->bus->handle($command)->order
+        );
+    }
+
+    /** @test */
+    public function it_fires_events_before_and_after_post_middleware_but_before_failure_middleware()
+    {
+        $command = $this->makeCommand();
+
+        $post = new class extends PostCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'post';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $success = new class extends FailureCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'failure';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler('failure', true))
+            ->registerMiddleware(get_class($post), get_class($command))
+            ->registerMiddleware(get_class($success), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(PostMiddlewareStarted::class, $this->makeListener('post_started', $command))
+            ->subscribe(PostMiddlewareEnded::class, $this->makeListener('post_ended', $command));
+
+        $this->assertSame(
+            ['command', 'post_started', 'post', 'post_ended', 'failure'], $this->bus->handle($command)->order
+        );
+    }
+
+    /** @test */
+    public function it_fires_events_before_and_after_failure_middleware()
+    {
+        $command = $this->makeCommand();
+
+        $success = new class extends FailureCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'failure';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler('failure', true))
+            ->registerMiddleware(get_class($success), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(FailureMiddlewareStarted::class, $this->makeListener('failure_started', $command))
+            ->subscribe(FailureMiddlewareEnded::class, $this->makeListener('failure_ended', $command));
+
+        $this->assertSame(
+            ['command', 'failure_started', 'failure', 'failure_ended'], $this->bus->handle($command)->order
+        );
+    }
+
+    /** @test */
+    public function it_fires_events_before_and_after_success_middleware()
+    {
+        $command = $this->makeCommand();
+
+        $success = new class extends SuccessCommandMiddleware {
+            public function handle($command, Closure $next)
+            {
+                $command->order[] = 'success';
+                return parent::handle($command, $next);
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $this->makeCommandHandler())
+            ->registerMiddleware(get_class($success), get_class($command));
+
+        $this->config->eventProvider()
+            ->subscribe(SuccessMiddlewareStarted::class, $this->makeListener('success_started', $command))
+            ->subscribe(SuccessMiddlewareEnded::class, $this->makeListener('success_ended', $command));
+
+        $this->assertSame(
+            ['command', 'success_started', 'success', 'success_ended'], $this->bus->handle($command)->order
+        );
+    }
+}
diff --git a/tests/WmiScripting/Support/Bus/CommandBusMiddlewareTest.php b/tests/WmiScripting/Support/Bus/CommandBusMiddlewareTest.php
new file mode 100644
index 0000000..583c062
--- /dev/null
+++ b/tests/WmiScripting/Support/Bus/CommandBusMiddlewareTest.php
@@ -0,0 +1,188 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Bus;
+
+use Closure;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PreCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\PostCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\FailureCommandMiddleware;
+use PhpWinTools\WmiScripting\Support\Bus\Middleware\SuccessCommandMiddleware;
+
+class CommandBusMiddlewareTest extends CommandBusTest
+{
+    /** @test */
+    public function it_can_accept_a_closure_as_pre_middleware()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
+
+        $middleware = new class extends PreCommandMiddleware {
+            public $name = 'middleware';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $closure = function ($subject, Closure $next) {
+            $subject->order[] = 'closure';
+            return $next($subject);
+        };
+
+        $this->bus->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($middleware), get_class($command))
+            ->registerMiddleware($closure, get_class($command), PreCommandMiddleware::class);
+
+        $this->assertSame(['middleware', 'closure', 'command'], $this->bus->handle($command)->order);
+    }
+
+    /** @test */
+    public function it_can_accept_a_closure_as_post_middleware()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
+
+        $middleware = new class extends PreCommandMiddleware {
+            public $name = 'middleware';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $closure = function ($subject, Closure $next) {
+            $subject->order[] = 'closure';
+            return $next($subject);
+        };
+
+        $this->bus->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($middleware), get_class($command))
+            ->registerMiddleware($closure, get_class($command), PostCommandMiddleware::class);
+
+        $this->assertSame(['middleware', 'command', 'closure'], $this->bus->handle($command)->order);
+    }
+
+    /** @test */
+    public function it_can_accept_a_closure_as_success_middleware()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
+
+        $pre = new class extends PreCommandMiddleware {
+            public $name = 'pre';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $post = new class extends PostCommandMiddleware {
+            public $name = 'post';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $closure = function ($subject, Closure $next) {
+            $subject->order[] = 'closure';
+            return $next($subject);
+        };
+
+        $this->bus->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($post), get_class($command))
+            ->registerMiddleware($closure, get_class($command), SuccessCommandMiddleware::class)
+            ->registerMiddleware(get_class($pre), get_class($command));
+
+        $this->assertSame(['pre', 'command', 'post', 'closure'], $this->bus->handle($command)->order);
+    }
+
+    /** @test */
+    public function it_can_accept_a_closure_as_failure_middleware()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler('failed', true);
+
+        $pre = new class extends PreCommandMiddleware {
+            public $name = 'pre';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $post = new class extends PostCommandMiddleware {
+            public $name = 'post';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $success = function ($subject, Closure $next) {
+            $subject->order[] = 'success';
+            return $next($subject);
+        };
+
+        $failure = function ($subject, Closure $next) {
+            $subject->order[] = 'failure';
+            return $next($subject);
+        };
+
+        $this->bus->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($post), get_class($command))
+            ->registerMiddleware($failure, get_class($command), FailureCommandMiddleware::class)
+            ->registerMiddleware($success, get_class($command), SuccessCommandMiddleware::class)
+            ->registerMiddleware(get_class($pre), get_class($command));
+
+        $this->assertSame(['pre', 'command', 'post', 'failure'], $this->bus->handle($command)->order);
+    }
+
+    /** @test */
+    public function it_runs_pre_middleware_before_the_handler()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
+
+        $first_middleware = new class extends PreCommandMiddleware {
+            public $name = 'first';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $second_middleware = new class extends PreCommandMiddleware {
+            public $name = 'second';
+            public function handle($subject, Closure $next)
+            {
+                $subject->order[] = $this->name;
+                return parent::handle($subject, $next);
+            }
+        };
+
+        $after_middleware = new class extends PreCommandMiddleware {
+            public $name = 'first after';
+            public function handle($subject, Closure $next)
+            {
+                $result = parent::handle($subject, $next);
+                $subject->order[] = $this->name;
+                return $result;
+            }
+        };
+
+        $this->bus->assignHandler(get_class($command), $handler)
+            ->registerMiddleware(get_class($after_middleware), get_class($command))
+            ->registerMiddleware(get_class($first_middleware), get_class($command))
+            ->registerMiddleware(get_class($second_middleware), get_class($command));
+
+        $this->assertEquals(['first', 'second', 'first after', 'command'], $this->bus->handle($command)->order);
+    }
+}
diff --git a/tests/WmiScripting/Support/Bus/CommandBusTest.php b/tests/WmiScripting/Support/Bus/CommandBusTest.php
new file mode 100644
index 0000000..f3ed203
--- /dev/null
+++ b/tests/WmiScripting/Support/Bus/CommandBusTest.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Bus;
+
+use Tests\TestCase;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Bus\CommandBus;
+use PhpWinTools\WmiScripting\Support\Events\Listener;
+use PhpWinTools\WmiScripting\Support\Bus\CommandHandler;
+use PhpWinTools\WmiScripting\Support\Bus\Commands\Command;
+
+class CommandBusTest extends TestCase
+{
+    /** @var Config */
+    protected $config;
+
+    /** @var CommandBus */
+    protected $bus;
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->config = Config::newInstance();
+
+        $this->bus = $this->config->commandBus();
+    }
+
+    /** @test */
+    public function it_can_instantiate()
+    {
+        $config = new Config();
+        $bus = new CommandBus();
+
+        $this->assertInstanceOf(CommandBus::class, $bus);
+        $this->assertSame($config, $bus->getConfig());
+        $this->assertSame(CommandBus::instance(), $bus);
+    }
+
+    /** @test */
+    public function it_sends_commands_to_assigned_handler()
+    {
+        $command = $this->makeCommand();
+        $handler = $this->makeCommandHandler();
+
+        $this->bus->assignHandler(get_class($command), $handler);
+
+        $this->assertEmpty($command->order);
+        $this->assertSame(['command'], $this->bus->handle($command)->order);
+    }
+
+    protected function makeCommand()
+    {
+        return new class extends Command {
+            public $order = [];
+        };
+    }
+
+    protected function makeCommandHandler($failure_result = null, bool $trigger_failure = false)
+    {
+        return new class($failure_result, $trigger_failure) extends CommandHandler {
+            protected $failure_result;
+
+            protected $trigger_failure = false;
+
+            public function __construct($failure_result, $trigger_failure)
+            {
+                $this->failure_result = $failure_result;
+                $this->trigger_failure = $trigger_failure;
+            }
+
+            public function handle(Command $command)
+            {
+                $command->order[] = 'command';
+
+                if ($this->trigger_failure) {
+                    return $this->failure_result;
+                }
+
+                return $command;
+            }
+        };
+    }
+
+    protected function makeListener($name, $command)
+    {
+        return new class($command, $name) extends Listener {
+            public $command;
+
+            public $name;
+
+            public function __construct($command, $name)
+            {
+                $this->command = $command;
+                $this->name = $name;
+            }
+
+            public function react(Event $event)
+            {
+                $this->command->order[] = $this->name;
+            }
+        };
+    }
+}
diff --git a/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php b/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
new file mode 100644
index 0000000..58189da
--- /dev/null
+++ b/tests/WmiScripting/Support/Cache/ArrayCacheDriverTest.php
@@ -0,0 +1,161 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Cache;
+
+use Tests\TestCase;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Cache\ArrayDriver;
+use PhpWinTools\WmiScripting\Support\Cache\CacheProvider;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheHit;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheMissed;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheCleared;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyStored;
+use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyDeleted;
+use PhpWinTools\WmiScripting\Support\Cache\Events\CacheKeyExpired;
+use PhpWinTools\WmiScripting\Exceptions\CacheInvalidArgumentException;
+
+class ArrayCacheDriverTest extends TestCase
+{
+    /** @var CacheProvider */
+    protected $cache;
+
+    /** @var Config */
+    protected $config;
+
+    /** @var EventHistoryProvider  */
+    protected $eventHistory;
+
+    protected $cached_values = [
+        'key1' => 'value1',
+        'key2' => 'value2',
+        'key3' => 'value3',
+        'key4' => 'value4',
+    ];
+
+    protected $time_to_live_seconds = 60;
+
+    protected function setUp()
+    {
+        $this->config = Config::newInstance()->trackEvents();
+        $this->eventHistory = $this->config->eventHistoryProvider();
+        $this->cache = new CacheProvider($this->config, new ArrayDriver($this->config));
+        $this->cache->set($this->cached_values, $this->time_to_live_seconds);
+    }
+
+    /** @test */
+    public function it_uses_the_array_driver_when_specified()
+    {
+        $this->assertInstanceOf(ArrayDriver::class, $this->cache->driver());
+    }
+
+    /** @test */
+    public function it_fires_an_event_when_a_value_is_set_and_returns_true_when_successful()
+    {
+        $this->assertCount(4, $this->eventHistory->get(CacheKeyStored::class));
+        $this->assertTrue($this->cache->set('test', $value = 'value'));
+        $this->assertCount(5, $this->eventHistory->get(CacheKeyStored::class));
+        $this->assertTrue($this->cache->has('test'));
+        $this->assertSame($value, $this->cache->get('test'));
+    }
+
+    /** @test */
+    public function it_throws_an_exception_when_setting_an_invalid_key()
+    {
+        $this->expectException(CacheInvalidArgumentException::class);
+
+        $this->cache->set(new \stdClass(), 'test');
+    }
+
+    /** @test */
+    public function it_returns_false_when_unable_to_store_multiple_items()
+    {
+        $this->assertFalse($this->cache->setMultiple([1 => 'value']));
+    }
+
+    /** @test */
+    public function it_throws_an_exception_when_trying_to_get_an_invalid_key()
+    {
+        $this->expectException(CacheInvalidArgumentException::class);
+
+        $this->cache->get(new \stdClass());
+    }
+
+    /** @test */
+    public function it_sets_default_value_to_keys_that_do_not_exist_when_getting_multiple_and_fire_correct_events()
+    {
+        $results = $this->cache->get(['key2', 'not a key'], false);
+
+        $this->assertSame($this->cached_values['key2'], $results['key2']);
+        $this->assertFalse($results['not a key']);
+        $this->assertCount(1, $hit = $this->eventHistory->get(CacheHit::class));
+        $this->assertCount(1, $miss = $this->eventHistory->get(CacheMissed::class));
+        $this->assertSame($results['key2'], $hit[0]->event()->payload()->get('value'));
+        $this->assertSame('not a key', $miss[0]->event()->payload()->get('key'));
+    }
+
+    /** @test */
+    public function it_can_return_a_cached_value_and_fire_event()
+    {
+        $this->assertSame($this->cached_values['key3'], $this->cache->get('key3'));
+        $this->assertTrue($this->eventHistory->hasFired(CacheHit::class));
+    }
+
+    /** @test */
+    public function it_can_delete_an_item_and_fire_an_event()
+    {
+        $this->assertSame($this->cached_values['key2'], $this->cache->get('key2'));
+        $this->assertTrue($this->cache->delete('key2'));
+        $this->assertFalse($this->cache->get('key2', false));
+        $this->assertTrue($this->eventHistory->hasFired(CacheKeyDeleted::class));
+    }
+
+    /** @test */
+    public function it_can_delete_multiple_items_fire_events()
+    {
+        $this->assertTrue($this->cache->notEmpty());
+        $this->assertTrue($this->cache->delete(['key1', 'key2', 'key3', 'key4']));
+        $this->assertCount(4, $this->eventHistory->get(CacheKeyDeleted::class));
+        $this->assertTrue($this->cache->empty());
+    }
+
+    /** @test */
+    public function it_returns_false_when_trying_delete_an_item_that_doesnt_exist_or_is_invalid()
+    {
+        $this->assertFalse($this->cache->delete('not a key'));
+        $this->assertFalse($this->cache->driver()->deleteMultiple(['not a key']));
+        $this->assertFalse($this->cache->delete([new \stdClass()]));
+    }
+
+    /** @test */
+    public function it_can_get_multiple_values_and_fire_events()
+    {
+        $this->assertSame($this->cached_values, $this->cache->get(['key1', 'key2', 'key3', 'key4']));
+        $this->assertCount(4, $this->eventHistory->get(CacheHit::class));
+    }
+
+    /** @test */
+    public function it_can_clear_its_cache_and_fire_an_event()
+    {
+        $this->assertTrue($this->cache->notEmpty());
+        $this->assertTrue($this->cache->clear());
+        $this->assertTrue($this->cache->empty());
+        $this->assertTrue($this->eventHistory->hasFired(CacheCleared::class));
+    }
+
+    /** @test */
+    public function it_removes_expired_values_when_getting_multiple()
+    {
+        $this->cache->set('test', 'value', -30);
+        $this->assertFalse($this->cache->get(['test'], false)['test']);
+        $this->assertTrue($this->eventHistory->hasFired(CacheKeyExpired::class));
+    }
+
+    /** @test */
+    public function it_removes_an_expired_key_and_fires_an_event()
+    {
+        $this->assertTrue($this->cache->set('test', 'value', -30));
+        $this->assertFalse($this->cache->get('test', false));
+        $this->assertTrue($this->eventHistory->hasFired(CacheKeyExpired::class));
+    }
+}
diff --git a/tests/WmiScripting/Support/Connections/ConnectionTest.php b/tests/WmiScripting/Support/Connections/ConnectionTest.php
new file mode 100644
index 0000000..fc6dbde
--- /dev/null
+++ b/tests/WmiScripting/Support/Connections/ConnectionTest.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Connections;
+
+use Tests\TestCase;
+
+class ConnectionTest extends TestCase
+{
+    /** @test */
+    public function it_can_instantiate_a_com_connection_from_configuration()
+    {
+        $this->assertTrue(true);
+    }
+}
diff --git a/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
new file mode 100644
index 0000000..4dd37f9
--- /dev/null
+++ b/tests/WmiScripting/Support/Event/EventHistoryProviderTest.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Event;
+
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Events\Payload;
+use PhpWinTools\WmiScripting\Support\Events\Listener;
+use PhpWinTools\WmiScripting\Support\Bus\Events\CommandBusEvent;
+
+class EventHistoryProviderTest extends EventProviderTest
+{
+    /** @test */
+    public function it_does_not_track_events_by_default()
+    {
+        $this->event->fire(new Event(new Payload()));
+
+        $this->assertEmpty($this->history->all());
+    }
+
+    /** @test */
+    public function it_can_track_events()
+    {
+        $notFired = new class(new Payload()) extends Event {};
+        $this->event->trackEvents()->fire(new Event(new Payload()));
+
+        $this->assertTrue($this->history->hasFired(Event::class));
+        $this->assertTrue($this->history->hasNotFired(get_class($notFired)));
+    }
+
+    /** @test */
+    public function it_can_count_events()
+    {
+        $this->event->trackEvents();
+        $child = new class(new Payload()) extends Event {};
+
+        $this->event->fire($child);
+        $this->event->fire(new Event(new Payload()));
+        $this->event->fire(new Event(new Payload()));
+
+        $this->assertEquals(3, $this->history->count());
+        $this->assertEquals(3, $this->history->eventCount());
+        $this->assertEquals(3, $this->history->eventCount(Event::class));
+        $this->assertEquals(1, $this->history->eventCount(get_class($child)));
+    }
+
+    /** @test */
+    public function it_can_count_events_through_ancestry()
+    {
+        $this->event->trackEvents();
+
+        $child = new class(new Payload()) extends Event {};
+
+        $this->event->fire($child);
+        $this->event->fire(Event::new(new Payload()));
+
+        $this->assertEquals(1, $this->history->eventCount(get_class($child)));
+        $this->assertEquals(2, $this->history->eventCount(Event::class));
+    }
+
+    /** @test */
+    public function it_can_count_events_through_ancestry_without_over_counting()
+    {
+        $this->event->trackEvents();
+
+        $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
+        $notFired = new class($bus = Config::instance()->commandBus(), 'nothing') extends CommandBusEvent {};
+
+        $this->event->fire(Event::new(new Payload()));
+        $this->event->fire($child);
+        $this->event->fire(new CommandBusEvent($bus, 'new test'));
+
+        $this->assertEquals(3, $this->history->eventCount(Event::class));
+        $this->assertEquals(2, $this->history->eventCount(CommandBusEvent::class));
+        $this->assertEquals(1, $this->history->eventCount(get_class($child)));
+        $this->assertEquals(0, $this->history->eventCount(get_class($notFired)));
+    }
+
+    /** @test */
+    public function it_returns_events_in_the_order_that_they_were_fired()
+    {
+        $this->event->trackEvents();
+
+        $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
+
+        $this->event->fire($first = Event::new(new Payload()));
+        $this->event->fire($second = Event::new(new Payload()));
+        $this->event->fire($child);
+        $this->event->fire($forth = new CommandBusEvent($bus, 'new test'));
+
+
+        $this->assertSame($first, $this->history->all()[0]->event());
+        $this->assertSame($second, $this->history->all()[1]->event());
+        $this->assertSame($child, $this->history->all()[2]->event());
+        $this->assertSame($forth, $this->history->all()[3]->event());
+    }
+
+    /** @test */
+    public function it_can_return_all_fired_events_by_name()
+    {
+        $this->event->trackEvents();
+
+        $child = new class($bus = Config::instance()->commandBus(), 'test') extends CommandBusEvent {};
+
+        $this->event->fire($first = Event::new(new Payload()));
+        $this->event->fire($second = Event::new(new Payload()));
+        $this->event->fire($child);
+
+        $this->assertCount(1, $firedEvents = $this->history->get(get_class($child)));
+        $this->assertSame($child, $firedEvents[0]->event());
+
+        $this->assertCount(3, $firedEvents = $this->history->get(Event::class));
+        $this->assertSame($first, $firedEvents[0]->event());
+        $this->assertSame($second, $firedEvents[1]->event());
+        $this->assertSame($child, $firedEvents[2]->event());
+    }
+
+    /** @test */
+    public function it_returns_an_empty_array_if_no_events_are_found_calls_default_value_if_callable_or_returns_it()
+    {
+        $this->assertEmpty($this->history->get(Event::class));
+        $this->assertSame('nope', $this->history->get(Event::class, 'nope'));
+        $this->assertSame('i returned', $this->history->get(Event::class, function () {
+            return 'i returned';
+        }));
+    }
+
+    /** @test */
+    public function it_can_return_an_array_of_fired_events_by_the_listener_name_triggered()
+    {
+        $listener = $this->makeListener('listener', new class {
+            public $order = [];
+        });
+
+        $this->event->trackEvents()
+            ->subscribe(Event::class, $listener)
+            ->fire($event = Event::new(new Payload()));
+
+        $this->assertSame($event, $this->history->getFromListener(get_class($listener))[0]->event());
+    }
+
+    /** @test */
+    public function it_returns_an_empty_array_if_no_listeners_are_found_calls_default_value_if_callable_or_returns_it()
+    {
+        $this->assertEmpty($this->history->getFromListener(Listener::class));
+        $this->assertSame('nope', $this->history->getFromListener(Listener::class, 'nope'));
+        $this->assertSame('i returned', $this->history->getFromListener(Listener::class, function () {
+            return 'i returned';
+        }));
+    }
+}
diff --git a/tests/WmiScripting/Support/Event/EventProviderTest.php b/tests/WmiScripting/Support/Event/EventProviderTest.php
new file mode 100644
index 0000000..0416959
--- /dev/null
+++ b/tests/WmiScripting/Support/Event/EventProviderTest.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Tests\WmiScripting\Support\Event;
+
+use Tests\TestCase;
+use PhpWinTools\WmiScripting\Configuration\Config;
+use PhpWinTools\WmiScripting\Support\Events\Event;
+use PhpWinTools\WmiScripting\Support\Events\Payload;
+use PhpWinTools\WmiScripting\Support\Events\Listener;
+use PhpWinTools\WmiScripting\Support\Events\EventProvider;
+use PhpWinTools\WmiScripting\Support\Events\EventHistoryProvider;
+
+class EventProviderTest extends TestCase
+{
+    /** @var EventProvider */
+    protected $event;
+
+    /** @var EventHistoryProvider */
+    protected $history;
+
+    protected function setUp()
+    {
+        $config = Config::newInstance();
+        $this->event = $config->eventProvider();
+        $this->history = $this->event->history();
+    }
+
+    /** @test */
+    public function it_can_instantiate()
+    {
+        $this->assertInstanceOf(EventProvider::class, new EventProvider(new Config()));
+        $this->assertInstanceOf(EventProvider::class, EventProvider::instance());
+    }
+
+    /** @test */
+    public function it_registers_itself_with_the_config_on_instantiation()
+    {
+        $config = new Config();
+        $configEvent = $config->getBoundProvider('event');
+
+        $this->assertNotSame($configEvent, $newEvent = new EventProvider());
+        $this->assertSame($newEvent, $config->getBoundProvider('event'));
+    }
+
+    /** @test */
+    public function it_runs_listeners_when_an_applicable_event_is_fired()
+    {
+        $subject = new class {
+            public $order = [];
+        };
+
+        $child = new class(new Payload()) extends Event {};
+
+        $this->event->subscribe(get_class($child), $this->makeListener('listener', $subject));
+
+        $this->event->fire(Event::new(new Payload()));
+        $this->assertEmpty($subject->order);
+
+        $this->event->fire($child);
+        $this->assertEquals(['listener'], $subject->order);
+    }
+
+    /** @test */
+    public function it_runs_ancestor_listeners_when_a_child_event_is_fired()
+    {
+        $subject = new class { public $order = []; };
+        $descendant = new class(new Payload()) extends Event {};
+        $listener = $this->makeListener('listener_fired', $subject);
+
+        $this->event->trackEvents()
+            ->subscribe(Event::class, $listener)
+            ->fire($descendant);
+
+        $this->assertEquals(['listener_fired'], $subject->order);
+
+        $this->assertFalse($this->history->wasFiredByName(Event::class));
+        $this->assertTrue($this->history->wasFiredByDescendant(Event::class));
+    }
+
+    protected function makeListener($name, $subject)
+    {
+        return new class($subject, $name) extends Listener {
+            public $subject;
+
+            public $name;
+
+            public function __construct($subject, $name)
+            {
+                $this->subject = $subject;
+                $this->name = $name;
+            }
+
+            public function react(Event $event)
+            {
+                $this->subject->order[] = $this->name;
+            }
+        };
+    }
+}
