:param method: the command name
        :param igd_args: ordered case sensitive M-SEARCH headers, if provided all headers to be used must be provided
        :param lan_address: the ip address of the local interface
        :param gateway_address: the ip address of the gateway
        :param timeout: timeout, in seconds
        :param interface_name: name of the network interface, the default is aliased to 'default'
        :param kwargs: keyword arguments for the command
        :param loop: EventLoop, used for testing

def run_cli(cls, method, igd_args: OrderedDict, lan_address: str = '', gateway_address: str = '', timeout: int = 30,
                interface_name: str = 'default', unicast: bool = True, kwargs: dict = None, loop=None) -> None:
        """
        :param method: the command name
        :param igd_args: ordered case sensitive M-SEARCH headers, if provided all headers to be used must be provided
        :param lan_address: the ip address of the local interface
        :param gateway_address: the ip address of the gateway
        :param timeout: timeout, in seconds
        :param interface_name: name of the network interface, the default is aliased to 'default'
        :param kwargs: keyword arguments for the command
        :param loop: EventLoop, used for testing
        """
        kwargs = kwargs or {}
        igd_args = igd_args
        timeout = int(timeout)
        loop = loop or asyncio.get_event_loop_policy().get_event_loop()
        fut: asyncio.Future = asyncio.Future()

        async def wrapper():  # wrap the upnp setup and call of the command in a coroutine

            if method == 'm_search':  # if we're only m_searching don't do any device discovery
                fn = lambda *_a, **_kw: cls.m_search(
                    lan_address, gateway_address, timeout, igd_args, unicast, interface_name, loop
                )
            else:  # automatically discover the gateway
                try:
                    u = await cls.discover(
                        lan_address, gateway_address, timeout, igd_args, interface_name, loop=loop
                    )
                except UPnPError as err:
                    fut.set_exception(err)
                    return
                if hasattr(u, method) and hasattr(getattr(u, method), "_cli"):
                    fn = getattr(u, method)
                else:
                    fut.set_exception(UPnPError("\"%s\" is not a recognized command" % method))
                    return
            try:  # call the command
                result = await fn(**{k: fn.__annotations__[k](v) for k, v in kwargs.items()})
                fut.set_result(result)
            except UPnPError as err:
                fut.set_exception(err)

            except Exception as err:
                log.exception("uncaught error")
                fut.set_exception(UPnPError("uncaught error: %s" % str(err)))

        if not hasattr(UPnP, method) or not hasattr(getattr(UPnP, method), "_cli"):
            fut.set_exception(UPnPError("\"%s\" is not a recognized command" % method))
        else:
            loop.run_until_complete(wrapper())
        try:
            result = fut.result()
        except UPnPError as err:
            print("aioupnp encountered an error: %s" % str(err))
            return

        if isinstance(result, (list, tuple, dict)):
            print(json.dumps(result, indent=2, default=_encode))
        else:
            print(result)