Python library for Windows Remote Management (WinRM) service enabling remote command execution and PowerShell scripts on Windows machines.
—
Comprehensive exception hierarchy for handling WinRM-specific errors, transport failures, and authentication issues. PyWinRM provides detailed error information to help diagnose connection problems, authentication failures, and WinRM service issues.
PyWinRM defines a structured exception hierarchy for different types of failures.
# Base exception classes
class WinRMError(Exception):
"""Generic WinRM error with HTTP status code."""
code = 500
class WinRMTransportError(Exception):
"""Transport-level HTTP errors (unexpected status codes)."""
@property
def protocol(self) -> str: ...
@property
def code(self) -> int: ...
@property
def message(self) -> str: ...
@property
def response_text(self) -> str: ...
class WinRMOperationTimeoutError(Exception):
"""WinRM operation timeout (retryable)."""
code = 500
# Authentication exceptions
class AuthenticationError(WinRMError):
"""Base authentication error."""
code = 401
class BasicAuthDisabledError(AuthenticationError):
"""HTTP Basic authentication is disabled on remote host."""
message = "WinRM/HTTP Basic authentication is not enabled on remote host"
class InvalidCredentialsError(AuthenticationError):
"""Invalid username/password credentials."""Detailed WSMan protocol fault information for diagnosing WinRM service errors.
class WSManFaultError(WinRMError):
"""
WSMan fault with detailed error information.
Contains raw response and parsed fault details from WSMan service.
Provides both Microsoft-specific codes and standard WSMan fault data.
"""
def __init__(
self,
code: int,
message: str,
response: str,
reason: str,
fault_code: str | None = None,
fault_subcode: str | None = None,
wsman_fault_code: int | None = None,
wmierror_code: int | None = None
):
"""
Initialize WSMan fault error with detailed fault information.
Parameters:
- code: HTTP status code of the response
- message: error message from transport layer
- response: raw WSMan response text
- reason: WSMan fault reason text
- fault_code: WSMan fault code string
- fault_subcode: WSMan fault subcode string
- wsman_fault_code: Microsoft WSManFault specific code
- wmierror_code: Microsoft WMI error code (for quota violations, etc.)
"""
# Fault information attributes
code: int # HTTP status code
response: str # Raw WSMan response
fault_code: str | None # WSMan fault code
fault_subcode: str | None # WSMan fault subcode
reason: str # Fault reason text
wsman_fault_code: int | None # MS-specific fault code
wmierror_code: int | None # WMI error codeimport winrm
from winrm.exceptions import (
WinRMError, WSManFaultError, WinRMTransportError,
AuthenticationError, WinRMOperationTimeoutError
)
def safe_winrm_operation(hostname, username, password, command):
"""Execute WinRM command with comprehensive error handling."""
try:
s = winrm.Session(hostname, auth=(username, password))
r = s.run_cmd(command)
if r.status_code == 0:
return r.std_out.decode('utf-8')
else:
print(f"Command failed with exit code: {r.status_code}")
print(f"Error output: {r.std_err.decode('utf-8')}")
return None
except AuthenticationError as e:
print(f"Authentication failed: {e}")
print("Check username, password, and authentication method")
return None
except WSManFaultError as e:
print(f"WSMan fault: {e.reason}")
print(f"HTTP Status: {e.code}")
if e.wsman_fault_code:
print(f"WSMan Fault Code: {e.wsman_fault_code}")
if e.wmierror_code:
print(f"WMI Error Code: {e.wmierror_code}")
return None
except WinRMTransportError as e:
print(f"Transport error: {e.message}")
print(f"HTTP Status: {e.code}")
print(f"Response: {e.response_text[:200]}...")
return None
except WinRMOperationTimeoutError as e:
print("Operation timed out - this is often retryable")
return None
except WinRMError as e:
print(f"General WinRM error: {e}")
return None
except Exception as e:
print(f"Unexpected error: {e}")
return None
# Usage
result = safe_winrm_operation('windows-host', 'user', 'password', 'hostname')
if result:
print(f"Hostname: {result.strip()}")def try_authentication_methods(hostname, username, password):
"""Try different authentication methods until one succeeds."""
auth_methods = [
('ntlm', {'transport': 'ntlm', 'message_encryption': 'auto'}),
('ssl', {'transport': 'ssl'}),
('kerberos', {'transport': 'kerberos'}),
('basic', {'transport': 'basic'})
]
for method_name, kwargs in auth_methods:
try:
print(f"Trying {method_name} authentication...")
s = winrm.Session(hostname, auth=(username, password), **kwargs)
r = s.run_cmd('echo', ['test'])
if r.status_code == 0:
print(f"Success with {method_name} authentication")
return s
except BasicAuthDisabledError:
print("Basic authentication is disabled on the server")
continue
except InvalidCredentialsError:
print("Invalid credentials - check username and password")
break # Don't try other methods with bad credentials
except AuthenticationError as e:
print(f"Authentication failed with {method_name}: {e}")
continue
except Exception as e:
print(f"Error with {method_name}: {e}")
continue
print("All authentication methods failed")
return None
# Usage
session = try_authentication_methods('windows-host', 'user', 'password')
if session:
# Use successful session
r = session.run_cmd('systeminfo')def analyze_wsman_fault(fault_error):
"""Analyze WSMan fault error and provide troubleshooting guidance."""
analysis = {
'http_status': fault_error.code,
'fault_reason': fault_error.reason,
'fault_code': fault_error.fault_code,
'wsman_code': fault_error.wsman_fault_code,
'wmi_code': fault_error.wmierror_code,
'guidance': []
}
# Common fault code analysis
if fault_error.fault_code == 'a:ActionNotSupported':
analysis['guidance'].append("The requested WS-Management action is not supported")
analysis['guidance'].append("Check WinRM service configuration and Windows version")
elif fault_error.fault_code == 'a:AccessDenied':
analysis['guidance'].append("Access denied - check user permissions")
analysis['guidance'].append("User may need 'Log on as a service' or WinRM permissions")
elif fault_error.fault_code == 'a:QuotaLimit':
analysis['guidance'].append("WinRM quota limit exceeded")
analysis['guidance'].append("Increase MaxShellsPerUser or MaxConcurrentOperationsPerUser")
# Microsoft-specific fault codes
if fault_error.wsman_fault_code:
wsman_code = fault_error.wsman_fault_code
if wsman_code == 2150858793: # 0x80338029
analysis['guidance'].append("WinRM service is not running or not properly configured")
elif wsman_code == 2150858770: # 0x80338012
analysis['guidance'].append("The specified resource URI is not supported")
# WMI error codes
if fault_error.wmierror_code:
wmi_code = fault_error.wmierror_code
if wmi_code == 2147749889: # 0x80041001
analysis['guidance'].append("WMI: General failure")
elif wmi_code == 2147749890: # 0x80041002
analysis['guidance'].append("WMI: Object not found")
return analysis
# Usage in exception handler
try:
s = winrm.Session('windows-host', auth=('user', 'password'))
r = s.run_cmd('invalidcommand')
except WSManFaultError as e:
analysis = analyze_wsman_fault(e)
print(f"WSMan Fault Analysis:")
print(f"HTTP Status: {analysis['http_status']}")
print(f"Reason: {analysis['fault_reason']}")
if analysis['fault_code']:
print(f"Fault Code: {analysis['fault_code']}")
if analysis['guidance']:
print("Troubleshooting suggestions:")
for suggestion in analysis['guidance']:
print(f" - {suggestion}")import time
import random
def execute_with_retry(session, command, args=None, max_retries=3, backoff_factor=2):
"""Execute command with exponential backoff retry for timeout errors."""
args = args or []
for attempt in range(max_retries):
try:
r = session.run_cmd(command, args)
return r # Success
except WinRMOperationTimeoutError:
if attempt < max_retries - 1:
wait_time = backoff_factor ** attempt + random.uniform(0, 1)
print(f"Operation timeout, retrying in {wait_time:.1f}s (attempt {attempt + 1})")
time.sleep(wait_time)
continue
else:
print("Operation timed out after all retry attempts")
raise
except (WSManFaultError, WinRMTransportError) as e:
# These errors are typically not retryable
print(f"Non-retryable error: {e}")
raise
except Exception as e:
# Unexpected error
print(f"Unexpected error on attempt {attempt + 1}: {e}")
if attempt < max_retries - 1:
time.sleep(1)
continue
else:
raise
# Usage
try:
r = execute_with_retry(session, 'ping', ['google.com', '-n', '10'])
print("Ping completed successfully")
except Exception as e:
print(f"Command failed after retries: {e}")def validate_winrm_connection(hostname, username, password, **kwargs):
"""Validate WinRM connection and return detailed diagnostics."""
diagnostics = {
'connection_successful': False,
'authentication_successful': False,
'command_execution_successful': False,
'errors': [],
'warnings': []
}
try:
# Test connection and authentication
s = winrm.Session(hostname, auth=(username, password), **kwargs)
diagnostics['connection_successful'] = True
diagnostics['authentication_successful'] = True
# Test command execution
r = s.run_cmd('echo', ['connection_test'])
if r.status_code == 0:
diagnostics['command_execution_successful'] = True
output = r.std_out.decode('utf-8').strip()
if output == 'connection_test':
diagnostics['echo_successful'] = True
else:
diagnostics['warnings'].append(f"Unexpected echo output: {output}")
else:
diagnostics['errors'].append(f"Echo command failed with code: {r.status_code}")
except BasicAuthDisabledError:
diagnostics['errors'].append("Basic authentication is disabled on the server")
except InvalidCredentialsError:
diagnostics['errors'].append("Invalid username or password")
except AuthenticationError as e:
diagnostics['errors'].append(f"Authentication error: {e}")
except WSManFaultError as e:
diagnostics['connection_successful'] = True # Network connection worked
diagnostics['errors'].append(f"WSMan fault: {e.reason}")
if e.wsman_fault_code:
diagnostics['errors'].append(f"WSMan code: {e.wsman_fault_code}")
except WinRMTransportError as e:
diagnostics['errors'].append(f"Transport error: {e.message} (HTTP {e.code})")
except Exception as e:
diagnostics['errors'].append(f"Unexpected error: {e}")
return diagnostics
# Usage
diag = validate_winrm_connection('windows-host', 'user', 'password', transport='ntlm')
print(f"Connection: {'✓' if diag['connection_successful'] else '✗'}")
print(f"Authentication: {'✓' if diag['authentication_successful'] else '✗'}")
print(f"Command execution: {'✓' if diag['command_execution_successful'] else '✗'}")
if diag['errors']:
print("Errors:")
for error in diag['errors']:
print(f" - {error}")
if diag['warnings']:
print("Warnings:")
for warning in diag['warnings']:
print(f" - {warning}")# Common WSMan fault codes and their meanings:
WSMAN_FAULT_CODES = {
'a:ActionNotSupported': 'WS-Management action not supported',
'a:AccessDenied': 'Access denied - check permissions',
'a:QuotaLimit': 'WinRM quota limit exceeded',
'a:InternalError': 'Internal server error',
'a:InvalidResourceURI': 'Invalid resource URI',
'a:TimedOut': 'Operation timed out on server side'
}
# Microsoft WSMan fault codes (decimal):
MS_WSMAN_FAULT_CODES = {
2150858793: 'WinRM service not running or not configured', # 0x80338029
2150858770: 'Resource URI not supported', # 0x80338012
2150858778: 'Invalid operation timeout specified', # 0x8033801A
2150858843: 'User quota exceeded' # 0x8033805B
}These error codes help identify specific WinRM configuration issues and guide troubleshooting efforts for successful remote management deployment.
Install with Tessl CLI
npx tessl i tessl/pypi-pywinrm