CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pywinrm

Python library for Windows Remote Management (WinRM) service enabling remote command execution and PowerShell scripts on Windows machines.

Pending
Overview
Eval results
Files

exceptions.mddocs/

Exception Handling

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.

Capabilities

Exception Hierarchy

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."""

WSMan Fault Errors

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 code

Exception Handling Examples

Basic Exception Handling

import 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()}")

Authentication Error Recovery

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')

WSMan Fault Analysis

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}")

Retry Logic with Timeout Handling

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}")

Connection Validation

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 Error Scenarios

WinRM Service Configuration Issues

# 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

docs

auth-security.md

exceptions.md

index.md

protocol.md

response.md

session.md

tile.json