Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 48 additions & 46 deletions winrm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,53 @@
FEATURE_PROXY_SUPPORT = True


def clean_error_msg(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(b"#< 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 = _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 original string
warnings.warn(
"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
return new_msg.strip().encode('utf-8')

# either failed to decode CLIXML or there was nothing to decode
# just return the original message
return msg


def _strip_namespace(xml):
"""strips any namespaces from an xml string"""
p = re.compile(b"xmlns=*[\"\"][^\"\"]*[\"\"]")
allmatches = p.finditer(xml)
for match in allmatches:
xml = xml.replace(match.group(), b"")
return xml


class Response(object):
"""Response from a remote command execution"""
def __init__(self, args):
Expand Down Expand Up @@ -54,54 +101,9 @@ def run_ps(self, script):
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)
rs.std_err = 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(b"#< 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 original string
warnings.warn(
"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
return new_msg.strip().encode('utf-8')

# either failed to decode CLIXML or there was nothing to decode
# just return the original message
return msg

def _strip_namespace(self, xml):
"""strips any namespaces from an xml string"""
p = re.compile(b"xmlns=*[\"\"][^\"\"]*[\"\"]")
allmatches = p.finditer(xml)
for match in allmatches:
xml = xml.replace(match.group(), b"")
return xml

@staticmethod
def _build_url(target, transport):
match = re.match(
Expand Down
9 changes: 5 additions & 4 deletions winrm/tests/test_session.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from winrm import Session
from winrm import clean_error_msg


def test_run_cmd(protocol_fake):
Expand Down Expand Up @@ -62,23 +63,23 @@ def test_decode_clixml_error():
s = Session('windows-host.example.com', auth=('john.smith', 'secret'))
msg = b'#< CLIXML\r\n<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj><Obj S="progress" RefId="1"><TNRef RefId="0" /><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj><S S="Error">fake : The term \'fake\' is not recognized as the name of a cmdlet, function, script file, or operable program. Check _x000D__x000A_</S><S S="Error">the spelling of the name, or if a path was included, verify that the path is correct and try again._x000D__x000A_</S><S S="Error">At line:1 char:1_x000D__x000A_</S><S S="Error">+ fake cmdlet_x000D__x000A_</S><S S="Error">+ ~~~~_x000D__x000A_</S><S S="Error"> + CategoryInfo : ObjectNotFound: (fake:String) [], CommandNotFoundException_x000D__x000A_</S><S S="Error"> + FullyQualifiedErrorId : CommandNotFoundException_x000D__x000A_</S><S S="Error"> _x000D__x000A_</S></Objs>'
expected = b"fake : The term 'fake' is not recognized as the name of a cmdlet, function, script file, or operable program. Check \nthe spelling of the name, or if a path was included, verify that the path is correct and try again.\nAt line:1 char:1\n+ fake cmdlet\n+ ~~~~\n + CategoryInfo : ObjectNotFound: (fake:String) [], CommandNotFoundException\n + FullyQualifiedErrorId : CommandNotFoundException"
actual = s._clean_error_msg(msg)
actual = clean_error_msg(msg)
assert actual == expected


def test_decode_clixml_no_clixml():
s = Session('windows-host.example.com', auth=('john.smith', 'secret'))
msg = b"stderr line"
expected = b"stderr line"
actual = s._clean_error_msg(msg)
actual = clean_error_msg(msg)
assert actual == expected


def test_decode_clixml_no_errors():
s = Session('windows-host.example.com', auth=('john.smith', 'secret'))
msg = b'#< CLIXML\r\n<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj><Obj S="progress" RefId="1"><TNRef RefId="0" /><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>'
expected = msg
actual = s._clean_error_msg(msg)
actual = clean_error_msg(msg)
assert actual == expected


Expand All @@ -87,6 +88,6 @@ def test_decode_clixml_invalid_xml():
msg = b'#< CLIXML\r\n<in >dasf<?dsfij>'

with pytest.warns(UserWarning, match="There was a problem converting the Powershell error message"):
actual = s._clean_error_msg(msg)
actual = clean_error_msg(msg)

assert actual == msg