0
# Exception Handling
1
2
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.
3
4
## Capabilities
5
6
### Exception Hierarchy
7
8
PyWinRM defines a structured exception hierarchy for different types of failures.
9
10
```python { .api }
11
# Base exception classes
12
class WinRMError(Exception):
13
"""Generic WinRM error with HTTP status code."""
14
code = 500
15
16
class WinRMTransportError(Exception):
17
"""Transport-level HTTP errors (unexpected status codes)."""
18
19
@property
20
def protocol(self) -> str: ...
21
22
@property
23
def code(self) -> int: ...
24
25
@property
26
def message(self) -> str: ...
27
28
@property
29
def response_text(self) -> str: ...
30
31
class WinRMOperationTimeoutError(Exception):
32
"""WinRM operation timeout (retryable)."""
33
code = 500
34
35
# Authentication exceptions
36
class AuthenticationError(WinRMError):
37
"""Base authentication error."""
38
code = 401
39
40
class BasicAuthDisabledError(AuthenticationError):
41
"""HTTP Basic authentication is disabled on remote host."""
42
message = "WinRM/HTTP Basic authentication is not enabled on remote host"
43
44
class InvalidCredentialsError(AuthenticationError):
45
"""Invalid username/password credentials."""
46
```
47
48
### WSMan Fault Errors
49
50
Detailed WSMan protocol fault information for diagnosing WinRM service errors.
51
52
```python { .api }
53
class WSManFaultError(WinRMError):
54
"""
55
WSMan fault with detailed error information.
56
57
Contains raw response and parsed fault details from WSMan service.
58
Provides both Microsoft-specific codes and standard WSMan fault data.
59
"""
60
61
def __init__(
62
self,
63
code: int,
64
message: str,
65
response: str,
66
reason: str,
67
fault_code: str | None = None,
68
fault_subcode: str | None = None,
69
wsman_fault_code: int | None = None,
70
wmierror_code: int | None = None
71
):
72
"""
73
Initialize WSMan fault error with detailed fault information.
74
75
Parameters:
76
- code: HTTP status code of the response
77
- message: error message from transport layer
78
- response: raw WSMan response text
79
- reason: WSMan fault reason text
80
- fault_code: WSMan fault code string
81
- fault_subcode: WSMan fault subcode string
82
- wsman_fault_code: Microsoft WSManFault specific code
83
- wmierror_code: Microsoft WMI error code (for quota violations, etc.)
84
"""
85
86
# Fault information attributes
87
code: int # HTTP status code
88
response: str # Raw WSMan response
89
fault_code: str | None # WSMan fault code
90
fault_subcode: str | None # WSMan fault subcode
91
reason: str # Fault reason text
92
wsman_fault_code: int | None # MS-specific fault code
93
wmierror_code: int | None # WMI error code
94
```
95
96
## Exception Handling Examples
97
98
### Basic Exception Handling
99
100
```python
101
import winrm
102
from winrm.exceptions import (
103
WinRMError, WSManFaultError, WinRMTransportError,
104
AuthenticationError, WinRMOperationTimeoutError
105
)
106
107
def safe_winrm_operation(hostname, username, password, command):
108
"""Execute WinRM command with comprehensive error handling."""
109
110
try:
111
s = winrm.Session(hostname, auth=(username, password))
112
r = s.run_cmd(command)
113
114
if r.status_code == 0:
115
return r.std_out.decode('utf-8')
116
else:
117
print(f"Command failed with exit code: {r.status_code}")
118
print(f"Error output: {r.std_err.decode('utf-8')}")
119
return None
120
121
except AuthenticationError as e:
122
print(f"Authentication failed: {e}")
123
print("Check username, password, and authentication method")
124
return None
125
126
except WSManFaultError as e:
127
print(f"WSMan fault: {e.reason}")
128
print(f"HTTP Status: {e.code}")
129
if e.wsman_fault_code:
130
print(f"WSMan Fault Code: {e.wsman_fault_code}")
131
if e.wmierror_code:
132
print(f"WMI Error Code: {e.wmierror_code}")
133
return None
134
135
except WinRMTransportError as e:
136
print(f"Transport error: {e.message}")
137
print(f"HTTP Status: {e.code}")
138
print(f"Response: {e.response_text[:200]}...")
139
return None
140
141
except WinRMOperationTimeoutError as e:
142
print("Operation timed out - this is often retryable")
143
return None
144
145
except WinRMError as e:
146
print(f"General WinRM error: {e}")
147
return None
148
149
except Exception as e:
150
print(f"Unexpected error: {e}")
151
return None
152
153
# Usage
154
result = safe_winrm_operation('windows-host', 'user', 'password', 'hostname')
155
if result:
156
print(f"Hostname: {result.strip()}")
157
```
158
159
### Authentication Error Recovery
160
161
```python
162
def try_authentication_methods(hostname, username, password):
163
"""Try different authentication methods until one succeeds."""
164
165
auth_methods = [
166
('ntlm', {'transport': 'ntlm', 'message_encryption': 'auto'}),
167
('ssl', {'transport': 'ssl'}),
168
('kerberos', {'transport': 'kerberos'}),
169
('basic', {'transport': 'basic'})
170
]
171
172
for method_name, kwargs in auth_methods:
173
try:
174
print(f"Trying {method_name} authentication...")
175
s = winrm.Session(hostname, auth=(username, password), **kwargs)
176
r = s.run_cmd('echo', ['test'])
177
178
if r.status_code == 0:
179
print(f"Success with {method_name} authentication")
180
return s
181
182
except BasicAuthDisabledError:
183
print("Basic authentication is disabled on the server")
184
continue
185
186
except InvalidCredentialsError:
187
print("Invalid credentials - check username and password")
188
break # Don't try other methods with bad credentials
189
190
except AuthenticationError as e:
191
print(f"Authentication failed with {method_name}: {e}")
192
continue
193
194
except Exception as e:
195
print(f"Error with {method_name}: {e}")
196
continue
197
198
print("All authentication methods failed")
199
return None
200
201
# Usage
202
session = try_authentication_methods('windows-host', 'user', 'password')
203
if session:
204
# Use successful session
205
r = session.run_cmd('systeminfo')
206
```
207
208
### WSMan Fault Analysis
209
210
```python
211
def analyze_wsman_fault(fault_error):
212
"""Analyze WSMan fault error and provide troubleshooting guidance."""
213
214
analysis = {
215
'http_status': fault_error.code,
216
'fault_reason': fault_error.reason,
217
'fault_code': fault_error.fault_code,
218
'wsman_code': fault_error.wsman_fault_code,
219
'wmi_code': fault_error.wmierror_code,
220
'guidance': []
221
}
222
223
# Common fault code analysis
224
if fault_error.fault_code == 'a:ActionNotSupported':
225
analysis['guidance'].append("The requested WS-Management action is not supported")
226
analysis['guidance'].append("Check WinRM service configuration and Windows version")
227
228
elif fault_error.fault_code == 'a:AccessDenied':
229
analysis['guidance'].append("Access denied - check user permissions")
230
analysis['guidance'].append("User may need 'Log on as a service' or WinRM permissions")
231
232
elif fault_error.fault_code == 'a:QuotaLimit':
233
analysis['guidance'].append("WinRM quota limit exceeded")
234
analysis['guidance'].append("Increase MaxShellsPerUser or MaxConcurrentOperationsPerUser")
235
236
# Microsoft-specific fault codes
237
if fault_error.wsman_fault_code:
238
wsman_code = fault_error.wsman_fault_code
239
if wsman_code == 2150858793: # 0x80338029
240
analysis['guidance'].append("WinRM service is not running or not properly configured")
241
elif wsman_code == 2150858770: # 0x80338012
242
analysis['guidance'].append("The specified resource URI is not supported")
243
244
# WMI error codes
245
if fault_error.wmierror_code:
246
wmi_code = fault_error.wmierror_code
247
if wmi_code == 2147749889: # 0x80041001
248
analysis['guidance'].append("WMI: General failure")
249
elif wmi_code == 2147749890: # 0x80041002
250
analysis['guidance'].append("WMI: Object not found")
251
252
return analysis
253
254
# Usage in exception handler
255
try:
256
s = winrm.Session('windows-host', auth=('user', 'password'))
257
r = s.run_cmd('invalidcommand')
258
except WSManFaultError as e:
259
analysis = analyze_wsman_fault(e)
260
261
print(f"WSMan Fault Analysis:")
262
print(f"HTTP Status: {analysis['http_status']}")
263
print(f"Reason: {analysis['fault_reason']}")
264
if analysis['fault_code']:
265
print(f"Fault Code: {analysis['fault_code']}")
266
267
if analysis['guidance']:
268
print("Troubleshooting suggestions:")
269
for suggestion in analysis['guidance']:
270
print(f" - {suggestion}")
271
```
272
273
### Retry Logic with Timeout Handling
274
275
```python
276
import time
277
import random
278
279
def execute_with_retry(session, command, args=None, max_retries=3, backoff_factor=2):
280
"""Execute command with exponential backoff retry for timeout errors."""
281
282
args = args or []
283
284
for attempt in range(max_retries):
285
try:
286
r = session.run_cmd(command, args)
287
return r # Success
288
289
except WinRMOperationTimeoutError:
290
if attempt < max_retries - 1:
291
wait_time = backoff_factor ** attempt + random.uniform(0, 1)
292
print(f"Operation timeout, retrying in {wait_time:.1f}s (attempt {attempt + 1})")
293
time.sleep(wait_time)
294
continue
295
else:
296
print("Operation timed out after all retry attempts")
297
raise
298
299
except (WSManFaultError, WinRMTransportError) as e:
300
# These errors are typically not retryable
301
print(f"Non-retryable error: {e}")
302
raise
303
304
except Exception as e:
305
# Unexpected error
306
print(f"Unexpected error on attempt {attempt + 1}: {e}")
307
if attempt < max_retries - 1:
308
time.sleep(1)
309
continue
310
else:
311
raise
312
313
# Usage
314
try:
315
r = execute_with_retry(session, 'ping', ['google.com', '-n', '10'])
316
print("Ping completed successfully")
317
except Exception as e:
318
print(f"Command failed after retries: {e}")
319
```
320
321
### Connection Validation
322
323
```python
324
def validate_winrm_connection(hostname, username, password, **kwargs):
325
"""Validate WinRM connection and return detailed diagnostics."""
326
327
diagnostics = {
328
'connection_successful': False,
329
'authentication_successful': False,
330
'command_execution_successful': False,
331
'errors': [],
332
'warnings': []
333
}
334
335
try:
336
# Test connection and authentication
337
s = winrm.Session(hostname, auth=(username, password), **kwargs)
338
diagnostics['connection_successful'] = True
339
diagnostics['authentication_successful'] = True
340
341
# Test command execution
342
r = s.run_cmd('echo', ['connection_test'])
343
if r.status_code == 0:
344
diagnostics['command_execution_successful'] = True
345
output = r.std_out.decode('utf-8').strip()
346
if output == 'connection_test':
347
diagnostics['echo_successful'] = True
348
else:
349
diagnostics['warnings'].append(f"Unexpected echo output: {output}")
350
else:
351
diagnostics['errors'].append(f"Echo command failed with code: {r.status_code}")
352
353
except BasicAuthDisabledError:
354
diagnostics['errors'].append("Basic authentication is disabled on the server")
355
356
except InvalidCredentialsError:
357
diagnostics['errors'].append("Invalid username or password")
358
359
except AuthenticationError as e:
360
diagnostics['errors'].append(f"Authentication error: {e}")
361
362
except WSManFaultError as e:
363
diagnostics['connection_successful'] = True # Network connection worked
364
diagnostics['errors'].append(f"WSMan fault: {e.reason}")
365
if e.wsman_fault_code:
366
diagnostics['errors'].append(f"WSMan code: {e.wsman_fault_code}")
367
368
except WinRMTransportError as e:
369
diagnostics['errors'].append(f"Transport error: {e.message} (HTTP {e.code})")
370
371
except Exception as e:
372
diagnostics['errors'].append(f"Unexpected error: {e}")
373
374
return diagnostics
375
376
# Usage
377
diag = validate_winrm_connection('windows-host', 'user', 'password', transport='ntlm')
378
print(f"Connection: {'✓' if diag['connection_successful'] else '✗'}")
379
print(f"Authentication: {'✓' if diag['authentication_successful'] else '✗'}")
380
print(f"Command execution: {'✓' if diag['command_execution_successful'] else '✗'}")
381
382
if diag['errors']:
383
print("Errors:")
384
for error in diag['errors']:
385
print(f" - {error}")
386
387
if diag['warnings']:
388
print("Warnings:")
389
for warning in diag['warnings']:
390
print(f" - {warning}")
391
```
392
393
## Common Error Scenarios
394
395
### WinRM Service Configuration Issues
396
397
```python
398
# Common WSMan fault codes and their meanings:
399
WSMAN_FAULT_CODES = {
400
'a:ActionNotSupported': 'WS-Management action not supported',
401
'a:AccessDenied': 'Access denied - check permissions',
402
'a:QuotaLimit': 'WinRM quota limit exceeded',
403
'a:InternalError': 'Internal server error',
404
'a:InvalidResourceURI': 'Invalid resource URI',
405
'a:TimedOut': 'Operation timed out on server side'
406
}
407
408
# Microsoft WSMan fault codes (decimal):
409
MS_WSMAN_FAULT_CODES = {
410
2150858793: 'WinRM service not running or not configured', # 0x80338029
411
2150858770: 'Resource URI not supported', # 0x80338012
412
2150858778: 'Invalid operation timeout specified', # 0x8033801A
413
2150858843: 'User quota exceeded' # 0x8033805B
414
}
415
```
416
417
These error codes help identify specific WinRM configuration issues and guide troubleshooting efforts for successful remote management deployment.