-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsystem_config.py
More file actions
1148 lines (922 loc) · 40.6 KB
/
Copy pathsystem_config.py
File metadata and controls
1148 lines (922 loc) · 40.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
"""
Windows System Configuration Utilities for NCSI Resolver
This module provides functions to configure Windows system settings required for
the NCSI Resolver to work properly, including registry edits and hosts file modifications.
"""
import ctypes
import datetime
import logging
import os
import platform
import re
import subprocess
import sys
import time
import winreg
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Union
try:
from version import get_version_info
# Get version information
__version_info__ = get_version_info("system_config")
__version__ = __version_info__["version"]
__description__ = __version_info__["description"]
except ImportError:
# Fallback version info if version.py is missing
__version__ = "unknown"
__description__ = "Windows System Configuration for NCSI Resolver"
# Set up logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger('system_config')
# Constants
HOSTS_FILE_PATH = r"C:\Windows\System32\drivers\etc\hosts"
NCSI_REGISTRY_KEY = r"SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet"
DEFAULT_NCSI_HOST = "www.msftconnecttest.com"
DEFAULT_NCSI_IP = "127.0.0.1"
TIMEOUT = 10 # seconds
BACKUP_DIR = os.path.join(os.environ.get('LOCALAPPDATA', os.path.expanduser('~')), "NCSI_Resolver", "Backups")
def create_timestamp():
"""Create a timestamp string for backup files."""
return datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
def is_admin() -> bool:
"""
Check if the current process has administrative privileges.
Returns:
bool: True if running with admin privileges, False otherwise
"""
try:
return ctypes.windll.shell32.IsUserAnAdmin() != 0
except Exception:
return False
def run_as_admin(script_path: str, *args) -> None:
"""
Restart the current script with administrative privileges.
Args:
script_path: Path to the script to run
*args: Additional arguments to pass
"""
if not is_admin():
logger.info("Requesting administrative privileges...")
# Convert to a list for easier handling
arg_list = list(args)
# Prepare the arguments
if script_path.endswith('.py'):
# If it's a .py file, we need to call it with python
cmd = [sys.executable, script_path] + arg_list
else:
# Otherwise assume it's executable
cmd = [script_path] + arg_list
try:
# Request elevation via ShellExecute
ctypes.windll.shell32.ShellExecuteW(
None, "runas", cmd[0], ' '.join(f'"{arg}"' for arg in cmd[1:]), None, 1
)
sys.exit(0)
except Exception as e:
logger.error(f"Failed to get admin privileges: {e}")
sys.exit(1)
def get_local_ip() -> Optional[str]:
"""
Get the local IP address of the machine.
Returns:
str: The local IP address, or None if it can't be determined
"""
try:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
local_ip = s.getsockname()[0]
s.close()
return local_ip
except Exception as e:
logger.error(f"Failed to get local IP: {e}")
return None
def backup_registry_values() -> Dict[str, Dict[str, Tuple[int, Union[str, bytes]]]]:
"""
Backup existing NCSI registry values before modification.
Returns:
Dict containing original registry values, empty if none existed
"""
original_values = {}
timestamp = create_timestamp()
try:
# Ensure backup directory exists
os.makedirs(BACKUP_DIR, exist_ok=True)
# Create a backup registry file path
backup_file = os.path.join(BACKUP_DIR, f"ncsi_registry_backup_{timestamp}.reg")
try:
# Try to open the registry key
reg_key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
NCSI_REGISTRY_KEY,
0,
winreg.KEY_READ
)
# Dictionary to store original values
original_values[NCSI_REGISTRY_KEY] = {}
# Check for existing values
try:
value, value_type = winreg.QueryValueEx(reg_key, "ActiveWebProbeHost")
original_values[NCSI_REGISTRY_KEY]["ActiveWebProbeHost"] = (value_type, value)
logger.info(f"Backing up existing registry value: ActiveWebProbeHost = {value}")
except FileNotFoundError:
logger.info("Registry value 'ActiveWebProbeHost' did not exist before modification")
try:
value, value_type = winreg.QueryValueEx(reg_key, "ActiveWebProbePath")
original_values[NCSI_REGISTRY_KEY]["ActiveWebProbePath"] = (value_type, value)
logger.info(f"Backing up existing registry value: ActiveWebProbePath = {value}")
except FileNotFoundError:
logger.info("Registry value 'ActiveWebProbePath' did not exist before modification")
# Close the key
winreg.CloseKey(reg_key)
except FileNotFoundError:
logger.info(f"Registry key {NCSI_REGISTRY_KEY} not found, nothing to backup")
# Export registry key to .reg file if we found any values
if original_values and original_values[NCSI_REGISTRY_KEY]:
try:
# Use reg.exe to export the key
subprocess.run(
[
"reg", "export",
f"HKLM\\{NCSI_REGISTRY_KEY}",
backup_file
],
check=True,
capture_output=True,
timeout=TIMEOUT
)
logger.info(f"Registry backup saved to {backup_file}")
except subprocess.CalledProcessError as e:
logger.warning(f"Could not export registry key: {e.stderr.decode() if e.stderr else str(e)}")
return original_values
except Exception as e:
logger.error(f"Error backing up registry values: {e}")
return {}
def backup_hosts_file() -> str:
"""
Create a timestamped backup of the hosts file.
Returns:
str: Path to the backup file, or empty string if backup failed
"""
hosts_path = Path(HOSTS_FILE_PATH)
timestamp = create_timestamp()
try:
# Ensure backup directory exists
os.makedirs(BACKUP_DIR, exist_ok=True)
# Create backup filename
backup_path = os.path.join(BACKUP_DIR, f"hosts.original.{timestamp}.bak")
# Check if we already have a hosts file entry for the NCSI host
has_ncsi_entry = False
if hosts_path.exists():
with open(hosts_path, 'r') as f:
hosts_content = f.read()
pattern = re.compile(rf'^\s*\d+\.\d+\.\d+\.\d+\s+{re.escape(DEFAULT_NCSI_HOST)}(?:\s|$)', re.MULTILINE)
has_ncsi_entry = bool(pattern.search(hosts_content))
# Create a full backup
if hosts_path.exists():
with open(hosts_path, 'r') as f:
hosts_content = f.read()
with open(backup_path, 'w') as f:
f.write(hosts_content)
logger.info(f"Created hosts file backup at {backup_path}")
# Also create a standard .bak file in the same directory for easier restoration
standard_backup = hosts_path.with_suffix('.ncsi_backup.bak')
if not standard_backup.exists(): # Only create if it doesn't exist already
with open(standard_backup, 'w') as f:
f.write(hosts_content)
logger.info(f"Created standard hosts backup at {standard_backup}")
# Log if we're overriding an existing entry
if has_ncsi_entry:
logger.warning(f"Hosts file already contains an entry for {DEFAULT_NCSI_HOST}, will be modified")
return backup_path
except Exception as e:
logger.error(f"Error backing up hosts file: {e}")
return ""
def update_hosts_file(hostname: str = DEFAULT_NCSI_HOST, ip: str = None) -> bool:
"""
Update the Windows hosts file to redirect NCSI requests.
Args:
hostname: The hostname to redirect
ip: The IP address to redirect to (default: local IP)
Returns:
bool: True if successful, False otherwise
"""
if not is_admin():
logger.error("Administrative privileges required to update hosts file")
return False
# Use local IP if not specified
if ip is None:
ip = get_local_ip() or DEFAULT_NCSI_IP
hosts_path = Path(HOSTS_FILE_PATH)
# Check if hosts file exists
if not hosts_path.exists():
logger.error(f"Hosts file not found at {HOSTS_FILE_PATH}")
return False
# Create a backup of the hosts file
backup_path = backup_hosts_file()
if not backup_path:
logger.warning("Could not create hosts file backup, proceeding with caution")
try:
# Read current hosts file
with open(hosts_path, 'r') as f:
hosts_content = f.read()
# Check if the hostname is already in the hosts file
pattern = re.compile(rf'^\s*\d+\.\d+\.\d+\.\d+\s+{re.escape(hostname)}(?:\s|$)', re.MULTILINE)
match = pattern.search(hosts_content)
if match:
# Update existing entry
hosts_content = pattern.sub(f"{ip} {hostname}", hosts_content)
logger.info(f"Updated hosts file entry for {hostname} to {ip}")
else:
# Add new entry
if not hosts_content.endswith('\n'):
hosts_content += '\n'
hosts_content += f"{ip} {hostname}\n"
logger.info(f"Added new hosts file entry for {hostname} to {ip}")
# Write updated hosts file
with open(hosts_path, 'w') as f:
f.write(hosts_content)
return True
except Exception as e:
logger.error(f"Error updating hosts file: {e}")
# Try to restore from backup if we have one
if backup_path and os.path.exists(backup_path):
try:
with open(backup_path, 'r') as f:
backup_content = f.read()
with open(hosts_path, 'w') as f:
f.write(backup_content)
logger.info("Restored hosts file from backup after error")
except Exception as restore_error:
logger.error(f"Error restoring hosts file from backup: {restore_error}")
return False
def update_ncsi_registry(probe_host: str = None, probe_path: str = "/ncsi.txt", port: int = 80) -> bool:
"""
Update the Windows registry for NCSI settings.
Args:
probe_host: The hostname to use for NCSI probes (default: local IP)
probe_path: The path to use for NCSI probes
port: The port to use for NCSI probes
Returns:
bool: True if successful, False otherwise
"""
if not is_admin():
logger.error("Administrative privileges required to update registry")
return False
# Use local IP if not specified
if probe_host is None:
probe_host = get_local_ip() or DEFAULT_NCSI_IP
# Backup existing registry values
original_values = backup_registry_values()
try:
# Open the registry key
reg_key = winreg.CreateKeyEx(
winreg.HKEY_LOCAL_MACHINE,
NCSI_REGISTRY_KEY,
0,
winreg.KEY_WRITE
)
# Format the host with port if not using default HTTP port
if port != 80:
formatted_host = f"{probe_host}:{port}"
else:
formatted_host = probe_host
# Update registry values
winreg.SetValueEx(reg_key, "ActiveWebProbeHost", 0, winreg.REG_SZ, formatted_host)
winreg.SetValueEx(reg_key, "ActiveWebProbePath", 0, winreg.REG_SZ, probe_path)
# Close the key
winreg.CloseKey(reg_key)
logger.info(f"Updated NCSI registry settings to use {formatted_host}{probe_path}")
return True
except Exception as e:
logger.error(f"Error updating registry: {e}")
# Try to restore original values if available
if original_values:
try:
restore_registry_from_backup(original_values)
logger.info("Restored registry from backup after error")
except Exception as restore_error:
logger.error(f"Error restoring registry from backup: {restore_error}")
return False
def check_ncsi_registry() -> Dict[str, str]:
"""
Check the current NCSI registry settings.
Returns:
Dict[str, str]: A dictionary of current settings
"""
result = {}
try:
# Open the registry key
reg_key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
NCSI_REGISTRY_KEY,
0,
winreg.KEY_READ
)
# Read registry values
try:
result["ActiveWebProbeHost"] = winreg.QueryValueEx(reg_key, "ActiveWebProbeHost")[0]
except FileNotFoundError:
result["ActiveWebProbeHost"] = "default (not set)"
try:
result["ActiveWebProbePath"] = winreg.QueryValueEx(reg_key, "ActiveWebProbePath")[0]
except FileNotFoundError:
result["ActiveWebProbePath"] = "default (not set)"
# Close the key
winreg.CloseKey(reg_key)
except Exception as e:
logger.error(f"Error reading registry: {e}")
return result
def check_hosts_file(hostname: str = DEFAULT_NCSI_HOST) -> Optional[str]:
"""
Check if the hostname is redirected in the hosts file.
Args:
hostname: The hostname to check
Returns:
Optional[str]: The IP address if found, None otherwise
"""
try:
with open(HOSTS_FILE_PATH, 'r') as f:
hosts_content = f.read()
# Look for the hostname in the hosts file
pattern = re.compile(rf'^\s*(\d+\.\d+\.\d+\.\d+)\s+{re.escape(hostname)}(?:\s|$)', re.MULTILINE)
match = pattern.search(hosts_content)
if match:
return match.group(1)
return None
except Exception as e:
logger.error(f"Error reading hosts file: {e}")
return None
def restore_registry_from_backup(original_values: Dict[str, Dict[str, Tuple[int, Union[str, bytes]]]] = None) -> bool:
"""
Restore registry values from backup.
Args:
original_values: Dictionary with original registry values
Returns:
bool: True if successful, False otherwise
"""
try:
# Open the registry key
reg_key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
NCSI_REGISTRY_KEY,
0,
winreg.KEY_WRITE
)
# If we have original values, restore them
if original_values and NCSI_REGISTRY_KEY in original_values:
values = original_values[NCSI_REGISTRY_KEY]
if "ActiveWebProbeHost" in values:
value_type, value = values["ActiveWebProbeHost"]
winreg.SetValueEx(reg_key, "ActiveWebProbeHost", 0, value_type, value)
logger.info(f"Restored original registry value: ActiveWebProbeHost = {value}")
else:
# If the key didn't exist originally, delete it
try:
winreg.DeleteValue(reg_key, "ActiveWebProbeHost")
logger.info("Removed registry value: ActiveWebProbeHost")
except FileNotFoundError:
pass
if "ActiveWebProbePath" in values:
value_type, value = values["ActiveWebProbePath"]
winreg.SetValueEx(reg_key, "ActiveWebProbePath", 0, value_type, value)
logger.info(f"Restored original registry value: ActiveWebProbePath = {value}")
else:
# If the key didn't exist originally, delete it
try:
winreg.DeleteValue(reg_key, "ActiveWebProbePath")
logger.info("Removed registry value: ActiveWebProbePath")
except FileNotFoundError:
pass
else:
# If we don't have original values, just delete our added values
try:
winreg.DeleteValue(reg_key, "ActiveWebProbeHost")
logger.info("Removed registry value: ActiveWebProbeHost")
except FileNotFoundError:
pass
try:
winreg.DeleteValue(reg_key, "ActiveWebProbePath")
logger.info("Removed registry value: ActiveWebProbePath")
except FileNotFoundError:
pass
# Close the key
winreg.CloseKey(reg_key)
# Look for the most recent backup file
try:
backup_files = []
if os.path.exists(BACKUP_DIR):
for file in os.listdir(BACKUP_DIR):
if file.startswith("ncsi_registry_backup_") and file.endswith(".reg"):
backup_files.append(os.path.join(BACKUP_DIR, file))
if backup_files:
# Sort by modification time (newest first)
backup_files.sort(key=os.path.getmtime, reverse=True)
newest_backup = backup_files[0]
logger.info(f"Registry backup file available at: {newest_backup}")
logger.info("You can manually restore registry settings by double-clicking this file if needed")
except Exception as e:
logger.warning(f"Could not find registry backup files: {e}")
return True
except Exception as e:
logger.error(f"Error restoring registry values: {e}")
return False
def restore_hosts_file() -> bool:
"""
Restore hosts file from backup.
Returns:
bool: True if successful, False otherwise
"""
hosts_path = Path(HOSTS_FILE_PATH)
try:
# First, look for our standard backup in the hosts directory
standard_backup = hosts_path.with_suffix('.ncsi_backup.bak')
if standard_backup.exists():
logger.info(f"Found standard hosts backup at {standard_backup}")
# Read the backup
with open(standard_backup, 'r') as f:
backup_content = f.read()
# Read current hosts file
with open(hosts_path, 'r') as f:
current_content = f.read()
# Check if our entry is in the current hosts file
pattern = re.compile(rf'^\s*\d+\.\d+\.\d+\.\d+\s+{re.escape(DEFAULT_NCSI_HOST)}(?:\s|$).*$\n?', re.MULTILINE)
if pattern.search(current_content):
# Remove only our entry
modified_content = pattern.sub('', current_content)
# Write the modified content back
with open(hosts_path, 'w') as f:
f.write(modified_content)
logger.info(f"Removed {DEFAULT_NCSI_HOST} entry from hosts file")
# Don't restore from backup, as we only removed our entry
# This preserves any other changes the user might have made
return True
else:
# If our entry is not in the file, restore from backup
with open(hosts_path, 'w') as f:
f.write(backup_content)
logger.info("Restored hosts file from backup")
return True
# If standard backup doesn't exist, look for timestamped backups
backup_files = []
if os.path.exists(BACKUP_DIR):
for file in os.listdir(BACKUP_DIR):
if file.startswith("hosts.original.") and file.endswith(".bak"):
backup_files.append(os.path.join(BACKUP_DIR, file))
if backup_files:
# Sort by modification time (newest first)
backup_files.sort(key=os.path.getmtime, reverse=True)
newest_backup = backup_files[0]
logger.info(f"Using backup file: {newest_backup}")
# Restore from the backup
with open(newest_backup, 'r') as f:
backup_content = f.read()
# Read current hosts file
with open(hosts_path, 'r') as f:
current_content = f.read()
# Check if our entry is in the current hosts file
pattern = re.compile(rf'^\s*\d+\.\d+\.\d+\.\d+\s+{re.escape(DEFAULT_NCSI_HOST)}(?:\s|$).*$\n?', re.MULTILINE)
if pattern.search(current_content):
# Remove only our entry
modified_content = pattern.sub('', current_content)
# Write the modified content back
with open(hosts_path, 'w') as f:
f.write(modified_content)
logger.info(f"Removed {DEFAULT_NCSI_HOST} entry from hosts file")
return True
else:
# If our entry is not in the file, restore from backup
with open(hosts_path, 'w') as f:
f.write(backup_content)
logger.info("Restored hosts file from backup")
return True
# If no backup is found, just remove our entry from the hosts file
if hosts_path.exists():
with open(hosts_path, 'r') as f:
hosts_content = f.read()
# Remove the NCSI host entry
pattern = re.compile(rf'^\s*\d+\.\d+\.\d+\.\d+\s+{re.escape(DEFAULT_NCSI_HOST)}(?:\s|$).*$\n?', re.MULTILINE)
hosts_content = pattern.sub('', hosts_content)
with open(hosts_path, 'w') as f:
f.write(hosts_content)
logger.info(f"Removed {DEFAULT_NCSI_HOST} entry from hosts file")
return True
except Exception as e:
logger.error(f"Error restoring hosts file: {e}")
return False
def restart_network_service() -> bool:
"""
Restart the Network Location Awareness (NLA) service to apply changes.
Returns:
bool: True if successful, False otherwise
"""
if not is_admin():
logger.error("Administrative privileges required to restart network service")
return False
try:
# Try restart instead of stop/start
logger.info("Attempting to restart Network Location Awareness service...")
restart_result = subprocess.run(
["net", "stop", "NlaSvc", "/y"],
check=False, # Don't raise exception if command fails
capture_output=True,
timeout=TIMEOUT
)
if restart_result.returncode != 0:
# If direct stop fails, try SC command to restart
logger.info("Direct stop failed, trying SC to restart service...")
sc_result = subprocess.run(
["sc", "stop", "NlaSvc"],
check=False,
capture_output=True,
timeout=TIMEOUT
)
# Even if SC fails, continue since we'll still flush DNS and renew IP
if sc_result.returncode != 0:
logger.warning("Could not stop NlaSvc service, changes may require a system restart to take effect")
# Try to start the service again
start_result = subprocess.run(
["net", "start", "NlaSvc"],
check=False,
capture_output=True,
timeout=TIMEOUT
)
if start_result.returncode == 0:
logger.info("Successfully restarted Network Location Awareness service")
else:
logger.warning("Could not start NlaSvc service, it may start automatically or require a system restart")
# Return true even if we couldn't restart the service
# The registry changes will still take effect eventually
return True
except Exception as e:
logger.error(f"Error managing network service: {e}")
# Continue with other network operations
return False
def detect_wifi_adapters() -> List[str]:
"""
Detect Wi-Fi adapters on the system.
Returns:
List[str]: List of Wi-Fi adapter names, or empty list if none found
"""
try:
# Method 1: Try using netsh (Windows-specific)
result = subprocess.run(
["netsh", "wlan", "show", "interfaces"],
check=False, # Don't raise exception if command fails
capture_output=True,
text=True,
timeout=TIMEOUT
)
# Extract adapter names
adapters = []
if result.returncode == 0:
for line in result.stdout.splitlines():
if "Name" in line and ":" in line:
adapters.append(line.split(":", 1)[1].strip())
if adapters:
return adapters
# Method 2: Try using WMI for more detailed information
try:
import wmi
c = wmi.WMI()
wifi_adapters = []
# Look for wireless adapters
for nic in c.Win32_NetworkAdapter():
# Check various properties that might indicate wireless
if any(wifi_term.lower() in nic.Name.lower() for wifi_term in
["wireless", "wifi", "wi-fi", "802.11", "wlan"]):
wifi_adapters.append(nic.Name)
if wifi_adapters:
return wifi_adapters
except ImportError:
# WMI module not available, try one more approach
pass
# Method 3: Try using ipconfig
result = subprocess.run(
["ipconfig", "/all"],
check=False,
capture_output=True,
text=True,
timeout=TIMEOUT
)
if result.returncode == 0:
wifi_sections = []
current_section = []
in_section = False
for line in result.stdout.splitlines():
if "adapter" in line.lower() and ":" in line:
if in_section and any(wifi_term.lower() in "\n".join(current_section).lower()
for wifi_term in ["wireless", "wifi", "wi-fi", "802.11", "wlan"]):
wifi_name = current_section[0].split(":")[0].strip()
wifi_sections.append(wifi_name)
current_section = [line]
in_section = True
elif in_section:
current_section.append(line)
# Check the last section
if in_section and any(wifi_term.lower() in "\n".join(current_section).lower()
for wifi_term in ["wireless", "wifi", "wi-fi", "802.11", "wlan"]):
wifi_name = current_section[0].split(":")[0].strip()
wifi_sections.append(wifi_name)
return wifi_sections
except Exception as e:
logger.warning(f"Error detecting Wi-Fi adapters: {e}")
return []
def configure_wifi_adapter(skip_if_no_wifi: bool = True) -> bool:
"""
Configure Wi-Fi adapter for optimal stability.
Args:
skip_if_no_wifi: Whether to skip silently if no Wi-Fi adapter is found
Returns:
bool: True if successful or skipped, False otherwise
"""
if not is_admin():
logger.error("Administrative privileges required to configure network adapter")
return False
try:
# Get list of wireless adapters using our detection function
adapters = detect_wifi_adapters()
if not adapters:
if skip_if_no_wifi:
logger.info("No wireless adapters found. Skipping Wi-Fi optimization.")
return True # Return success since we're skipping
else:
logger.warning("No wireless adapters found")
return False
logger.info(f"Found {len(adapters)} wireless adapters: {', '.join(adapters)}")
# Configure each adapter
for adapter in adapters:
# Check if Intel adapter (common troublemakers)
if "intel" in adapter.lower():
logger.info(f"Configuring Intel adapter: {adapter}")
# Lower the roaming aggressiveness
subprocess.run([
"netsh", "wlan", "set", "profileparameter",
f'name="{adapter}"', "roaming=1"
], check=False, timeout=TIMEOUT)
# Prefer 5GHz band
subprocess.run([
"netsh", "wlan", "set", "profileparameter",
f'name="{adapter}"', "preferredband=5"
], check=False, timeout=TIMEOUT)
# General settings for any adapter
# Disable power saving
try:
subprocess.run([
"powercfg", "-setacvalueindex", "scheme_current",
"19cbb8fa-5279-450e-9fac-8a3d5fedd0c1",
"12bbebe6-58d6-4636-95bb-3217ef867c1a", "0"
], check=False, timeout=TIMEOUT)
# Apply changes
subprocess.run(["powercfg", "-setactive", "scheme_current"], check=False, timeout=TIMEOUT)
logger.info(f"Configured power settings for {adapter}")
except Exception as e:
logger.warning(f"Failed to configure power settings: {e}")
return True
except Exception as e:
logger.error(f"Error configuring Wi-Fi adapter: {e}")
return False
def refresh_network() -> bool:
"""
Refresh network settings and DNS cache.
Returns:
bool: True if successful, False otherwise
"""
try:
# Flush DNS cache
subprocess.run(["ipconfig", "/flushdns"], check=True, capture_output=True, timeout=TIMEOUT)
logger.info("Flushed DNS cache")
# Release and renew IP
subprocess.run(["ipconfig", "/release"], check=False, capture_output=True, timeout=TIMEOUT)
subprocess.run(["ipconfig", "/renew"], check=False, capture_output=True, timeout=TIMEOUT)
logger.info("Released and renewed IP address")
return True
except Exception as e:
logger.error(f"Error refreshing network: {e}")
return False
def configure_system(probe_host: str = None,
probe_path: str = "/ncsi.txt",
port: int = 80,
restart_services: bool = True,
configure_wifi: bool = True) -> bool:
"""
Configure all system settings for NCSI Resolver.
Args:
probe_host: The hostname to use for NCSI probes (default: local IP)
probe_path: The path to use for NCSI probes
port: The port to use for NCSI probes
restart_services: Whether to restart network services
configure_wifi: Whether to attempt Wi-Fi adapter configuration
Returns:
bool: True if all operations successful, False otherwise
"""
if not is_admin():
logger.warning("Administrative privileges required to configure system")
return False
# Use local IP if not specified
if probe_host is None:
probe_host = get_local_ip() or DEFAULT_NCSI_IP
success = True
# Update hosts file
if not update_hosts_file(DEFAULT_NCSI_HOST, probe_host):
success = False
# Update registry
if not update_ncsi_registry(probe_host, probe_path, port):
success = False
# Configure Wi-Fi adapter only if requested
if configure_wifi:
if not configure_wifi_adapter(skip_if_no_wifi=True):
logger.warning("Failed to configure Wi-Fi adapter, continuing with other operations")
# Restart services if requested
if restart_services:
if not restart_network_service():
success = False
if not refresh_network():
logger.warning("Failed to refresh network, continuing with other operations")
# Print summary of current configuration
if success:
logger.info("System configuration successful")
# Show current settings
registry_settings = check_ncsi_registry()
hosts_redirect = check_hosts_file(DEFAULT_NCSI_HOST)
logger.info("Current NCSI configuration:")
logger.info(f" Registry settings:")
for key, value in registry_settings.items():
logger.info(f" {key}: {value}")
logger.info(f" Hosts file redirect: {DEFAULT_NCSI_HOST} -> {hosts_redirect or 'not set'}")
else:
logger.error("System configuration failed")
return success
def check_configuration() -> Dict[str, Union[str, bool]]:
"""
Check the current NCSI configuration status.
Returns:
Dict: A dictionary with configuration status
"""
result = {
"registry_settings": check_ncsi_registry(),
"hosts_file_redirect": check_hosts_file(DEFAULT_NCSI_HOST),
"is_configured": False
}
# Check if properly configured
if (result["registry_settings"].get("ActiveWebProbeHost") != "default (not set)" and
result["hosts_file_redirect"] is not None):
result["is_configured"] = True
return result
def create_windows_defaults_reg(target_path: str) -> bool:
"""
Create the Windows default registry settings file at the specified path.
Args:
target_path: Path where the file should be created
Returns:
bool: True if successful, False otherwise
"""
try:
# Ensure directory exists
os.makedirs(os.path.dirname(os.path.abspath(target_path)), exist_ok=True)
# Create Windows default registry content with correct values
# Note: ActiveWebProbePath does NOT have a leading slash
content = """Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\NlaSvc\\Parameters\\Internet]
"EnableActiveProbing"=dword:00000001
"ActiveWebProbeHost"="www.msftconnecttest.com"
"ActiveWebProbePath"="connecttest.txt"
"ActiveWebProbeContent"="Microsoft Connect Test"
"EnableActiveHTTPS"=dword:00000001
"""
# Write to file
with open(target_path, 'w') as f:
f.write(content)
logger.info(f"Created Windows default registry settings file at {target_path}")
return True
except Exception as e:
logger.error(f"Error creating Windows default registry file: {e}")
return False