import re, json, sys, io from openpyxl import load_workbook from unidecode import unidecode sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') ps = open(r'D:\VNPT-M\Tool\audit_tool\NEW\Update\audit_lhdsin\audit_check_script\audit_cis_windows.ps1', 'r', encoding='utf-8').read() # Strategy: find ALL lines with Write-CheckResult, regardless of conditional context # Also handle the case where IsDomain branches produce different descriptions all_checks = [] # Find all Write-CheckResult calls - including those inside conditionals for m in re.finditer(r'Write-CheckResult\s+"([^"]+)"\s+"([^"]+)"', ps): check_id = m.group(1) desc = m.group(2) if not check_id.startswith('$'): all_checks.append((check_id, desc)) print(f'Direct calls (no variables): {len(all_checks)}') # Now expand variable-based calls from Test-FirewallProfile and Test-EventLogPolicy fw_calls = re.findall(r'Test-FirewallProfile\s+"(\w+)"\s+"([\d.]+)"\s+"(\w+)"', ps) for profile, prefix, label in fw_calls: all_checks.append((f'{prefix}.1', f"Thiet lap trang thai 'Windows Firewall: {label} : Firewall state'")) all_checks.append((f'{prefix}.2', f"Thiet lap trang thai 'Windows Firewall: {label} : Inbound connections'")) all_checks.append((f'{prefix}.3', f"Thiet lap trang thai 'Windows Firewall: {label} : Outbound connections'")) log_calls = re.findall(r'Test-EventLogPolicy\s+"(\w+)"\s+"([\d.]+)"\s+"(\w+)"', ps) for logname, prefix, label in log_calls: all_checks.append((f'{prefix}.1', f"Thiet lap chinh sach '{label} : Control Event Log behavior when the log file reaches its maximum size'")) all_checks.append((f'{prefix}.2', f"Thiet lap chinh sach '{label} : Specify the maximum log file size (KB)'")) # Build criteria list, removing duplicates (same CheckId + same Description) seen = set() criteria_unique = [] for cid, desc in all_checks: key = (cid, desc) if key not in seen: seen.add(key) criteria_unique.append((cid, desc)) criteria = criteria_unique print(f'Total unique criteria: {len(criteria)}') # Build ps_criteria lookup ps_criteria = {} for cid, desc in criteria: full = f"{cid}. {desc}" key = unidecode(full) ps_criteria[key] = full # Load Excel wb = load_workbook(r'D:\VNPT-M\Tool\audit_tool\NEW\Update\audit_lhdsin\config\Windows_Checklist.xlsx') ws = wb.worksheets[0] excel_rows = {} # Pass 1: match by full unidecoded text for row in range(1, ws.max_row + 1): val_a = str(ws.cell(row=row, column=1).value or "").strip() val_b = str(ws.cell(row=row, column=2).value or "").strip() combined = f"{val_a} {val_b}".strip() norm = unidecode(combined) if norm in ps_criteria: excel_rows[row] = ps_criteria[norm] del ps_criteria[norm] # Pass 2: match by CheckId prefix still = dict(ps_criteria) for cid_key, full_text in still.items(): cid = full_text.split('. ')[0] for row in range(1, ws.max_row + 1): if row in excel_rows: continue val_a = str(ws.cell(row=row, column=1).value or "").strip() if val_a and cid in val_a: excel_rows[row] = full_text del ps_criteria[cid_key] break # Pass 3: match column B alone still2 = dict(ps_criteria) for cid_key, full_text in still2.items(): cid = full_text.split('. ')[0] for row in range(1, ws.max_row + 1): if row in excel_rows: continue val_b = str(ws.cell(row=row, column=2).value or "").strip() if val_b: norm_b = unidecode(val_b) parts = norm_b.split() if parts and cid in parts[0]: excel_rows[row] = full_text del ps_criteria[cid_key] break print(f'\nMatched rows: {len(excel_rows)}') print(f'Unmatched: {len(ps_criteria)}') if ps_criteria: print('\nUnmatched check IDs:') for k in sorted(ps_criteria.keys()): cid = ps_criteria[k].split('. ')[0] print(f' {cid}: {ps_criteria[k][:80]}') # Build new config new_data = [] for row in sorted(excel_rows.keys()): new_data.append({str(row): excel_rows[row]}) new_config = {"data": new_data} output_path = r'D:\VNPT-M\Tool\audit_tool\NEW\Update\audit_lhdsin\config\windows_config.json' with open(output_path, 'w', encoding='utf-8') as f: json.dump(new_config, f, ensure_ascii=False, indent=4) print(f'\nNew config: {output_path}') print(f'Total entries: {len(new_data)}') # Verify against actual output out_file = r'D:\VNPT-M\Tool\audit_tool\NEW\Update\audit_lhdsin\output\MEVAS-NTL-DATAN_2026_07_29-05_18_36.txt' with open(out_file, 'r', encoding='utf-8', errors='replace') as f: lines = f.read().splitlines() audit_results = {} for line in lines: line = line.strip() if line.startswith('{') and line.endswith('}'): try: parsed = json.loads(line) key = unidecode(str(list(parsed.keys())[0])) audit_results[key] = (str(list(parsed.values())[0]), line) except: pass cfg_names = {unidecode(str(list(item.values())[0])) for item in new_config['data']} output_names = set(audit_results.keys()) matched = cfg_names & output_names output_only = output_names - cfg_names print(f'Output lines: {len(output_names)}') print(f'Config matches output: {len(matched)}/{len(output_names)}') print(f'Output not in config: {len(output_only)}') if output_only: print('Missing from config:') for n in sorted(list(output_only))[:5]: print(f' {n[:80]}')