pywinrm-0.3.0/0002775000175000017500000000000013216534570014021 5ustar mdavismdavis00000000000000pywinrm-0.3.0/pywinrm.egg-info/0002775000175000017500000000000013216534570017220 5ustar mdavismdavis00000000000000pywinrm-0.3.0/pywinrm.egg-info/requires.txt0000664000175000017500000000017413216534570021620 0ustar mdavismdavis00000000000000xmltodict requests>=2.9.1 requests_ntlm>=0.3.0 six [credssp] requests-credssp>=0.0.1 [kerberos] requests-kerberos>=0.10.0 pywinrm-0.3.0/pywinrm.egg-info/PKG-INFO0000664000175000017500000000247713216534570020325 0ustar mdavismdavis00000000000000Metadata-Version: 1.1 Name: pywinrm Version: 0.3.0 Summary: Python library for Windows Remote Management Home-page: http://github.com/diyan/pywinrm/ Author: Alexey Diyan Author-email: alexey.diyan@gmail.com License: MIT license Description-Content-Type: UNKNOWN Description: UNKNOWN Keywords: winrm,ws-man,devops,ws-management Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Clustering Classifier: Topic :: System :: Distributed Computing Classifier: Topic :: System :: Systems Administration pywinrm-0.3.0/pywinrm.egg-info/SOURCES.txt0000664000175000017500000000123713216534570021105 0ustar mdavismdavis00000000000000README.md setup.cfg setup.py pywinrm.egg-info/PKG-INFO pywinrm.egg-info/SOURCES.txt pywinrm.egg-info/dependency_links.txt pywinrm.egg-info/requires.txt pywinrm.egg-info/top_level.txt winrm/__init__.py winrm/encryption.py winrm/exceptions.py winrm/protocol.py winrm/transport.py winrm/tests/__init__.py winrm/tests/conftest.py winrm/tests/sample_script.ps1 winrm/tests/test_cmd.py winrm/tests/test_encryption.py winrm/tests/test_integration_protocol.py winrm/tests/test_integration_session.py winrm/tests/test_nori_type_casting.py winrm/tests/test_powershell.py winrm/tests/test_protocol.py winrm/tests/test_session.py winrm/tests/test_transport.py winrm/tests/test_wql.pypywinrm-0.3.0/pywinrm.egg-info/top_level.txt0000664000175000017500000000000613216534570021744 0ustar mdavismdavis00000000000000winrm pywinrm-0.3.0/pywinrm.egg-info/dependency_links.txt0000664000175000017500000000000113216534570023264 0ustar mdavismdavis00000000000000 pywinrm-0.3.0/PKG-INFO0000664000175000017500000000247713216534570015126 0ustar mdavismdavis00000000000000Metadata-Version: 1.1 Name: pywinrm Version: 0.3.0 Summary: Python library for Windows Remote Management Home-page: http://github.com/diyan/pywinrm/ Author: Alexey Diyan Author-email: alexey.diyan@gmail.com License: MIT license Description-Content-Type: UNKNOWN Description: UNKNOWN Keywords: winrm,ws-man,devops,ws-management Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Clustering Classifier: Topic :: System :: Distributed Computing Classifier: Topic :: System :: Systems Administration pywinrm-0.3.0/winrm/0002775000175000017500000000000013216534570015155 5ustar mdavismdavis00000000000000pywinrm-0.3.0/winrm/__init__.py0000664000175000017500000001173513207041431017260 0ustar mdavismdavis00000000000000from __future__ import unicode_literals import re from base64 import b64encode import xml.etree.ElementTree as ET from winrm.protocol import Protocol # Feature support attributes for multi-version clients. # These values can be easily checked for with hasattr(winrm, "FEATURE_X"), # "'auth_type' in winrm.FEATURE_SUPPORTED_AUTHTYPES", etc for clients to sniff features # supported by a particular version of pywinrm FEATURE_SUPPORTED_AUTHTYPES=['basic', 'certificate', 'ntlm', 'kerberos', 'plaintext', 'ssl', 'credssp'] FEATURE_READ_TIMEOUT=True FEATURE_OPERATION_TIMEOUT=True class Response(object): """Response from a remote command execution""" def __init__(self, args): self.std_out, self.std_err, self.status_code = args def __repr__(self): # TODO put tree dots at the end if out/err was truncated return ''.format( self.status_code, self.std_out[:20], self.std_err[:20]) class Session(object): # TODO implement context manager methods def __init__(self, target, auth, **kwargs): username, password = auth self.url = self._build_url(target, kwargs.get('transport', 'plaintext')) self.protocol = Protocol(self.url, username=username, password=password, **kwargs) def run_cmd(self, command, args=()): # TODO optimize perf. Do not call open/close shell every time shell_id = self.protocol.open_shell() command_id = self.protocol.run_command(shell_id, command, args) rs = Response(self.protocol.get_command_output(shell_id, command_id)) self.protocol.cleanup_command(shell_id, command_id) self.protocol.close_shell(shell_id) return rs def run_ps(self, script): """base64 encodes a Powershell script and executes the powershell encoded script command """ # must use utf16 little endian on windows encoded_ps = b64encode(script.encode('utf_16_le')).decode('ascii') rs = self.run_cmd('powershell -encodedcommand {0}'.format(encoded_ps)) if len(rs.std_err): # if there was an error message, clean it it up and make it human # readable rs.std_err = self._clean_error_msg(rs.std_err) return rs def _clean_error_msg(self, msg): """converts a Powershell CLIXML message to a more human readable string """ # TODO prepare unit test, beautify code # if the msg does not start with this, return it as is if msg.startswith("#< CLIXML\r\n"): # for proper xml, we need to remove the CLIXML part # (the first line) msg_xml = msg[11:] try: # remove the namespaces from the xml for easier processing msg_xml = self._strip_namespace(msg_xml) root = ET.fromstring(msg_xml) # the S node is the error message, find all S nodes nodes = root.findall("./S") new_msg = "" for s in nodes: # append error msg string to result, also # the hex chars represent CRLF so we replace with newline new_msg += s.text.replace("_x000D__x000A_", "\n") except Exception as e: # if any of the above fails, the msg was not true xml # print a warning and return the orignal string # TODO do not print, raise user defined error instead print("Warning: there was a problem converting the Powershell" " error message: %s" % (e)) else: # if new_msg was populated, that's our error message # otherwise the original error message will be used if len(new_msg): # remove leading and trailing whitespace while we are here msg = new_msg.strip() return msg def _strip_namespace(self, xml): """strips any namespaces from an xml string""" try: p = re.compile("xmlns=*[\"\"][^\"\"]*[\"\"]") allmatches = p.finditer(xml) for match in allmatches: xml = xml.replace(match.group(), "") return xml except Exception as e: raise Exception(e) @staticmethod def _build_url(target, transport): match = re.match( '(?i)^((?Phttp[s]?)://)?(?P[0-9a-z-_.]+)(:(?P\d+))?(?P(/)?(wsman)?)?', target) # NOQA scheme = match.group('scheme') if not scheme: # TODO do we have anything other than HTTP/HTTPS scheme = 'https' if transport == 'ssl' else 'http' host = match.group('host') port = match.group('port') if not port: port = 5986 if transport == 'ssl' else 5985 path = match.group('path') if not path: path = 'wsman' return '{0}://{1}:{2}/{3}'.format(scheme, host, port, path.lstrip('/')) pywinrm-0.3.0/winrm/transport.py0000664000175000017500000003107713212626043017562 0ustar mdavismdavis00000000000000from __future__ import unicode_literals import sys import os import inspect is_py2 = sys.version[0] == '2' if is_py2: # use six for this instead? unicode_type = type(u'') else: # use six for this instead? unicode_type = type(u'') import requests import requests.auth import warnings from distutils.util import strtobool HAVE_KERBEROS = False try: from requests_kerberos import HTTPKerberosAuth, REQUIRED, OPTIONAL, DISABLED HAVE_KERBEROS = True except ImportError: pass HAVE_NTLM = False try: from requests_ntlm import HttpNtlmAuth HAVE_NTLM = True except ImportError as ie: pass HAVE_CREDSSP = False try: from requests_credssp import HttpCredSSPAuth HAVE_CREDSSP = True except ImportError as ie: pass from winrm.exceptions import InvalidCredentialsError, WinRMError, \ WinRMTransportError from winrm.encryption import Encryption __all__ = ['Transport'] class UnsupportedAuthArgument(Warning): pass class Transport(object): def __init__( self, endpoint, username=None, password=None, realm=None, service=None, keytab=None, ca_trust_path=None, cert_pem=None, cert_key_pem=None, read_timeout_sec=None, server_cert_validation='validate', kerberos_delegation=False, kerberos_hostname_override=None, auth_method='auto', message_encryption='auto', credssp_disable_tlsv1_2=False, send_cbt=True): self.endpoint = endpoint self.username = username self.password = password self.realm = realm self.service = service self.keytab = keytab self.ca_trust_path = ca_trust_path self.cert_pem = cert_pem self.cert_key_pem = cert_key_pem self.read_timeout_sec = read_timeout_sec self.server_cert_validation = server_cert_validation self.kerberos_hostname_override = kerberos_hostname_override self.message_encryption = message_encryption self.credssp_disable_tlsv1_2 = credssp_disable_tlsv1_2 self.send_cbt = send_cbt if self.server_cert_validation not in [None, 'validate', 'ignore']: raise WinRMError('invalid server_cert_validation mode: %s' % self.server_cert_validation) # defensively parse this to a bool if isinstance(kerberos_delegation, bool): self.kerberos_delegation = kerberos_delegation else: self.kerberos_delegation = bool(strtobool(str(kerberos_delegation))) self.auth_method = auth_method self.default_headers = { 'Content-Type': 'application/soap+xml;charset=UTF-8', 'User-Agent': 'Python WinRM client', } # try to suppress user-unfriendly warnings from requests' vendored urllib3 try: from requests.packages.urllib3.exceptions import InsecurePlatformWarning warnings.simplefilter('ignore', category=InsecurePlatformWarning) except: pass # oh well, we tried... try: from requests.packages.urllib3.exceptions import SNIMissingWarning warnings.simplefilter('ignore', category=SNIMissingWarning) except: pass # oh well, we tried... # if we're explicitly ignoring validation, try to suppress InsecureRequestWarning, since the user opted-in if self.server_cert_validation == 'ignore': try: from requests.packages.urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore', category=InsecureRequestWarning) except: pass # oh well, we tried... try: from urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore', category=InsecureRequestWarning) except: pass # oh well, we tried... # validate credential requirements for various auth types if self.auth_method != 'kerberos': if self.auth_method == 'certificate' or ( self.auth_method == 'ssl' and (self.cert_pem or self.cert_key_pem)): if not self.cert_pem or not self.cert_key_pem: raise InvalidCredentialsError("both cert_pem and cert_key_pem must be specified for cert auth") if not os.path.exists(self.cert_pem): raise InvalidCredentialsError("cert_pem file not found (%s)" % self.cert_pem) if not os.path.exists(self.cert_key_pem): raise InvalidCredentialsError("cert_key_pem file not found (%s)" % self.cert_key_pem) else: if not self.username: raise InvalidCredentialsError("auth method %s requires a username" % self.auth_method) if self.password is None: raise InvalidCredentialsError("auth method %s requires a password" % self.auth_method) self.session = None # Used for encrypting messages self.encryption = None # The Pywinrm Encryption class used to encrypt/decrypt messages if self.message_encryption not in ['auto', 'always', 'never']: raise WinRMError( "invalid message_encryption arg: %s. Should be 'auto', 'always', or 'never'" % self.message_encryption) def build_session(self): session = requests.Session() session.verify = self.server_cert_validation == 'validate' if session.verify and self.ca_trust_path: session.verify = self.ca_trust_path # configure proxies from HTTP/HTTPS_PROXY envvars session.trust_env = True settings = session.merge_environment_settings(url=self.endpoint, proxies={}, stream=None, verify=None, cert=None) # we're only applying proxies and/or verify from env, other settings are ignored session.proxies = settings['proxies'] if settings['verify'] is not None or self.ca_trust_path is not None: session.verify = self.ca_trust_path or settings['verify'] encryption_available = False if self.auth_method == 'kerberos': if not HAVE_KERBEROS: raise WinRMError("requested auth method is kerberos, but requests_kerberos is not installed") man_args = dict( mutual_authentication=REQUIRED, ) opt_args = dict( delegate=self.kerberos_delegation, force_preemptive=True, principal=self.username, hostname_override=self.kerberos_hostname_override, sanitize_mutual_error_response=False, service=self.service, send_cbt=self.send_cbt ) kerb_args = self._get_args(man_args, opt_args, HTTPKerberosAuth.__init__) session.auth = HTTPKerberosAuth(**kerb_args) encryption_available = hasattr(session.auth, 'winrm_encryption_available') and session.auth.winrm_encryption_available elif self.auth_method in ['certificate', 'ssl']: if self.auth_method == 'ssl' and not self.cert_pem and not self.cert_key_pem: # 'ssl' was overloaded for HTTPS with optional certificate auth, # fall back to basic auth if no cert specified session.auth = requests.auth.HTTPBasicAuth(username=self.username, password=self.password) else: session.cert = (self.cert_pem, self.cert_key_pem) session.headers['Authorization'] = \ "http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/https/mutual" elif self.auth_method == 'ntlm': if not HAVE_NTLM: raise WinRMError("requested auth method is ntlm, but requests_ntlm is not installed") man_args = dict( username=self.username, password=self.password ) opt_args = dict( send_cbt=self.send_cbt ) ntlm_args = self._get_args(man_args, opt_args, HttpNtlmAuth.__init__) session.auth = HttpNtlmAuth(**ntlm_args) # check if requests_ntlm has the session_security attribute available for encryption encryption_available = hasattr(session.auth, 'session_security') # TODO: ssl is not exactly right here- should really be client_cert elif self.auth_method in ['basic', 'plaintext']: session.auth = requests.auth.HTTPBasicAuth(username=self.username, password=self.password) elif self.auth_method == 'credssp': if not HAVE_CREDSSP: raise WinRMError("requests auth method is credssp, but requests-credssp is not installed") session.auth = HttpCredSSPAuth(username=self.username, password=self.password, disable_tlsv1_2=self.credssp_disable_tlsv1_2) encryption_available = hasattr(session.auth, 'wrap') and hasattr(session.auth, 'unwrap') else: raise WinRMError("unsupported auth method: %s" % self.auth_method) session.headers.update(self.default_headers) self.session = session # Will check the current config and see if we need to setup message encryption if self.message_encryption == 'always' and not encryption_available: raise WinRMError( "message encryption is set to 'always' but the selected auth method %s does not support it" % self.auth_method) elif encryption_available: if self.message_encryption == 'always': self.setup_encryption() elif self.message_encryption == 'auto' and not self.endpoint.lower().startswith('https'): self.setup_encryption() def setup_encryption(self): # Security context doesn't exist, sending blank message to initialise context request = requests.Request('POST', self.endpoint, data=None) prepared_request = self.session.prepare_request(request) self._send_message_request(prepared_request, '') self.encryption = Encryption(self.session, self.auth_method) def send_message(self, message): if not self.session: self.build_session() # urllib3 fails on SSL retries with unicode buffers- must send it a byte string # see https://github.com/shazow/urllib3/issues/717 if isinstance(message, unicode_type): message = message.encode('utf-8') if self.encryption: prepared_request = self.encryption.prepare_encrypted_request(self.session, self.endpoint, message) else: request = requests.Request('POST', self.endpoint, data=message) prepared_request = self.session.prepare_request(request) response = self._send_message_request(prepared_request, message) return self._get_message_response_text(response) def _send_message_request(self, prepared_request, message): try: response = self.session.send(prepared_request, timeout=self.read_timeout_sec) response.raise_for_status() return response except requests.HTTPError as ex: if ex.response.status_code == 401: raise InvalidCredentialsError("the specified credentials were rejected by the server") if ex.response.content: response_text = self._get_message_response_text(ex.response) else: response_text = '' raise WinRMTransportError('http', ex.response.status_code, response_text) def _get_message_response_text(self, response): if self.encryption: response_text = self.encryption.parse_encrypted_response(response) else: response_text = response.content return response_text def _get_args(self, mandatory_args, optional_args, function): argspec = set(inspect.getargspec(function).args) function_args = dict() for name, value in mandatory_args.items(): if name in argspec: function_args[name] = value else: raise Exception("Function %s does not contain mandatory arg " "%s, check installed version with pip list" % (str(function), name)) for name, value in optional_args.items(): if name in argspec: function_args[name] = value else: warnings.warn("Function %s does not contain optional arg %s, " "check installed version with pip list" % (str(function), name)) return function_args pywinrm-0.3.0/winrm/encryption.py0000664000175000017500000002366113212550034017714 0ustar mdavismdavis00000000000000import requests import re import struct import sys from winrm.exceptions import WinRMError is_py2 = sys.version[0] == '2' if is_py2: from urlparse import urlsplit else: from urllib.parse import urlsplit class Encryption(object): SIXTEN_KB = 16384 MIME_BOUNDARY = b'--Encrypted Boundary' def __init__(self, session, protocol): """ [MS-WSMV] v30.0 2016-07-14 2.2.9.1 Encrypted Message Types When using Encryption, there are three options available 1. Negotiate/SPNEGO 2. Kerberos 3. CredSSP Details for each implementation can be found in this document under this section This init sets the following values to use to encrypt and decrypt. This is to help generify the methods used in the body of the class. wrap: A method that will return the encrypted message and a signature unwrap: A method that will return an unencrypted message and verify the signature protocol_string: The protocol string used for the particular auth protocol :param session: The handle of the session to get GSS-API wrap and unwrap methods :param protocol: The auth protocol used, will determine the wrapping and unwrapping method plus the protocol string to use. Currently only NTLM and CredSSP is supported """ self.protocol = protocol self.session = session if protocol == 'ntlm': # Details under Negotiate [2.2.9.1.1] in MS-WSMV self.protocol_string = b"application/HTTP-SPNEGO-session-encrypted" self._build_message = self._build_ntlm_message self._decrypt_message = self._decrypt_ntlm_message elif protocol == 'credssp': # Details under CredSSP [2.2.9.1.3] in MS-WSMV self.protocol_string = b"application/HTTP-CredSSP-session-encrypted" self._build_message = self._build_credssp_message self._decrypt_message = self._decrypt_credssp_message elif protocol == 'kerberos': self.protocol_string = b"application/HTTP-SPNEGO-session-encrypted" self._build_message = self._build_kerberos_message self._decrypt_message = self._decrypt_kerberos_message else: raise WinRMError("Encryption for protocol '%s' not supported in pywinrm" % protocol) def prepare_encrypted_request(self, session, endpoint, message): """ Creates a prepared request to send to the server with an encrypted message and correct headers :param session: The handle of the session to prepare requests with :param endpoint: The endpoint/server to prepare requests to :param message: The unencrypted message to send to the server :return: A prepared request that has an encrypted message """ host = urlsplit(endpoint).hostname if self.protocol == 'credssp' and len(message) > self.SIXTEN_KB: content_type = 'multipart/x-multi-encrypted' encrypted_message = b'' message_chunks = [message[i:i+self.SIXTEN_KB] for i in range(0, len(message), self.SIXTEN_KB)] for message_chunk in message_chunks: encrypted_chunk = self._encrypt_message(message_chunk, host) encrypted_message += encrypted_chunk else: content_type = 'multipart/encrypted' encrypted_message = self._encrypt_message(message, host) encrypted_message += self.MIME_BOUNDARY + b"--\r\n" request = requests.Request('POST', endpoint, data=encrypted_message) prepared_request = session.prepare_request(request) prepared_request.headers['Content-Length'] = str(len(prepared_request.body)) prepared_request.headers['Content-Type'] = '{0};protocol="{1}";boundary="Encrypted Boundary"'\ .format(content_type, self.protocol_string.decode()) return prepared_request def parse_encrypted_response(self, response): """ Takes in the encrypted response from the server and decrypts it :param response: The response that needs to be decrytped :return: The unencrypted message from the server """ content_type = response.headers['Content-Type'] if 'protocol="{0}"'.format(self.protocol_string.decode()) in content_type: host = urlsplit(response.request.url).hostname msg = self._decrypt_response(response, host) else: msg = response.text return msg def _encrypt_message(self, message, host): message_length = str(len(message)).encode() encrypted_stream = self._build_message(message, host) message_payload = self.MIME_BOUNDARY + b"\r\n" \ b"\tContent-Type: " + self.protocol_string + b"\r\n" \ b"\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=" + message_length + b"\r\n" + \ self.MIME_BOUNDARY + b"\r\n" \ b"\tContent-Type: application/octet-stream\r\n" + \ encrypted_stream return message_payload def _decrypt_response(self, response, host): parts = response.content.split(self.MIME_BOUNDARY + b'\r\n') parts = list(filter(None, parts)) # filter out empty parts of the split message = b'' for i in range(0, len(parts)): if i % 2 == 1: continue header = parts[i].strip() payload = parts[i + 1] expected_length = int(header.split(b'Length=')[1]) # remove the end MIME block if it exists if payload.endswith(self.MIME_BOUNDARY + b'--\r\n'): payload = payload[:len(payload) - 24] encrypted_data = payload.replace(b'\tContent-Type: application/octet-stream\r\n', b'') decrypted_message = self._decrypt_message(encrypted_data, host) actual_length = len(decrypted_message) if actual_length != expected_length: raise WinRMError('Encrypted length from server does not match the ' 'expected size, message has been tampered with') message += decrypted_message return message def _decrypt_ntlm_message(self, encrypted_data, host): signature_length = struct.unpack(" http://windows-host:5985/wsman http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 153600 uuid:11111111-1111-1111-1111-111111111111 PT20S http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.xmlsoap.org/ws/2004/09/transfer/Create FALSE 437 stdin stdout stderr """ open_shell_response = """\ http://schemas.xmlsoap.org/ws/2004/09/transfer/CreateResponse uuid:11111111-1111-1111-1111-111111111112 http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous uuid:11111111-1111-1111-1111-111111111111 http://windows-host:5985/wsman http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd 11111111-1111-1111-1111-111111111113 """ close_shell_request = """\ http://windows-host:5985/wsman http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 153600 uuid:11111111-1111-1111-1111-111111111111 PT20S http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete 11111111-1111-1111-1111-111111111113 """ close_shell_response = """\ http://schemas.xmlsoap.org/ws/2004/09/transfer/DeleteResponse uuid:11111111-1111-1111-1111-111111111112 http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous uuid:11111111-1111-1111-1111-111111111111 """ run_cmd_with_args_request = """\ http://windows-host:5985/wsman http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 153600 uuid:11111111-1111-1111-1111-111111111111 PT20S http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command 11111111-1111-1111-1111-111111111113 TRUE FALSE ipconfig /all """ run_cmd_wo_args_request = """\ http://windows-host:5985/wsman http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 153600 uuid:11111111-1111-1111-1111-111111111111 PT20S http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command 11111111-1111-1111-1111-111111111113 TRUE FALSE hostname """ run_cmd_response = """\ http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandResponse uuid:11111111-1111-1111-1111-111111111112 http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous uuid:11111111-1111-1111-1111-111111111111 11111111-1111-1111-1111-111111111114 """ cleanup_cmd_request = """\ http://windows-host:5985/wsman http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 153600 uuid:11111111-1111-1111-1111-111111111111 PT20S http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal 11111111-1111-1111-1111-111111111113 http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate """ cleanup_cmd_response = """\ http://schemas.microsoft.com/wbem/wsman/1/windows/shell/SignalResponse uuid:11111111-1111-1111-1111-111111111112 http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous uuid:11111111-1111-1111-1111-111111111111 """ get_cmd_output_request = """\ http://windows-host:5985/wsman http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 153600 uuid:11111111-1111-1111-1111-111111111111 PT20S http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive 11111111-1111-1111-1111-111111111113 stdout stderr """ get_cmd_output_response = """\ http://schemas.microsoft.com/wbem/wsman/1/windows/shell/ReceiveResponse uuid:11111111-1111-1111-1111-111111111112 http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous uuid:11111111-1111-1111-1111-111111111111 DQpXaW5kb3dzIElQIENvbmZpZ3VyYXRpb24NCg0K ICAgSG9zdCBOYW1lIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogV0lORE9XUy1IT1NUCiAgIFByaW1hcnkgRG5zIFN1ZmZpeCAgLiAuIC4gLiAuIC4gLiA6IAogICBOb2RlIFR5cGUgLiAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBIeWJyaWQKICAgSVAgUm91dGluZyBFbmFibGVkLiAuIC4gLiAuIC4gLiAuIDogTm8KICAgV0lOUyBQcm94eSBFbmFibGVkLiAuIC4gLiAuIC4gLiAuIDogTm8KCkV0aGVybmV0IGFkYXB0ZXIgTG9jYWwgQXJlYSBDb25uZWN0aW9uOgoKICAgQ29ubmVjdGlvbi1zcGVjaWZpYyBETlMgU3VmZml4ICAuIDogCiAgIERlc2NyaXB0aW9uIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IEludGVsKFIpIDgyNTY3Vi0yIEdpZ2FiaXQgTmV0d29yayBDb25uZWN0aW9uCiAgIFBoeXNpY2FsIEFkZHJlc3MuIC4gLiAuIC4gLiAuIC4gLiA6IEY4LTBGLTQxLTE2LTg4LUU4CiAgIERIQ1AgRW5hYmxlZC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IE5vCiAgIEF1dG9jb25maWd1cmF0aW9uIEVuYWJsZWQgLiAuIC4gLiA6IFllcwogICBMaW5rLWxvY2FsIElQdjYgQWRkcmVzcyAuIC4gLiAuIC4gOiBmZTgwOjphOTkwOjM1ZTM6YTZhYjpmYzE1JTEwKFByZWZlcnJlZCkgCiAgIElQdjQgQWRkcmVzcy4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDE3My4xODUuMTUzLjkzKFByZWZlcnJlZCkgCiAgIFN1Ym5ldCBNYXNrIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDI1NS4yNTUuMjU1LjI0OAogICBEZWZhdWx0IEdhdGV3YXkgLiAuIC4gLiAuIC4gLiAuIC4gOiAxNzMuMTg1LjE1My44OQogICBESENQdjYgSUFJRCAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiAyNTExMzc4NTcKICAgREhDUHY2IENsaWVudCBEVUlELiAuIC4gLiAuIC4gLiAuIDogMDAtMDEtMDAtMDEtMTYtM0ItM0YtQzItRjgtMEYtNDEtMTYtODgtRTgKICAgRE5TIFNlcnZlcnMgLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogMjA3LjkxLjUuMzIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMjA4LjY3LjIyMi4yMjIKICAgTmV0QklPUyBvdmVyIFRjcGlwLiAuIC4gLiAuIC4gLiAuIDogRW5hYmxlZAoKRXRoZXJuZXQgYWRhcHRlciBMb2NhbCBBcmVhIENvbm5lY3Rpb24qIDk6CgogICBNZWRpYSBTdGF0ZSAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBNZWRpYSBkaXNjb25uZWN0ZWQKICAgQ29ubmVjdGlvbi1zcGVjaWZpYyBETlMgU3VmZml4ICAuIDogCiAgIERlc2NyaXB0aW9uIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IEp1bmlwZXIgTmV0d29yayBDb25uZWN0IFZpcnR1YWwgQWRhcHRlcgogICBQaHlzaWNhbCBBZGRyZXNzLiAuIC4gLiAuIC4gLiAuIC4gOiAwMC1GRi1BMC04My00OC0wNAogICBESENQIEVuYWJsZWQuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBZZXMKICAgQXV0b2NvbmZpZ3VyYXRpb24gRW5hYmxlZCAuIC4gLiAuIDogWWVzCgpUdW5uZWwgYWRhcHRlciBpc2F0YXAue0FBNDI2QjM3LTM2OTUtNEVCOC05OTBGLTRDRkFDODQ1RkQxN306CgogICBNZWRpYSBTdGF0ZSAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBNZWRpYSBkaXNjb25uZWN0ZWQKICAgQ29ubmVjdGlvbi1zcGVjaWZpYyBETlMgU3VmZml4ICAuIDogCiAgIERlc2NyaXB0aW9uIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IE1pY3Jvc29mdCBJU0FUQVAgQWRhcHRlcgogICBQaHlzaWNhbCBBZGRyZXNzLiAuIC4gLiAuIC4gLiAuIC4gOiAwMC0wMC0wMC0wMC0wMC0wMC0wMC1FMAogICBESENQIEVuYWJsZWQuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBObwogICBBdXRvY29uZmlndXJhdGlvbiBFbmFibGVkIC4gLiAuIC4gOiBZZXMKClR1bm5lbCBhZGFwdGVyIFRlcmVkbyBUdW5uZWxpbmcgUHNldWRvLUludGVyZmFjZToKCiAgIENvbm5lY3Rpb24tc3BlY2lmaWMgRE5TIFN1ZmZpeCAgLiA6IAogICBEZXNjcmlwdGlvbiAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBUZXJlZG8gVHVubmVsaW5nIFBzZXVkby1JbnRlcmZhY2UKICAgUGh5c2ljYWwgQWRkcmVzcy4gLiAuIC4gLiAuIC4gLiAuIDogMDAtMDAtMDAtMDAtMDAtMDAtMDAtRTAKICAgREhDUCBFbmFibGVkLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogTm8KICAgQXV0b2NvbmZpZ3VyYXRpb24gRW5hYmxlZCAuIC4gLiAuIDogWWVzCiAgIElQdjYgQWRkcmVzcy4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IDIwMDE6MDo5ZDM4Ojk1M2M6MmNlZjo3ZmM6NTI0Njo2NmEyKFByZWZlcnJlZCkgCiAgIExpbmstbG9jYWwgSVB2NiBBZGRyZXNzIC4gLiAuIC4gLiA6IGZlODA6OjJjZWY6N2ZjOjUyNDY6NjZhMiUxMyhQcmVmZXJyZWQpIAogICBEZWZhdWx0IEdhdGV3YXkgLiAuIC4gLiAuIC4gLiAuIC4gOiAKICAgTmV0QklPUyBvdmVyIFRjcGlwLiAuIC4gLiAuIC4gLiAuIDogRGlzYWJsZWQKClR1bm5lbCBhZGFwdGVyIDZUTzQgQWRhcHRlcjoKCiAgIENvbm5lY3Rpb24tc3BlY2lmaWMgRE5TIFN1ZmZpeCAgLiA6IAogICBEZXNjcmlwdGlvbiAuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiBNaWNyb3NvZnQgNnRvNCBBZGFwdGVyICMyCiAgIFBoeXNpY2FsIEFkZHJlc3MuIC4gLiAuIC4gLiAuIC4gLiA6IDAwLTAwLTAwLTAwLTAwLTAwLTAwLUUwCiAgIERIQ1AgRW5hYmxlZC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IE5vCiAgIEF1dG9jb25maWd1cmF0aW9uIEVuYWJsZWQgLiAuIC4gLiA6IFllcwogICBJUHY2IEFkZHJlc3MuIC4gLiAuIC4gLiAuIC4gLiAuIC4gOiAyMDAyOmFkYjk6OTk1ZDo6YWRiOTo5OTVkKFByZWZlcnJlZCkgCiAgIERlZmF1bHQgR2F0ZXdheSAuIC4gLiAuIC4gLiAuIC4gLiA6IDIwMDI6YzA1ODo2MzAxOjpjMDU4OjYzMDEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMjAwMjpjMDU4OjYzMDE6OjEKICAgRE5TIFNlcnZlcnMgLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogMjA3LjkxLjUuMzIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMjA4LjY3LjIyMi4yMjIKICAgTmV0QklPUyBvdmVyIFRjcGlwLiAuIC4gLiAuIC4gLiAuIDogRGlzYWJsZWQKClR1bm5lbCBhZGFwdGVyIGlzYXRhcC57QkExNjBGQzUtNzAyOC00QjFGLUEwNEItMUFDODAyQjBGRjVBfToKCiAgIE1lZGlhIFN0YXRlIC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IE1lZGlhIGRpc2Nvbm5lY3RlZAogICBDb25uZWN0aW9uLXNwZWNpZmljIEROUyBTdWZmaXggIC4gOiAKICAgRGVzY3JpcHRpb24gLiAuIC4gLiAuIC4gLiAuIC4gLiAuIDogTWljcm9zb2Z0IElTQVRBUCBBZGFwdGVyICMyCiAgIFBoeXNpY2FsIEFkZHJlc3MuIC4gLiAuIC4gLiAuIC4gLiA6IDAwLTAwLTAwLTAwLTAwLTAwLTAwLUUwCiAgIERIQ1AgRW5hYmxlZC4gLiAuIC4gLiAuIC4gLiAuIC4gLiA6IE5vCiAgIEF1dG9jb25maWd1cmF0aW9uIEVuYWJsZWQgLiAuIC4gLiA6IFllcwo= 0 """ def sort_dict(ordered_dict): items = sorted(ordered_dict.items(), key=lambda x: x[0]) ordered_dict.clear() for key, value in items: if isinstance(value, dict): sort_dict(value) ordered_dict[key] = value def xml_str_compare(first, second): first_dict = xmltodict.parse(first) second_dict = xmltodict.parse(second) sort_dict(first_dict) sort_dict(second_dict) return first_dict == second_dict class TransportStub(object): def send_message(self, message): if xml_str_compare(message, open_shell_request): return open_shell_response elif xml_str_compare(message, close_shell_request): return close_shell_response elif xml_str_compare( message, run_cmd_with_args_request) or xml_str_compare( message, run_cmd_wo_args_request): return run_cmd_response elif xml_str_compare(message, cleanup_cmd_request): return cleanup_cmd_response elif xml_str_compare(message, get_cmd_output_request): return get_cmd_output_response else: raise Exception('Message was not expected') @fixture(scope='module') def protocol_fake(request): uuid4_patcher = patch('uuid.uuid4') uuid4_mock = uuid4_patcher.start() uuid4_mock.return_value = uuid.UUID( '11111111-1111-1111-1111-111111111111') from winrm.protocol import Protocol protocol_fake = Protocol( endpoint='http://windows-host:5985/wsman', transport='plaintext', username='john.smith', password='secret') protocol_fake.transport = TransportStub() def uuid4_patch_stop(): uuid4_patcher.stop() request.addfinalizer(uuid4_patch_stop) return protocol_fake @fixture(scope='module') def protocol_real(): endpoint = os.environ.get('WINRM_ENDPOINT', None) transport = os.environ.get('WINRM_TRANSPORT', None) username = os.environ.get('WINRM_USERNAME', None) password = os.environ.get('WINRM_PASSWORD', None) if endpoint: settings = dict( endpoint=endpoint, operation_timeout_sec=5, read_timeout_sec=7 ) if transport: settings['transport'] = transport if username: settings['username'] = username if password: settings['password'] = password from winrm.protocol import Protocol protocol = Protocol(**settings) return protocol else: skip('WINRM_ENDPOINT environment variable was not set. Integration tests will be skipped') pywinrm-0.3.0/winrm/tests/test_nori_type_casting.py0000664000175000017500000000000013207035244023426 0ustar mdavismdavis00000000000000pywinrm-0.3.0/winrm/tests/test_integration_session.py0000664000175000017500000000014713207035244024010 0ustar mdavismdavis00000000000000import pytest xfail = pytest.mark.xfail @xfail() def test_run_cmd(): raise NotImplementedError() pywinrm-0.3.0/winrm/tests/__init__.py0000664000175000017500000000000013207035244020406 0ustar mdavismdavis00000000000000pywinrm-0.3.0/winrm/tests/test_powershell.py0000664000175000017500000000000013207035244022072 0ustar mdavismdavis00000000000000pywinrm-0.3.0/winrm/tests/test_encryption.py0000664000175000017500000002747413212550254022127 0ustar mdavismdavis00000000000000import base64 import pytest import struct from winrm.encryption import Encryption from winrm.exceptions import WinRMError def test_init_with_invalid_protocol(): with pytest.raises(WinRMError) as excinfo: Encryption(None, 'invalid_protocol') assert "Encryption for protocol 'invalid_protocol' not supported in pywinrm" in str(excinfo.value) def test_encrypt_message(): test_session = SessionTest() test_message = b"unencrypted message" test_endpoint = b"endpoint" encryption = Encryption(test_session, 'ntlm') actual = encryption.prepare_encrypted_request(test_session, test_endpoint, test_message) expected_encrypted_message = b"dW5lbmNyeXB0ZWQgbWVzc2FnZQ==" expected_signature = b"1234" signature_length = struct.pack("= read_timeout_sec or operation_timeout_sec < 1: raise WinRMError("read_timeout_sec must exceed operation_timeout_sec, and both must be non-zero") self.read_timeout_sec = read_timeout_sec self.operation_timeout_sec = operation_timeout_sec self.max_env_sz = Protocol.DEFAULT_MAX_ENV_SIZE self.locale = Protocol.DEFAULT_LOCALE self.transport = Transport( endpoint=endpoint, username=username, password=password, realm=realm, service=service, keytab=keytab, ca_trust_path=ca_trust_path, cert_pem=cert_pem, cert_key_pem=cert_key_pem, read_timeout_sec=self.read_timeout_sec, server_cert_validation=server_cert_validation, kerberos_delegation=kerberos_delegation, kerberos_hostname_override=kerberos_hostname_override, auth_method=transport, message_encryption=message_encryption, credssp_disable_tlsv1_2=credssp_disable_tlsv1_2, send_cbt=send_cbt ) self.username = username self.password = password self.service = service self.keytab = keytab self.ca_trust_path = ca_trust_path self.server_cert_validation = server_cert_validation self.kerberos_delegation = kerberos_delegation self.kerberos_hostname_override = kerberos_hostname_override self.credssp_disable_tlsv1_2 = credssp_disable_tlsv1_2 def open_shell(self, i_stream='stdin', o_stream='stdout stderr', working_directory=None, env_vars=None, noprofile=False, codepage=437, lifetime=None, idle_timeout=None): """ Create a Shell on the destination host @param string i_stream: Which input stream to open. Leave this alone unless you know what you're doing (default: stdin) @param string o_stream: Which output stream to open. Leave this alone unless you know what you're doing (default: stdout stderr) @param string working_directory: the directory to create the shell in @param dict env_vars: environment variables to set for the shell. For instance: {'PATH': '%PATH%;c:/Program Files (x86)/Git/bin/', 'CYGWIN': 'nontsec codepage:utf8'} @returns The ShellId from the SOAP response. This is our open shell instance on the remote machine. @rtype string """ req = {'env:Envelope': self._get_soap_header( resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd', # NOQA action='http://schemas.xmlsoap.org/ws/2004/09/transfer/Create')} header = req['env:Envelope']['env:Header'] header['w:OptionSet'] = { 'w:Option': [ { '@Name': 'WINRS_NOPROFILE', '#text': str(noprofile).upper() # TODO remove str call }, { '@Name': 'WINRS_CODEPAGE', '#text': str(codepage) # TODO remove str call } ] } shell = req['env:Envelope'].setdefault( 'env:Body', {}).setdefault('rsp:Shell', {}) shell['rsp:InputStreams'] = i_stream shell['rsp:OutputStreams'] = o_stream if working_directory: # TODO ensure that rsp:WorkingDirectory should be nested within rsp:Shell # NOQA shell['rsp:WorkingDirectory'] = working_directory # TODO check Lifetime param: http://msdn.microsoft.com/en-us/library/cc251546(v=PROT.13).aspx # NOQA #if lifetime: # shell['rsp:Lifetime'] = iso8601_duration.sec_to_dur(lifetime) # TODO make it so the input is given in milliseconds and converted to xs:duration # NOQA if idle_timeout: shell['rsp:IdleTimeOut'] = idle_timeout if env_vars: env = shell.setdefault('rsp:Environment', {}) for key, value in env_vars.items(): env['rsp:Variable'] = {'@Name': key, '#text': value} res = self.send_message(xmltodict.unparse(req)) #res = xmltodict.parse(res) #return res['s:Envelope']['s:Body']['x:ResourceCreated']['a:ReferenceParameters']['w:SelectorSet']['w:Selector']['#text'] root = ET.fromstring(res) return next( node for node in root.findall('.//*') if node.get('Name') == 'ShellId').text # Helper method for building SOAP Header def _get_soap_header( self, action=None, resource_uri=None, shell_id=None, message_id=None): if not message_id: message_id = uuid.uuid4() header = { '@xmlns:xsd': 'http://www.w3.org/2001/XMLSchema', '@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', '@xmlns:env': xmlns['soapenv'], '@xmlns:a': xmlns['soapaddr'], '@xmlns:b': 'http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd', '@xmlns:n': 'http://schemas.xmlsoap.org/ws/2004/09/enumeration', '@xmlns:x': 'http://schemas.xmlsoap.org/ws/2004/09/transfer', '@xmlns:w': 'http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd', '@xmlns:p': 'http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd', '@xmlns:rsp': 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell', # NOQA '@xmlns:cfg': 'http://schemas.microsoft.com/wbem/wsman/1/config', 'env:Header': { 'a:To': 'http://windows-host:5985/wsman', 'a:ReplyTo': { 'a:Address': { '@mustUnderstand': 'true', '#text': 'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous' # NOQA } }, 'w:MaxEnvelopeSize': { '@mustUnderstand': 'true', '#text': '153600' }, 'a:MessageID': 'uuid:{0}'.format(message_id), 'w:Locale': { '@mustUnderstand': 'false', '@xml:lang': 'en-US' }, 'p:DataLocale': { '@mustUnderstand': 'false', '@xml:lang': 'en-US' }, # TODO: research this a bit http://msdn.microsoft.com/en-us/library/cc251561(v=PROT.13).aspx # NOQA # 'cfg:MaxTimeoutms': 600 # Operation timeout in ISO8601 format, see http://msdn.microsoft.com/en-us/library/ee916629(v=PROT.13).aspx # NOQA 'w:OperationTimeout': 'PT{0}S'.format(int(self.operation_timeout_sec)), 'w:ResourceURI': { '@mustUnderstand': 'true', '#text': resource_uri }, 'a:Action': { '@mustUnderstand': 'true', '#text': action } } } if shell_id: header['env:Header']['w:SelectorSet'] = { 'w:Selector': { '@Name': 'ShellId', '#text': shell_id } } return header def send_message(self, message): # TODO add message_id vs relates_to checking # TODO port error handling code try: resp = self.transport.send_message(message) return resp except WinRMTransportError as ex: try: # if response is XML-parseable, it's probably a SOAP fault; extract the details root = ET.fromstring(ex.response_text) except: # assume some other transport error; raise the original exception raise ex fault = root.find('soapenv:Body/soapenv:Fault', xmlns) if fault is not None: fault_data = dict( transport_message=ex.message, http_status_code=ex.code ) wsmanfault_code = fault.find('soapenv:Detail/wsmanfault:WSManFault[@Code]', xmlns) if wsmanfault_code is not None: fault_data['wsmanfault_code'] = wsmanfault_code.get('Code') # convert receive timeout code to WinRMOperationTimeoutError if fault_data['wsmanfault_code'] == '2150858793': # TODO: this fault code is specific to the Receive operation; convert all op timeouts? raise WinRMOperationTimeoutError() fault_code = fault.find('soapenv:Code/soapenv:Value', xmlns) if fault_code is not None: fault_data['fault_code'] = fault_code.text fault_subcode = fault.find('soapenv:Code/soapenv:Subcode/soapenv:Value', xmlns) if fault_subcode is not None: fault_data['fault_subcode'] = fault_subcode.text error_message = fault.find('soapenv:Reason/soapenv:Text', xmlns) if error_message is not None: error_message = error_message.text else: error_message = "(no error message in fault)" raise WinRMError('{0} (extended fault data: {1})'.format(error_message, fault_data)) def close_shell(self, shell_id): """ Close the shell @param string shell_id: The shell id on the remote machine. See #open_shell @returns This should have more error checking but it just returns true for now. @rtype bool """ message_id = uuid.uuid4() req = {'env:Envelope': self._get_soap_header( resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd', # NOQA action='http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete', shell_id=shell_id, message_id=message_id)} # SOAP message requires empty env:Body req['env:Envelope'].setdefault('env:Body', {}) res = self.send_message(xmltodict.unparse(req)) root = ET.fromstring(res) relates_to = next( node for node in root.findall('.//*') if node.tag.endswith('RelatesTo')).text # TODO change assert into user-friendly exception assert uuid.UUID(relates_to.replace('uuid:', '')) == message_id def run_command( self, shell_id, command, arguments=(), console_mode_stdin=True, skip_cmd_shell=False): """ Run a command on a machine with an open shell @param string shell_id: The shell id on the remote machine. See #open_shell @param string command: The command to run on the remote machine @param iterable of string arguments: An array of arguments for this command @param bool console_mode_stdin: (default: True) @param bool skip_cmd_shell: (default: False) @return: The CommandId from the SOAP response. This is the ID we need to query in order to get output. @rtype string """ req = {'env:Envelope': self._get_soap_header( resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd', # NOQA action='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command', # NOQA shell_id=shell_id)} header = req['env:Envelope']['env:Header'] header['w:OptionSet'] = { 'w:Option': [ { '@Name': 'WINRS_CONSOLEMODE_STDIN', '#text': str(console_mode_stdin).upper() }, { '@Name': 'WINRS_SKIP_CMD_SHELL', '#text': str(skip_cmd_shell).upper() } ] } cmd_line = req['env:Envelope'].setdefault( 'env:Body', {}).setdefault('rsp:CommandLine', {}) cmd_line['rsp:Command'] = {'#text': command} if arguments: unicode_args = [a if isinstance(a, text_type) else a.decode('utf-8') for a in arguments] cmd_line['rsp:Arguments'] = u' '.join(unicode_args) res = self.send_message(xmltodict.unparse(req)) root = ET.fromstring(res) command_id = next( node for node in root.findall('.//*') if node.tag.endswith('CommandId')).text return command_id def cleanup_command(self, shell_id, command_id): """ Clean-up after a command. @see #run_command @param string shell_id: The shell id on the remote machine. See #open_shell @param string command_id: The command id on the remote machine. See #run_command @returns: This should have more error checking but it just returns true for now. @rtype bool """ message_id = uuid.uuid4() req = {'env:Envelope': self._get_soap_header( resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd', # NOQA action='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal', # NOQA shell_id=shell_id, message_id=message_id)} # Signal the Command references to terminate (close stdout/stderr) signal = req['env:Envelope'].setdefault( 'env:Body', {}).setdefault('rsp:Signal', {}) signal['@CommandId'] = command_id signal['rsp:Code'] = 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate' # NOQA res = self.send_message(xmltodict.unparse(req)) root = ET.fromstring(res) relates_to = next( node for node in root.findall('.//*') if node.tag.endswith('RelatesTo')).text # TODO change assert into user-friendly exception assert uuid.UUID(relates_to.replace('uuid:', '')) == message_id def get_command_output(self, shell_id, command_id): """ Get the Output of the given shell and command @param string shell_id: The shell id on the remote machine. See #open_shell @param string command_id: The command id on the remote machine. See #run_command #@return [Hash] Returns a Hash with a key :exitcode and :data. Data is an Array of Hashes where the cooresponding key # is either :stdout or :stderr. The reason it is in an Array so so we can get the output in the order it ocurrs on # the console. """ stdout_buffer, stderr_buffer = [], [] command_done = False while not command_done: try: stdout, stderr, return_code, command_done = \ self._raw_get_command_output(shell_id, command_id) stdout_buffer.append(stdout) stderr_buffer.append(stderr) except WinRMOperationTimeoutError as e: # this is an expected error when waiting for a long-running process, just silently retry pass return b''.join(stdout_buffer), b''.join(stderr_buffer), return_code def _raw_get_command_output(self, shell_id, command_id): req = {'env:Envelope': self._get_soap_header( resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd', # NOQA action='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive', # NOQA shell_id=shell_id)} stream = req['env:Envelope'].setdefault('env:Body', {}).setdefault( 'rsp:Receive', {}).setdefault('rsp:DesiredStream', {}) stream['@CommandId'] = command_id stream['#text'] = 'stdout stderr' res = self.send_message(xmltodict.unparse(req)) root = ET.fromstring(res) stream_nodes = [ node for node in root.findall('.//*') if node.tag.endswith('Stream')] stdout = stderr = b'' return_code = -1 for stream_node in stream_nodes: if not stream_node.text: continue if stream_node.attrib['Name'] == 'stdout': stdout += base64.b64decode(stream_node.text.encode('ascii')) elif stream_node.attrib['Name'] == 'stderr': stderr += base64.b64decode(stream_node.text.encode('ascii')) # We may need to get additional output if the stream has not finished. # The CommandState will change from Running to Done like so: # @example # from... # # to... # # 0 # command_done = len([ node for node in root.findall('.//*') if node.get('State', '').endswith('CommandState/Done')]) == 1 if command_done: return_code = int( next(node for node in root.findall('.//*') if node.tag.endswith('ExitCode')).text) return stdout, stderr, return_code, command_done pywinrm-0.3.0/winrm/exceptions.py0000664000175000017500000000231013212113364017670 0ustar mdavismdavis00000000000000from __future__ import unicode_literals class WinRMError(Exception): """"Generic WinRM error""" code = 500 class WinRMTransportError(Exception): """WinRM errors specific to transport-level problems (unexpcted HTTP error codes, etc)""" @property def protocol(self): return self.args[0] @property def code(self): return self.args[1] @property def message(self): return 'Bad HTTP response returned from server. Code {0}'.format(self.code) @property def response_text(self): return self.args[2] def __str__(self): return self.message class WinRMOperationTimeoutError(Exception): """ Raised when a WinRM-level operation timeout (not a connection-level timeout) has occurred. This is considered a normal error that should be retried transparently by the client when waiting for output from a long-running process. """ code = 500 class AuthenticationError(WinRMError): """Authorization Error""" code = 401 class BasicAuthDisabledError(AuthenticationError): message = 'WinRM/HTTP Basic authentication is not enabled on remote host' class InvalidCredentialsError(AuthenticationError): pass pywinrm-0.3.0/README.md0000664000175000017500000002160113212627324015272 0ustar mdavismdavis00000000000000# pywinrm pywinrm is a Python client for the Windows Remote Management (WinRM) service. It allows you to invoke commands on target Windows machines from any machine that can run Python. [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/diyan/pywinrm/blob/master/LICENSE) [![Travis Build](https://travis-ci.org/diyan/pywinrm.svg)](https://travis-ci.org/diyan/pywinrm) [![AppVeyor Build](https://ci.appveyor.com/api/projects/status/github/diyan/pywinrm?svg=true)](https://ci.appveyor.com/project/diyan/pywinrm) [![Coverage](https://coveralls.io/repos/diyan/pywinrm/badge.svg)](https://coveralls.io/r/diyan/pywinrm) [![PyPI](https://img.shields.io/pypi/dm/pywinrm.svg)](https://pypi.python.org/pypi/pywinrm) WinRM allows you to perform various management tasks remotely. These include, but are not limited to: running batch scripts, powershell scripts, and fetching WMI variables. Used by [Ansible](https://www.ansible.com/) for Windows support. For more information on WinRM, please visit [Microsoft's WinRM site](http://msdn.microsoft.com/en-us/library/aa384426.aspx). ## Requirements * Linux, Mac OS X or Windows * CPython 2.6-2.7, 3.3-3.5 or PyPy2 * [requests-kerberos](http://pypi.python.org/pypi/requests-kerberos) and [requests-credssp](https://github.com/jborean93/requests-credssp) is optional ## Installation ### To install pywinrm with support for basic, certificate, and NTLM auth, simply ```bash $ pip install pywinrm ``` ### To use Kerberos authentication you need these optional dependencies ```bash # for Debian/Ubuntu/etc: $ sudo apt-get install gcc python-dev libkrb5-dev $ pip install pywinrm[kerberos] # for RHEL/CentOS/etc: $ sudo yum install gcc python-devel krb5-devel krb5-workstation python-devel $ pip install pywinrm[kerberos] ``` ### To use CredSSP authentication you need these optional dependencies ```bash # for Debian/Ubuntu/etc: $ sudo apt-get install gcc python-dev libssl-dev $ pip install pywinrm[credssp] # for RHEL/CentOS/etc: $ sudo yum install gcc python-devel openssl-devel $ pip install pywinrm[credssp] ``` ## Example Usage ### Run a process on a remote host ```python import winrm s = winrm.Session('windows-host.example.com', auth=('john.smith', 'secret')) r = s.run_cmd('ipconfig', ['/all']) >>> r.status_code 0 >>> r.std_out Windows IP Configuration Host Name . . . . . . . . . . . . : WINDOWS-HOST Primary Dns Suffix . . . . . . . : Node Type . . . . . . . . . . . . : Hybrid IP Routing Enabled. . . . . . . . : No WINS Proxy Enabled. . . . . . . . : No ... >>> r.std_err ``` NOTE: pywinrm will try and guess the correct endpoint url from the following formats: - windows-host -> http://windows-host:5985/wsman - windows-host:1111 -> http://windows-host:1111/wsman - http://windows-host -> http://windows-host:5985/wsman - http://windows-host:1111 -> http://windows-host:1111/wsman - http://windows-host:1111/wsman -> http://windows-host:1111/wsman ### Run Powershell script on remote host ```python import winrm ps_script = """$strComputer = $Host Clear $RAM = WmiObject Win32_ComputerSystem $MB = 1048576 "Installed Memory: " + [int]($RAM.TotalPhysicalMemory /$MB) + " MB" """ s = winrm.Session('windows-host.example.com', auth=('john.smith', 'secret')) r = s.run_ps(ps_script) >>> r.status_code 0 >>> r.std_out Installed Memory: 3840 MB >>> r.std_err ``` Powershell scripts will be base64 UTF16 little-endian encoded prior to sending to the Windows host. Error messages are converted from the Powershell CLIXML format to a human readable format as a convenience. ### Run process with low-level API with domain user, disabling HTTPS cert validation ```python from winrm.protocol import Protocol p = Protocol( endpoint='https://windows-host:5986/wsman', transport='ntlm', username=r'somedomain\someuser', password='secret', server_cert_validation='ignore') shell_id = p.open_shell() command_id = p.run_command(shell_id, 'ipconfig', ['/all']) std_out, std_err, status_code = p.get_command_output(shell_id, command_id) p.cleanup_command(shell_id, command_id) p.close_shell(shell_id) ``` ### Valid transport options pywinrm supports various transport methods in order to authenticate with the WinRM server. The options that are supported in the `transport` parameter are; * `basic`: Basic auth only works for local Windows accounts not domain accounts. Credentials are base64 encoded when sending to the server. * `plaintext`: Same as basic auth. * `certificate`: Authentication is done through a certificate that is mapped to a local Windows account on the server. * `ssl`: When used in conjunction with `cert_pem` and `cert_key_pem` it will use a certificate as above. If not will revert to basic auth over HTTPS. * `kerberos`: Will use Kerberos authentication for domain accounts which only works when the client is in the same domain as the server and the required dependencies are installed. Currently a Kerberos ticket needs to be initiliased outside of pywinrm using the kinit command. * `ntlm`: Will use NTLM authentication for both domain and local accounts. * `credssp`: Will use CredSSP authentication for both domain and local accounts. Allows double hop authentication. This only works over a HTTPS endpoint and not HTTP. ### Encryption By default WinRM will not accept unencrypted messages from a client and Pywinrm currently has 2 ways to do this. 1. Using a HTTPS endpoint instead of HTTP (Recommended) 2. Use NTLM or CredSSP as the transport auth and setting `message_encryption` to `auto` or `always` Using a HTTPS endpoint is recommended as it will encrypt all the data sent through to the server including the credentials and works with all transport auth types. You can use [this script](https://github.com/ansible/ansible/blob/devel/examples/scripts/ConfigureRemotingForAnsible.ps1) to easily set up a HTTPS endpoint on WinRM with a self signed certificate but in a production environment this should be hardened with your own process. The second option is to use NTLM or CredSSP and set the `message_encryption` arg to protocol to `auto` or `always`. This will use the authentication GSS-API Wrap and Unwrap methods if available to encrypt the message contents sent to the server. This form of encryption is independent from the transport layer like TLS and is currently only supported by the NTLM and CredSSP transport auth. Kerberos currently does not have the methods available to achieve this. To configure message encryption you can use the `message_encryption` argument when initialising protocol. This option has 3 values that can be set as shown below. * `auto`: Default, Will only use message encryption if it is available for the auth method and HTTPS isn't used. * `never`: Will never use message encryption even when not over HTTPS. * `always`: Will always use message encryption even when running over HTTPS. If you set the value to `always` and the transport opt doesn't support message encryption i.e. Basic auth, Pywinrm will throw an exception. If you do not use a HTTPS endpoint or message encryption then the Windows server will automatically reject Pywinrm. You can change the settings on the Windows server to allow unencrypted messages and credentials but this highly insecure and shouldn't be used unless necessary. To allow unencrypted message run the following command either from cmd or powershell ``` # from cmd winrm set winrm/config/service @{AllowUnencrypted="true"} # or from powershell Set-Item -Path "WSMan:\localhost\Service\AllowUnencrypted" -Value $true ``` As a repeat this should definitely not be used as your credentials and messages will allow anybody to see what is sent over the wire. ### Enabling WinRM on remote host Enable WinRM over HTTP and HTTPS with self-signed certificate (includes firewall rules): ``` # from powershell: Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1')) ``` Enable WinRM over HTTP for test usage (includes firewall rules): ``` winrm quickconfig ``` Enable WinRM basic authentication. For domain users, it is necessary to use NTLM, Kerberos or CredSSP authentication (Kerberos and NTLM authentication are enabled by default CredSSP isn't). ``` # from cmd: winrm set winrm/config/service/auth @{Basic="true"} ``` Enable WinRM CredSSP authentication. This allows double hop support so you can authenticate with a network service when running command son the remote host. This command is run in Powershell. ```powershell Enable-WSManCredSSP -Role Server -Force Set-Item -Path "WSMan:\localhost\Service\Auth\CredSSP" -Value $true ``` ### Contributors (alphabetically) - Alessandro Pilotti - Alexey Diyan - Chris Church - David Cournapeau - Gema Gomez - Jijo Varghese - Jordan Borean - Juan J. Martinez - Lukas Bednar - Manuel Sabban - Matt Clark - Matt Davis - Maxim Kovgan - Nir Cohen - Patrick Dunnigan - Reina Abolofia Want to help - send a pull request. I will accept good pull requests for sure. pywinrm-0.3.0/setup.py0000664000175000017500000000363013216534432015530 0ustar mdavismdavis00000000000000from setuptools import setup __version__ = '0.3.0' project_name = 'pywinrm' # PyPi supports only reStructuredText, so pandoc should be installed # before uploading package try: import pypandoc long_description = pypandoc.convert('README.md', 'rst') except ImportError: long_description = '' setup( name=project_name, version=__version__, description='Python library for Windows Remote Management', long_description=long_description, keywords='winrm ws-man devops ws-management'.split(' '), author='Alexey Diyan', author_email='alexey.diyan@gmail.com', url='http://github.com/diyan/pywinrm/', license='MIT license', packages=('winrm', 'winrm.tests'), package_data={'winrm.tests': ['*.ps1']}, install_requires=['xmltodict', 'requests>=2.9.1', 'requests_ntlm>=0.3.0', 'six'], extras_require = dict(kerberos=['requests-kerberos>=0.10.0'], credssp=['requests-credssp>=0.0.1']), classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Natural Language :: English', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: System :: Clustering', 'Topic :: System :: Distributed Computing', 'Topic :: System :: Systems Administration' ], ) pywinrm-0.3.0/setup.cfg0000664000175000017500000000026713216534570015645 0ustar mdavismdavis00000000000000[bdist_rpm] requires = python-xmltodict [bdist_wheel] universal = 1 [tool:pytest] norecursedirs = .git .idea env pep8ignore = tests/*.py E501 [egg_info] tag_build = tag_date = 0