[Python-modules-commits] [txwinrm] 01/09: Import txwinrm_1.2.7.orig.tar.gz

Christopher Stuart Hoskin mans0954 at moszumanska.debian.org
Sat Feb 25 15:24:44 UTC 2017


This is an automated email from the git hooks/post-receive script.

mans0954 pushed a commit to branch master
in repository txwinrm.

commit 82c0f6be59bfc0d73db331c87a09eb2b6e14f6d0
Author: Christopher Hoskin <mans0954 at debian.org>
Date:   Sat Feb 25 13:34:34 2017 +0000

    Import txwinrm_1.2.7.orig.tar.gz
---
 README.rst                           |  94 +++++++++++++--
 setup.py                             |   2 +-
 txwinrm/SessionManager.py            | 101 ++++++++--------
 txwinrm/WinRMClient.py               | 227 ++++++++++++++++++++++++++++++-----
 txwinrm/app.py                       |   8 +-
 txwinrm/associate.py                 |  97 +++++++++++++++
 txwinrm/enumerate.py                 |   9 +-
 txwinrm/krb5.py                      | 213 ++++++++++++++++++++++----------
 txwinrm/request/command.xml          |   6 +-
 txwinrm/request/create.xml           |   8 +-
 txwinrm/request/delete.xml           |   6 +-
 txwinrm/request/enumerate.xml        |   2 +-
 txwinrm/request/event_pull.xml       |   6 +-
 txwinrm/request/pull.xml             |   2 +-
 txwinrm/request/receive.xml          |   6 +-
 txwinrm/request/send.xml             |   6 +-
 txwinrm/request/signal.xml           |   6 +-
 txwinrm/request/subscribe.xml        |   6 +-
 txwinrm/request/unsubscribe.xml      |   6 +-
 txwinrm/shell.py                     |   4 +-
 txwinrm/test/test_connection_info.py |  80 ++++++++++++
 txwinrm/util.py                      |  55 ++++++++-
 22 files changed, 755 insertions(+), 195 deletions(-)

diff --git a/README.rst b/README.rst
index 8bac9f0..0a007c9 100644
--- a/README.rst
+++ b/README.rst
@@ -9,9 +9,11 @@ and monitoring the way Zenoss users are used to with some new possibilities.
 
 Right now we're trying to get as much real world experience using the library
 as possible to prove out the reliability and performance improvements we're
-hoping to achieve. If you have access to Windows servers, you can help! It
-doesn't even require a Zenoss Core installation as this tool stands alone right
-now.
+hoping to achieve. If you have access to Windows servers, you can help! This
+tool no longer stands alone.  You must install kerberos.so from the Windows
+ZenPack, https://github.com/zenoss/ZenPacks.zenoss.Microsoft.Windows/tree/develop/ZenPacks/zenoss/Microsoft/Windows/lib.
+The source for this version of pykerberos can be found here:
+https://github.com/zenoss/ZenPacks.zenoss.Microsoft.Windows/tree/develop/src/pykerberos
 
 See the zenoss-windows forum for updates to the project, and leave your
 feedback there. 
@@ -73,26 +75,29 @@ Current Feature Support
 -----------------------
 
 -  HTTP
+-  HTTPS
 -  Basic authentication
 -  WQL queries
 -  WinRS
 -  typeperf
 -  Subscribe to the Windows Event Log
 -  Kerberos authentication (domain accounts)
+-  Single session clients
+-  WMI Associators queries
 
 
 Future Feature Support
 ----------------------
 
--  HTTPS
 -  NTLM authentication (local accounts)
+-  Kerberos keytab support
 
 
 Configuring the Target Windows Machines
 ---------------------------------------
 
-You can enable the WinRM service on Windows Server 2003, 2008 and 2012. Run
-Command Prompt as Administrator and execute the following commands
+You can enable the WinRM service on Windows Server 2003, 2008, 2012, and 2016. Run
+Command Prompt as Administrator and execute the following commands for basic authentication.
 
 ::
 
@@ -101,6 +106,15 @@ Command Prompt as Administrator and execute the following commands
     winrm s winrm/config/service/auth '@{Basic="true"}'
     winrm s winrm/config/winrs '@{MaxShellsPerUser="2147483647"}'
 
+If using domain authentication, run the following commands.
+
+::
+
+    winrm quickconfig
+    winrm s winrm/config/service '@{MaxConcurrentOperationsPerUser="4294967295"}'
+    winrm s winrm/config/service/auth '@{Kerberos="true"}'
+    winrm s winrm/config/winrs '@{MaxShellsPerUser="2147483647"}'
+
 
 WQL Queries
 -----------
@@ -109,7 +123,7 @@ You can pass a single host a query via the command line...
 
 ::
 
-    $ winrm -r host -u user -f "select * from Win32_NetworkAdapter"
+    $ winrm -r host -u user -p password -f "select * from Win32_NetworkAdapter"
 
 
 Another option is to create an ini-style config file and hit multiple targets
@@ -188,6 +202,35 @@ are 'basic' and 'kerberos'. 'basic' is the default.
 The '-d' option increases logging, printing out the XML for all requests and
 responses, along with the HTTP status code.
 
+The '-e' option specifies which service principal to use on the Windows host.  Valid
+values are 'http', 'https', and 'wsman'.
+
+::
+
+    usage: winrm.py [-h] [--debug] [--config CONFIG] [--remote REMOTE]
+                    [--authentication {basic,kerberos}] [--username USERNAME]
+                    [--dcip DCIP] [--keytab KEYTAB] [--password PASSWORD]
+                    [--ipaddress IPADDRESS] [--service SERVICE]
+                    [--includedir INCLUDEDIR] [--filter FILTER]
+
+    optional arguments:
+      -h, --help            show this help message and exit
+      --debug, -d
+      --config CONFIG, -c CONFIG
+      --remote REMOTE, -r REMOTE
+                            hostname
+      --authentication {basic,kerberos}, -a {basic,kerberos}
+      --username USERNAME, -u USERNAME
+      --dcip DCIP, -i DCIP  address of kdc
+      --keytab KEYTAB, -k KEYTAB
+      --password PASSWORD, -p PASSWORD
+      --ipaddress IPADDRESS, -s IPADDRESS
+      --service SERVICE, -e SERVICE
+                            http/https/wsman
+      --includedir INCLUDEDIR
+                            valid includedir
+      --filter FILTER, -f FILTER
+
 
 WinRS
 -----
@@ -208,7 +251,7 @@ An example of interactive mode
 
 ::
 
-    $ winrs interactive -u Administrator -x 'typeperf "\Memory\Pages/sec" "\PhysicalDisk(_Total)\Avg. Disk Queue Length" "\Processor(_Total)\% Processor Time" -si 1' -r oakland
+    $ winrs interactive -u Administrator -r oakland
     Microsoft Windows [Version 6.2.9200]
     (c) 2012 Microsoft Corporation. All rights reserved.
     C:\Users\Default>dir
@@ -287,6 +330,38 @@ An example of batch
     Exit code of shell on oakland: 0
 
 
+Usage
+
+::
+
+    usage: winrs.py [-h] [--debug] [--config CONFIG] [--remote REMOTE]
+                    [--authentication {basic,kerberos}] [--username USERNAME]
+                    [--dcip DCIP] [--keytab KEYTAB] [--password PASSWORD]
+                    [--ipaddress IPADDRESS] [--service SERVICE]
+                    [--includedir INCLUDEDIR] [--command COMMAND]
+                    [{interactive,single,batch,long,multiple}]
+
+    positional arguments:
+      {interactive,single,batch,long,multiple}
+
+    optional arguments:
+      -h, --help            show this help message and exit
+      --debug, -d
+      --config CONFIG, -c CONFIG
+      --remote REMOTE, -r REMOTE
+                            hostname
+      --authentication {basic,kerberos}, -a {basic,kerberos}
+      --username USERNAME, -u USERNAME
+      --dcip DCIP, -i DCIP  address of kdc
+      --keytab KEYTAB, -k KEYTAB
+      --password PASSWORD, -p PASSWORD
+      --ipaddress IPADDRESS, -s IPADDRESS
+      --service SERVICE, -e SERVICE
+                            http/https/wsman
+      --includedir INCLUDEDIR
+                            valid includedir
+      --command COMMAND, -x COMMAND
+
 Typeperf
 --------
 
@@ -422,5 +497,8 @@ Run txwinrm/test/precommit before merging to master. This requires that you...
 Changes
 -------
 
+1.2.2
+* Add support for multiple kdcs to be defined
+
 1.1.27
 * Add support for running commands/enumerations in a single session
diff --git a/setup.py b/setup.py
index c261752..b0b2ba7 100755
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@
 
 setup_kwargs = dict(
     name='txwinrm',
-    version='1.1.28',
+    version='1.2.7',
     description='Asynchronous Python WinRM client',
     long_description=open('README.rst').read(),
     license='See LICENSE file',
diff --git a/txwinrm/SessionManager.py b/txwinrm/SessionManager.py
old mode 100644
new mode 100755
index e202dcc..c03508a
--- a/txwinrm/SessionManager.py
+++ b/txwinrm/SessionManager.py
@@ -24,25 +24,22 @@ of transactions/requests being made through a single Session
 
 """
 
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.internet.defer import inlineCallbacks, returnValue
 
 
 class Session(object):
-    """
-    Session handler for connection to a device.
 
-    deferred_init will kick off a deferred login to a device from the first
-    client that needs the connection.  Subsequent clients will use the data
-    returned from the first login.
+    """Session handler for connection to a device.
 
-    The Session class is responsible for implementing the login/logout methods
+    Session class is responsible for implementing the login/logout methods.
     """
+
     def __init__(self):
-        # Used to keep track of clients using session
+        # Used to keep track of clients using session.
         self._clients = set()
 
-        # The currently valid token.  This can be anything that the client
-        # needs to know the connection is alive
+        # The currently valid token. This can be anything that the client
+        # needs to know the connection is alive.
         self._token = None
 
         # Deferred waiting for login result.
@@ -51,15 +48,15 @@ class Session(object):
         # Error from last login if applicable.
         self._login_error = None
 
-        # Deferred sending a refresh/keep-alive signal/request
-        self._refresh_d = None
-
     @inlineCallbacks
     def deferred_login(self, client):
-        """Return Deferred token
+        """Kick off a deferred login to a device from the first
+        client that needs the connection.
+
+        Subsequent clients will use the data returned from the first login.
 
         :param client: Client initiating a connection
-        :client: ZenPack specific client
+        :type client: ZenPack specific client
         :rtype: Deferred
         :return: Returns ZenPack unique token to be used for a session.
         """
@@ -89,16 +86,13 @@ class Session(object):
 
     @inlineCallbacks
     def deferred_logout(self, client):
-        """Return Deferred None.
-
-        Calls session._deferred_logout() only if all other clients using the same
-        session have also called deferred_logout.
-
+        """Calls session._deferred_logout() only if all other clients
+        using the same session have also called deferred_logout.
         """
         if len(self._clients) <= 1:
             if self._token:
                 try:
-                    yield self._deferred_logout()
+                    yield self._deferred_logout(client)
                 except Exception:
                     pass
 
@@ -110,63 +104,67 @@ class Session(object):
 
     @inlineCallbacks
     def _deferred_login(self, client):
-        '''login method
+        """Performs the ZenPack specific login to a device.
 
-        Performs the ZenPack specific login to a device.  This will only be called
-        from the first client to fire off the deferred.  All other clients will
-        use the _token returned from this method
+        This will only be called from the first client to fire off the deferred.
+        All other clients will use the _token returned from this method.
 
         :param client: Client initiating a connection
         :type client: ZenPack specific client
         :rtype: Deferred
         :return: Returns a Deferred which is logs into the device.
-        '''
+        """
         returnValue(None)
 
     @inlineCallbacks
-    def _deferred_logout(self):
-        '''logout method
+    def _deferred_logout(self, client):
+        """Performs the ZenPack specific logout from a device.
 
-        Performs the ZenPack specific logout from a device.  This will only be called
-        by the last client to logout of the session.
+        This will only be called by the last client to logout of the session.
 
+        :param client: Client closing connection
+        :type client: ZenPack specific client
         :rtype: Deferred
-        :return: Returns a Deferred which logs out of the device
-        '''
+        :return: Returns a Deferred which logs out of the device.
+        """
         returnValue(None)
 
 
 class SessionManager(object):
-    '''
-    Class to manage open sessions to devices.
-    '''
+
+    """Class to manage open sessions to devices."""
+
     def __init__(self):
-        # Used to keep track of sessions
+        # Used to keep track of sessions.
         self._sessions = {}
 
     def get_connection(self, key):
-        '''Return the session for a given key
-        '''
+        """Return the session for a given key."""
         if key is None:
             raise Exception('Client key cannot be empty')
         return self._sessions.get(key, None)
 
     def remove_connection(self, key):
+        """End a session by a key.
+
+        This can happen if the token is too old, the server reboots, or if
+        the XML API is disabled and enabled.
+        """
         session = self.get_connection(key)
         if session:
-            self._sessions.pop(session)
+            self._sessions.pop(key)
 
     @inlineCallbacks
     def init_connection(self, client, session_class=Session):
-        '''Initialize connection to device.
-        If a session is already started return it.
-        Else kick off deferred to initiate session
+        """Initialize connection to device.
 
-        The client must contain a key for session storage
+        If a session is already started return it
+        else kick off deferred to initiate session.
+        The client must contain a key for session storage.
 
         :param client: Client initiating connection
-        :client: ZenPack defined client.
-        '''
+        :type client: ZenPack defined client
+        """
         if not hasattr(client, 'key'):
             raise Exception('Client must contain a key field')
 
@@ -186,18 +184,19 @@ class SessionManager(object):
 
     @inlineCallbacks
     def close_connection(self, client):
-        '''Kick off a session's logout
-        If there are no more clients using a session, remove it
+        """Kick off a session's logout.
 
-        :param client:  Client closing connection
-        :client: ZenPack defined class
-        '''
+        If there are no more clients using a session, remove it.
+
+        :param client: Client closing connection
+        :type client: ZenPack defined class
+        """
         session = self.get_connection(client.key)
         if not session:
             returnValue(None)
         yield session.deferred_logout(client)
         if not session._clients:
-            # no more clients so we don't need to keep the session
+            # No more clients so we don't need to keep the session.
             self._sessions.pop(client.key)
         returnValue(None)
 
diff --git a/txwinrm/WinRMClient.py b/txwinrm/WinRMClient.py
index 797008a..2cfac59 100755
--- a/txwinrm/WinRMClient.py
+++ b/txwinrm/WinRMClient.py
@@ -8,8 +8,12 @@
 ##############################################################################
 
 import logging
+from collections import namedtuple
 from httplib import BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, OK
+import shlex
+from cStringIO import StringIO
 
+from twisted.internet import reactor
 from twisted.internet.defer import (
     inlineCallbacks,
     returnValue,
@@ -26,7 +30,6 @@ except ImportError:
         pass
 
 from . import constants as c
-from kerberos import GSSError
 from .util import (
     _authenticate_with_kerberos,
     _get_agent,
@@ -43,7 +46,7 @@ from .util import (
     RequestError,
     _ErrorReader,
     _StringProtocol,
-    ET
+    ET,
 )
 from .shell import (
     _find_shell_id,
@@ -61,14 +64,55 @@ from .enumerate import (
     _MAX_REQUESTS_PER_ENUMERATION
 )
 from .SessionManager import SESSION_MANAGER, Session
+kerberos = None
 LOG = logging.getLogger('winrm')
 
+EnumInfo = namedtuple('EnumInfo', ['wql', 'resource_uri'])
+
+
+def _build_ps_command_line_elem(ps_command, ps_script):
+    """Build PowerShell command line elements without splitting
+    the actual ps script into arguments. using _build_command_line_elem
+    with a ps script splits the script into separate arguments.  Remote
+    Windows shell inserts spaces when reconstituting the script.
+
+    ps_command - powershell command with arguments as string
+        e.g. 'powershell -NoLogo -NonInteractive -NoProfile -Command'
+    ps_script - script to be run in powershell as single line string
+        e.g. "& {get-counter -counter \"\memory\pages output/sec\" }"
+    """
+    command_line_parts = shlex.split(ps_command, posix=False)
+    # ensure '-command' is last
+    if command_line_parts[-1:][0].lower() != '-command':
+        index = 0
+        for option in command_line_parts:
+            if option.lower() == '-command':
+                command_line_parts.pop(index)
+                break
+            index += 1
+        command_line_parts.append(option)
+    prefix = "rsp"
+    ET.register_namespace(prefix, c.XML_NS_MSRSP)
+    command_line_elem = ET.Element('{%s}CommandLine' % c.XML_NS_MSRSP)
+    command_elem = ET.Element('{%s}Command' % c.XML_NS_MSRSP)
+    command_elem.text = command_line_parts[0]
+    command_line_elem.append(command_elem)
+    for arguments_text in command_line_parts[1:]:
+        arguments_elem = ET.Element('{%s}Arguments' % c.XML_NS_MSRSP)
+        arguments_elem.text = arguments_text
+        command_line_elem.append(arguments_elem)
+    arguments_elem = ET.Element('{%s}Arguments' % c.XML_NS_MSRSP)
+    arguments_elem.text = ps_script
+    command_line_elem.append(arguments_elem)
+    tree = ET.ElementTree(command_line_elem)
+    str_io = StringIO()
+    tree.write(str_io, encoding='utf-8')
+    return str_io.getvalue()
+
 
 class WinRMSession(Session):
     '''
     Session class to keep track of single winrm connection
-
-
     '''
     def __init__(self):
         super(WinRMSession, self).__init__()
@@ -93,6 +137,8 @@ class WinRMSession(Session):
         # connection.
         self.sem = DeferredSemaphore(1)
 
+        self._refresh_dc = None
+
     def is_kerberos(self):
         return self._conn_info.auth_type == 'kerberos'
 
@@ -161,12 +207,14 @@ class WinRMSession(Session):
                     raise e
             if response.code == UNAUTHORIZED:
                 if self.is_kerberos():
+                    if not kerberos:
+                        from .util import kerberos
                     auth_header = response.headers.getRawHeaders('WWW-Authenticate')[0]
                     auth_details = get_auth_details(auth_header)
                     try:
                         if auth_details:
                             self._gssclient._step(auth_details)
-                    except GSSError as e:
+                    except kerberos.GSSError as e:
                         msg = "HTTP Unauthorized received.  "
                         "Kerberos error code {0}: {1}.".format(e.args[1][1], e.args[1][0])
                         raise Exception(msg)
@@ -187,9 +235,9 @@ class WinRMSession(Session):
         returnValue(response)
 
     @inlineCallbacks
-    def send_request(self, request_template_name, client, **kwargs):
+    def send_request(self, request_template_name, client, envelope_size=None, **kwargs):
         response = yield self._send_request(
-            request_template_name, client, **kwargs)
+            request_template_name, client, envelope_size=envelope_size, **kwargs)
         proto = _StringProtocol()
         response.deliverBody(proto)
         body = yield proto.d
@@ -207,7 +255,11 @@ class WinRMSession(Session):
         returnValue(ET.fromstring(xml_str))
 
     @inlineCallbacks
-    def _send_request(self, request_template_name, client, **kwargs):
+    def _send_request(self, request_template_name, client, envelope_size=None,
+                      locale=None, code_page=None, **kwargs):
+        kwargs['envelope_size'] = envelope_size or client._conn_info.envelope_size
+        kwargs['locale'] = locale or client._conn_info.locale
+        kwargs['code_page'] = code_page or client._conn_info.code_page
         if self._login_d and not self._login_d.called:
             # check for a reconnection attempt so we do not send any requests
             # to a dead connection
@@ -233,14 +285,29 @@ class WinRMSession(Session):
         response = yield self.handle_response(request, response, client)
         returnValue(response)
 
+    def close_connection(self, client):
+        try:
+            self._refresh_dc.cancel()
+        except Exception:
+            pass
+
+        # Close the connection after 60 seconds.  This will give other clients
+        # enough time to keep the connection alive and continue using the same session.
+        self._refresh_dc = reactor.callLater(60, SESSION_MANAGER.close_connection, client)
+
 
 class WinRMClient(object):
+    """Base winrm client class
+
+    Contains core functionality for various types of winrm based clients
+    """
     def __init__(self, conn_info):
         verify_conn_info(conn_info)
         self.key = None
         self._conn_info = conn_info
         self.session_manager = SESSION_MANAGER
         self._session = None
+        self.ps_script = None
 
     @inlineCallbacks
     def init_connection(self):
@@ -285,7 +352,14 @@ class WinRMClient(object):
 
     @inlineCallbacks
     def _send_command(self, shell_id, command_line):
-        command_line_elem = _build_command_line_elem(command_line)
+        if self.ps_script is not None:
+            command_line_elem = _build_ps_command_line_elem(command_line,
+                                                            self.ps_script)
+        else:
+            command_line_elem = _build_command_line_elem(command_line)
+        LOG.debug('WinRMClient._send_command: sending command request '
+                  '(shell_id={0}, command_line_elem={1})'.format(
+                      shell_id, command_line_elem))
         command_elem = yield self._session.send_request(
             'command', self, shell_id=shell_id, command_line_elem=command_line_elem,
             timeout=self._conn_info.timeout)
@@ -299,28 +373,35 @@ class WinRMClient(object):
 
     @inlineCallbacks
     def close_connection(self):
-        yield self.session_manager.close_connection(self)
+        yield self._session.close_connection(self)
         returnValue(None)
 
 
 class SingleCommandClient(WinRMClient):
-
+    """Client to send a single command to a winrm device"""
     def __init__(self, conn_info):
         super(SingleCommandClient, self).__init__(conn_info)
         self.key = (self._conn_info.ipaddress, 'short')
 
     @inlineCallbacks
-    def run_command(self, command_line):
-        '''
-        Run a single command in the session's semaphore.  Windows must finish
+    def run_command(self, command_line, ps_script=None):
+        """Run a single command in the session's semaphore.  Windows must finish
         a command conversation before a new command or enumeration can start
-        '''
+
+        If running a powershell script, send it in separately with ps_script in
+        "& {<actual script here>}" format
+        e.g. command_line='powershell -NoLogo -NonInteractive -NoProfile -Command',
+        ps_script='"& {get-counter -counter \\\"\memory\pages output/sec\\\" }"'
+        """
+        cmd_response = None
+        self.ps_script = ps_script
         yield self.init_connection()
         try:
             cmd_response = yield self._session.sem.run(self.run_single_command,
                                                        command_line)
         except Exception:
             yield self.close_connection()
+            raise
         returnValue(cmd_response)
 
     @inlineCallbacks
@@ -335,7 +416,7 @@ class SingleCommandClient(WinRMClient):
                 .exit_code = <int>
         """
         shell_id = yield self._create_shell()
-
+        cmd_response = None
         try:
             cmd_response = yield self._run_command(shell_id, command_line)
         except TimeoutError:
@@ -368,6 +449,7 @@ class SingleCommandClient(WinRMClient):
 
 
 class LongCommandClient(WinRMClient):
+    """Client to run a single long running command to a winrm device"""
     def __init__(self, conn_info):
         super(LongCommandClient, self).__init__(conn_info)
         self._shell_id = None
@@ -375,15 +457,20 @@ class LongCommandClient(WinRMClient):
         self._exit_code = None
 
     @inlineCallbacks
-    def start(self, command_line):
+    def start(self, command_line, ps_script=''):
+        """Start long running command
+
+        If running a powershell script, send it in separately with ps_script in
+        "& {<actual script here>}" format
+        e.g. command_line='powershell -NoLogo -NonInteractive -NoProfile -Command',
+        ps_script='"& {get-counter -counter \\\"\memory\pages output/sec\\\" }"'
+
+        """
         LOG.debug("LongRunningCommand run_command: {0}".format(command_line))
-        self.key = (self._conn_info.ipaddress, command_line)
+        self.key = (self._conn_info.ipaddress, command_line + str(ps_script))
+        self.ps_script = ps_script
         yield self.init_connection()
         self._shell_id = yield self._create_shell()
-        command_line_elem = _build_command_line_elem(command_line)
-        LOG.debug('LongRunningCommand run_command: sending command request '
-                  '(shell_id={0}, command_line_elem={1})'.format(
-                      self._shell_id, command_line_elem))
         try:
             command_elem = yield self._send_command(self._shell_id,
                                                     command_line)
@@ -422,7 +509,8 @@ class LongCommandClient(WinRMClient):
 
 
 class EnumerateClient(WinRMClient):
-    """
+    """Client to send a single wmi query(WQL) to a winrm device
+
     Sends enumerate requests to a host running the WinRM service and returns
     a list of items.
     """
@@ -435,10 +523,8 @@ class EnumerateClient(WinRMClient):
 
     @inlineCallbacks
     def enumerate(self, wql, resource_uri=DEFAULT_RESOURCE_URI):
-        """
-        Runs a remote WQL query.
-        """
-        self.init_connection()
+        """Runs a remote WQL query."""
+        yield self.init_connection()
         request_template_name = 'enumerate'
         enumeration_context = None
         items = []
@@ -473,10 +559,9 @@ class EnumerateClient(WinRMClient):
 
     @inlineCallbacks
     def do_collect(self, enum_infos):
-        '''
-        Run enumerations in the session's semaphore.  Windows must finish
+        """Run enumerations in the session's semaphore.  Windows must finish
         an enumeration before a new command or enumeration can start
-        '''
+        """
         items = {}
         yield self.init_connection()
         self._session = self.session_manager.get_connection(self.key)
@@ -494,3 +579,85 @@ class EnumerateClient(WinRMClient):
 
         yield self.close_connection()
         returnValue(items)
+
+
+class AssociatorClient(EnumerateClient):
+    """WinRM Client that can return wmi classes that are associated with
+        another wmi class through a single property.
+        First a regular wmi query is run to select objects from a class.
+            e.g. 'select * from Win32_NetworkAdapter'
+        Next we will loop through the results and run the associator query
+        using a specific property of the object as input to return
+        a result class.
+            e.g. for interface in interfaces:
+                "ASSOCIATORS OF {Win32_NetworkAdapter.DeviceID=interface.DeviceID} WHERE ResultClass=Win32_PnPEntity'
+    """
+
+    @inlineCallbacks
+    def associate(self,
+                  seed_class,
+                  associations,
+                  where=None,
+                  resource_uri=DEFAULT_RESOURCE_URI,
+                  fields=['*']):
+        """Method to retrieve associated wmi classes based upon a
+        property from a given class
+
+        seed_class - wmi class which will be initially queried
+        associations - list of dicts containing parameters for
+            the 'ASSOCIATORS of {A}' wql statement.  We dequeue the
+            dicts and can search results from previous wql query to
+            search for nested associations.
+                search_class - initial class to associate with
+                search_property - property on search_class to match
+                return_class - class which will be returned
+                where_type - keyword of association type:
+                    AssocClass = AssocClassName
+                    RequiredAssocQualifier = QualifierName
+                    RequiredQualifier = QualifierName
+                    ResultClass = ClassName
+                    ResultRole = PropertyName
+                    Role = PropertyName
+        where - wql where clause to narrow scope of initial query
+        resource_uri - uri of resource.  this will be the same for both
+            input and result classes.  Limitation of WQL
+        fields - fields to return from seed_class on initial query
+
+        returns dict of seed_class and all return_class results
+            mapped by search_property
+
+        see https://msdn.microsoft.com/en-us/library/aa384793(v=vs.85).aspx
+        """
+        items = {}
+        wql = 'Select {} from {}'.format(','.join(fields), seed_class)
+        if where:
+            wql += ' where {}'.format(where)
+        enum_info = EnumInfo(wql, resource_uri)
+        results = yield self.do_collect([enum_info])
+        input_results = results[enum_info]
+
+        items[seed_class] = input_results
+        while associations:
+            association = associations.pop(0)
+            associate_results = []
+            prop_results = {}
+            for item in input_results:
+                try:
+                    prop = getattr(item, association['search_property'])
+                except AttributeError:
+                    continue
+                else:
+                    wql = "ASSOCIATORS of {{{}.{}='{}'}} WHERE {}={}".format(
+                        association['search_class'],
+                        association['search_property'],
+                        prop,
+                        association['where_type'],
+                        association['return_class'])
+                    enum_info = EnumInfo(wql, resource_uri)
+                    result = yield self.do_collect([enum_info])
+                    associate_results.extend(result[enum_info])
+                    prop_results[prop] = result[enum_info]
+
+            items[association['return_class']] = prop_results
+            input_results = associate_results
+        returnValue(items)
diff --git a/txwinrm/app.py b/txwinrm/app.py
index 4f92834..5a93753 100644
--- a/txwinrm/app.py
+++ b/txwinrm/app.py
@@ -211,15 +211,16 @@ def _parse_args(utility):
     parser = ArgumentParser()
     parser.add_argument("--debug", "-d", action="store_true")
     parser.add_argument("--config", "-c")
-    parser.add_argument("--remote", "-r")
+    parser.add_argument("--remote", "-r", help="hostname")
     parser.add_argument("--authentication", "-a", default='basic',
                         choices=['basic', 'kerberos'])
     parser.add_argument("--username", "-u")
-    parser.add_argument("--dcip", "-i")
+    parser.add_argument("--dcip", "-i", help="address of kdc")
     parser.add_argument("--keytab", "-k")
     parser.add_argument("--password", "-p")
     parser.add_argument("--ipaddress", "-s")
     parser.add_argument("--service", "-e", help='http/https/wsman', default='http')
+    parser.add_argument("--includedir", help="valid includedir")
     utility.add_args(parser)
     args = parser.parse_args()
     if not args.config:
@@ -239,7 +240,8 @@ def _parse_args(utility):
                 hostname, args.authentication,
                 args.username, password, scheme,
                 port, connectiontype, args.keytab,
-                args.dcip, ipaddress=args.ipaddress, service=args.service)
+                args.dcip, ipaddress=args.ipaddress, service=args.service,
+                include_dir=args.includedir)
             try:
                 verify_conn_info(args.conn_info)
             except Exception as e:
diff --git a/txwinrm/associate.py b/txwinrm/associate.py
new file mode 100755
index 0000000..0577ac4
--- /dev/null
+++ b/txwinrm/associate.py
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# Copyright (C) Zenoss, Inc. 2013, all rights reserved.
+#
+# This content is made available according to terms specified in the LICENSE
+# file at the top-level directory of this package.
+#
+##############################################################################
+
+import logging
+
+from twisted.internet import defer
+from WinRMClient import AssociatorClient
+from .util import (
+    ConnectionInfo,
+)
+
+log = logging.getLogger('winrm')
+# for use with seed_class of Win32_NetworkAdapter
+interface_map = [{'return_class': 'Win32_PnPEntity',
+                  'search_class': 'win32_NetworkAdapter',
+                  'search_property': 'DeviceID',
+                  'where_type': 'ResultClass'
+                  }]
+# for use with seed_class of Win32_DiskDrive
+disk_map = [{'return_class': 'Win32_DiskDriveToDiskPartition',
+             'search_class': 'Win32_DiskDrive',
+             'search_property': 'DeviceID',
+             'where_type': 'AssocClass'},
+            {'return_class': 'Win32_LogicalDiskToPartition',
+             'search_class': 'Win32_DiskPartition',
+             'search_property': 'DeviceID',
+             'where_type': 'AssocClass'
+             }]
+
+
+class WinrmAssociatorClient(object):
+
+    @defer.inlineCallbacks
+    def do_associate(self, conn_info):
+        """
+        """
+        client = AssociatorClient(conn_info)
+        items = {}
+
+        items = yield client.associate(
+            'Win32_DiskDrive',
+            disk_map
+        )
+
+        defer.returnValue(items)
+
+
+# ----- An example of usage...
+
+if __name__ == '__main__':
+    from pprint import pprint
+    import logging
+    from twisted.internet import reactor
+    logging.basicConfig()
+    winrm = WinrmAssociatorClient()
+
+    # Enter your params here before running
+    params = {
+        'hostname': "",  # name of host
+        'authtype': "",  # kerberos or basic
+        'user': "",  # username
+        'password': "",  # password
+        'kdc': "",  # kdc address
+        'ipaddress': ""  # ip address
+    }
+
+    ''' Remove this line and line at the end to run test
+    @defer.inlineCallbacks
+    def do_example_collect():
+        connectiontype = 'Keep-Alive'
+        conn_info = ConnectionInfo(
+            params['hostname'],
+            params['authtype'],
+            params['user'],
+            params['password'],
+            "http",
+            5985,
+            connectiontype,
+            "",
+            params['kdc'],
+            ipaddress=params['ipaddress'])
+
+        items = yield winrm.do_associate(conn_info)
+        pprint(items)
+        pprint(items.keys())
+        reactor.stop()
+
+    reactor.callWhenRunning(do_example_collect)
+    reactor.run()
+
+    Remove this line to execute above code'''
diff --git a/txwinrm/enumerate.py b/txwinrm/enumerate.py
old mode 100644
new mode 100755
index 26a92d7..8e379cb
--- a/txwinrm/enumerate.py
+++ b/txwinrm/enumerate.py
@@ -216,7 +216,7 @@ class ParserFeedingProtocol(Protocol):
         self.d = defer.Deferred()
         self._debug_data = ''
         self._sender = sender
-        self._data = [] 
+        self._data = []
 
     def dataReceived(self, data):
         """
@@ -241,13 +241,16 @@ class ParserFeedingProtocol(Protocol):
             #  Decrypt data first
             data = self._sender.decrypt_body(''.join(self._data))
             self._debug_data = data
-            self._xml_parser.feed(data)
+            try:
+                self._xml_parser.feed(data)
+            except Exception:
+                raise Exception('Could not parse SOAP message: {}'.format(data))
         if self._debug_data and log.isEnabledFor(logging.DEBUG):
             try:
                 import xml.dom.minidom
                 xml = xml.dom.minidom.parseString(self._debug_data)
                 log.debug(xml.toprettyxml())
-            except:
+            except Exception:
                 log.debug('Could not prettify response XML: "{0}"'
                           .format(self._debug_data))
         if isinstance(reason.value, ResponseFailed):
diff --git a/txwinrm/krb5.py b/txwinrm/krb5.py
index c3066b6..eed3bf3 100644
--- a/txwinrm/krb5.py
+++ b/txwinrm/krb5.py
@@ -8,7 +8,6 @@
 ##############################################################################
 
 import logging
-LOG = logging.getLogger('txwinrm.krb5')
 
 import collections
 import os
@@ -16,22 +15,22 @@ import re
 
 from twisted.internet import defer, reactor
 from twisted.internet.protocol import ProcessProtocol
+LOG = logging.getLogger('txwinrm.krb5')
 
 
 __all__ = [
     'kinit',
     'ccname',
-    ]
... 795 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/txwinrm.git



More information about the Python-modules-commits mailing list