From 208212a883b3719b19a99ae08892a98c64dd6d02 Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 15 Nov 2025 23:34:07 +0100 Subject: [PATCH] fixes, trmm, win11 --- msp/active_directory_tools.py | 198 ++ msp/msp/doctype/it_object/it_object.json | 16 +- .../msp_documentation/msp_documentation.js | 339 +++ .../msp_documentation/msp_documentation.json | 80 +- msp/tactical-rmm.py | 2505 +++++++++++++++++ msp/win11-amd-cpus.txt | 305 ++ msp/win11-intel-cpus.txt | 790 ++++++ 7 files changed, 4230 insertions(+), 3 deletions(-) create mode 100644 msp/active_directory_tools.py create mode 100644 msp/win11-amd-cpus.txt create mode 100644 msp/win11-intel-cpus.txt diff --git a/msp/active_directory_tools.py b/msp/active_directory_tools.py new file mode 100644 index 0000000..9f5f614 --- /dev/null +++ b/msp/active_directory_tools.py @@ -0,0 +1,198 @@ +import datetime +import uuid +import pandas as pd +from ldap3 import Server, Connection, ALL, NTLM +from dotenv import load_dotenv +import os + +# Load environment variables +load_dotenv() + +# Configuration from environment variables +server_name = os.getenv('LDAP_SERVER') +domain_name = os.getenv('LDAP_DOMAIN') +username = os.getenv('LDAP_USERNAME') # Format: DOMAIN\username +password = os.getenv('LDAP_PASSWORD') + +# Connect to the server +server = Server(server_name, get_info=ALL) +conn = Connection(server, user=username, password=password, authentication=NTLM) + +# Bind to the server +if not conn.bind(): + print('Error in binding to the server') + exit() + +# Get server info for automatic domain detection +server_info = server.info + +# Determine search base - use domain root for recursive search +search_base = None + +# Try environment variable first +env_search_base = os.getenv('LDAP_SEARCH_BASE') +if env_search_base: + search_base = env_search_base +# Try to construct from domain name +elif domain_name: + domain_parts = domain_name.split('.') + search_base = ','.join([f'DC={part}' for part in domain_parts]) +# Try to get from server naming contexts +elif hasattr(server_info, 'naming_contexts') and server_info.naming_contexts: + for nc in server_info.naming_contexts: + nc_str = str(nc) + if nc_str.startswith('DC=') and 'CN=Configuration' not in nc_str and 'CN=Schema' not in nc_str: + search_base = nc_str + break + +# Fallback +if not search_base: + search_base = 'DC=corp,DC=local' + +print(f"Durchsuche rekursiv: {search_base}") + +# Simple user filter (exclude computer accounts) +search_filter = '(&(objectClass=user)(!(sAMAccountName=*$)))' + +attributes = [ + 'sAMAccountName', 'lastLogon', 'lastLogonTimestamp', 'objectGUID', 'userAccountControl', + 'givenName', 'sn', 'cn', 'displayName', 'distinguishedName', + 'userPrincipalName', 'proxyAddresses', 'mail', 'lockoutTime' +] + +# Perform recursive search +print("Starte rekursive Suche...") +try: + from ldap3 import SUBTREE + success = conn.search(search_base, search_filter, search_scope=SUBTREE, attributes=attributes) + + if success and conn.entries: + print(f"Benutzer-Accounts gefunden: {len(conn.entries)}") + + # Remove duplicates based on distinguishedName + seen_dns = set() + unique_entries = [] + for entry in conn.entries: + dn = str(entry.distinguishedName) + if dn not in seen_dns: + seen_dns.add(dn) + unique_entries.append(entry) + + print(f"Nach Duplikat-Entfernung: {len(unique_entries)} eindeutige Benutzer-Accounts") + else: + unique_entries = [] + print("Keine Benutzer-Accounts gefunden") + +except Exception as e: + print(f"Fehler bei der Suche: {str(e)}") + unique_entries = [] + +# Function to convert Windows File Time to human-readable format +def convert_filetime(filetime): + if filetime and isinstance(filetime, int): + return (datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=filetime / 10)).replace(tzinfo=None) + elif filetime and isinstance(filetime, datetime.datetime): + return filetime.replace(tzinfo=None) + else: + return None + +# Function to check if the user is disabled +def is_user_disabled(user_account_control): + return bool(user_account_control & 0x0002) + +# Function to check if the user is locked +def is_user_locked(lockout_time): + return lockout_time and lockout_time != 0 + +# Collecting results in a list +results = [] + +for entry in unique_entries: + username = entry.sAMAccountName.value + last_logon = entry.lastLogon.value + last_logon_timestamp = entry.lastLogonTimestamp.value + object_guid = entry.objectGUID.value + user_account_control = entry.userAccountControl.value + lockout_time = entry.lockoutTime.value + + # Convert timestamps + last_logon_date = convert_filetime(last_logon) + last_logon_timestamp_date = convert_filetime(last_logon_timestamp) + + # Determine the most recent logon date + if last_logon_date and last_logon_timestamp_date: + most_recent_logon = max(last_logon_date, last_logon_timestamp_date) + else: + most_recent_logon = last_logon_date or last_logon_timestamp_date or 'Never logged in' + + guid_string = object_guid + disabled_status = "Disabled" if is_user_disabled(user_account_control) else "Enabled" + locked_status = "Locked" if is_user_locked(lockout_time) else "Unlocked" + + given_name = entry.givenName.value if entry.givenName else 'N/A' + sn = entry.sn.value if entry.sn else 'N/A' + cn = entry.cn.value if entry.cn else 'N/A' + display_name = entry.displayName.value if entry.displayName else 'N/A' + distinguished_name = entry.distinguishedName.value if entry.distinguishedName else 'N/A' + user_principal_name = entry.userPrincipalName.value if entry.userPrincipalName else 'N/A' + proxy_addresses = ', '.join(entry.proxyAddresses.values) if entry.proxyAddresses else 'N/A' + mail = entry.mail.value if entry.mail else 'N/A' + + # Skip computer accounts based on the presence of a dollar sign in the username + if not username.endswith('$'): + results.append({ + 'User': username, + 'Given Name': given_name, + 'Surname': sn, + 'Common Name': cn, + 'Display Name': display_name, + 'Distinguished Name': distinguished_name, + 'User Principal Name': user_principal_name, + 'Proxy Addresses': proxy_addresses, + 'Mail': mail, + 'Most Recent Logon': most_recent_logon, + 'GUID': guid_string, + 'Status': disabled_status, + 'Lockout Status': locked_status + }) + +# Unbind the connection +conn.unbind() + +# Create a DataFrame and export to Excel +df = pd.DataFrame(results) + +# Check if we have any results +if len(results) > 0: + # Ensure all datetime columns are timezone-unaware + datetime_columns = ['Most Recent Logon'] + for column in datetime_columns: + if column in df.columns: + df[column] = df[column].apply(lambda x: x.replace(tzinfo=None) if isinstance(x, datetime.datetime) else x) + + df.to_excel('active_directory_users.xlsx', index=False) + print(f"Benutzer-Accounts erfolgreich exportiert. Gefundene Benutzer: {len(results)}") +else: + print("\n=== DEBUGGING-INFORMATIONEN ===") + print("Keine Benutzer-Accounts gefunden.") + print(f"Search-Base: {search_base}") + print(f"Search-Filter: {search_filter}") + print(f"LDAP-Server: {server_name}") + print(f"Domain: {domain_name}") + + if server_info and hasattr(server_info, 'naming_contexts'): + print(f"Server Naming Contexts: {list(server_info.naming_contexts)}") + if server_info and hasattr(server_info, 'schema_entry'): + print(f"Schema Entry: {server_info.schema_entry}") + + print("\nBitte überprüfen Sie:") + print("1. LDAP-Verbindung und Anmeldedaten") + print("2. Search-Base Konfiguration") + print("3. Berechtigungen für die Benutzer-Suche") + print("4. Domain-Controller Erreichbarkeit") + + # Create empty Excel file with headers for reference + headers = ['User', 'Given Name', 'Surname', 'Common Name', 'Display Name', 'Distinguished Name', + 'User Principal Name', 'Proxy Addresses', 'Mail', 'Most Recent Logon', 'GUID', 'Status', 'Lockout Status'] + empty_df = pd.DataFrame(columns=headers) + empty_df.to_excel('active_directory_users.xlsx', index=False) \ No newline at end of file diff --git a/msp/msp/doctype/it_object/it_object.json b/msp/msp/doctype/it_object/it_object.json index 588cc14..e6a20f1 100644 --- a/msp/msp/doctype/it_object/it_object.json +++ b/msp/msp/doctype/it_object/it_object.json @@ -29,8 +29,10 @@ "network_config_section", "ip_adresses", "rmm_data_section", + "hardware_attributes", "rmm_specs", "rmm_software", + "created_from_rmm", "external_links_section", "admin_interface_link", "monitoring_link", @@ -238,11 +240,23 @@ "fieldname": "visible_in_documentation", "fieldtype": "Check", "label": "visible in Documentation" + }, + { + "default": "0", + "fieldname": "created_from_rmm", + "fieldtype": "Check", + "label": "Created From RMM" + }, + { + "fieldname": "hardware_attributes", + "fieldtype": "Table", + "label": "Hardware Attributes", + "options": "IT Object Hardware Attribute" } ], "image_field": "image", "links": [], - "modified": "2025-03-10 14:55:30.929828", + "modified": "2025-07-31 16:07:20.612702", "modified_by": "Administrator", "module": "MSP", "name": "IT Object", diff --git a/msp/msp/doctype/msp_documentation/msp_documentation.js b/msp/msp/doctype/msp_documentation/msp_documentation.js index 1d29415..c40c50f 100644 --- a/msp/msp/doctype/msp_documentation/msp_documentation.js +++ b/msp/msp/doctype/msp_documentation/msp_documentation.js @@ -82,6 +82,345 @@ frappe.ui.form.on('MSP Documentation', { } }); }, 'Workflow'); + + // Add button to fetch and store all RMM agent data as JSON + frm.add_custom_button('4. RMM Daten speichern', function(){ + frappe.dom.freeze('RMM-Daten werden abgerufen und gespeichert...'); + frappe.call({ + method: 'msp.tactical-rmm.fetch_and_store_all_agent_data', + args: { + documentation_name: frm.doc.name + }, + callback: function(r) { + frappe.dom.unfreeze(); + if (r.exc) { + frappe.msgprint({ + title: __('Fehler'), + indicator: 'red', + message: __('RMM-Daten konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.') + }); + return; + } + if (r.message && r.message.success) { + frappe.show_alert({ + message: r.message.message || __('RMM-Daten erfolgreich gespeichert'), + indicator: 'green' + }); + frm.reload_doc(); + } + } + }); + }, 'Workflow'); + + // Add button to fetch and store Active Directory computer data as JSON + frm.add_custom_button('5. AD Computer-Daten speichern', function(){ + frappe.dom.freeze('AD-Computer-Daten werden abgerufen und gespeichert...'); + frappe.call({ + method: 'msp.tactical-rmm.fetch_and_store_ad_computer_data', + args: { + documentation_name: frm.doc.name + }, + callback: function(r) { + frappe.dom.unfreeze(); + if (r.exc) { + frappe.msgprint({ + title: __('Fehler'), + indicator: 'red', + message: __('AD-Computer-Daten konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.') + }); + return; + } + if (r.message && r.message.success) { + frappe.show_alert({ + message: r.message.message || __('AD-Computer-Daten erfolgreich gespeichert'), + indicator: 'green' + }); + frm.reload_doc(); + } + } + }); + }, 'Workflow'); + + // Add button to fetch and store Active Directory user data as JSON + frm.add_custom_button('6. AD Benutzer-Daten speichern', function(){ + frappe.dom.freeze('AD-Benutzer-Daten werden abgerufen und gespeichert...'); + frappe.call({ + method: 'msp.tactical-rmm.fetch_and_store_ad_user_data', + args: { + documentation_name: frm.doc.name + }, + callback: function(r) { + frappe.dom.unfreeze(); + if (r.exc) { + frappe.msgprint({ + title: __('Fehler'), + indicator: 'red', + message: __('AD-Benutzer-Daten konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.') + }); + return; + } + if (r.message && r.message.success) { + frappe.show_alert({ + message: r.message.message || __('AD-Benutzer-Daten erfolgreich gespeichert'), + indicator: 'green' + }); + frm.reload_doc(); + } + } + }); + }, 'Workflow'); + + // Add button to compare RMM and AD data + frm.add_custom_button('7. RMM ↔ AD Abgleich', function(){ + frappe.dom.freeze('RMM- und AD-Daten werden abgeglichen...'); + frappe.call({ + method: 'msp.tactical-rmm.compare_rmm_and_ad_data', + args: { + documentation_name: frm.doc.name + }, + callback: function(r) { + frappe.dom.unfreeze(); + if (r.exc) { + frappe.msgprint({ + title: __('Fehler'), + indicator: 'red', + message: __('Datenabgleich konnte nicht durchgeführt werden. Bitte versuchen Sie es erneut.') + }); + return; + } + if (r.message && r.message.success) { + let stats = r.message.stats; + let details = `${stats.total_computers} Computer analysiert: ` + + `${stats.in_both} in beiden Systemen, ` + + `${stats.only_in_rmm} nur RMM, ` + + `${stats.only_in_ad} nur AD`; + + frappe.show_alert({ + message: __('Datenabgleich erfolgreich abgeschlossen. ') + details, + indicator: 'green' + }); + frm.reload_doc(); + } + } + }); + }, 'Workflow'); + + // Add button for Windows 11 compatibility check + frm.add_custom_button('8. Windows 11 Check', function(){ + frappe.dom.freeze('Windows 11 CPU-Kompatibilität wird geprüft...'); + frappe.call({ + method: 'msp.tactical-rmm.check_windows11_compatibility', + args: { + documentation_name: frm.doc.name + }, + callback: function(r) { + frappe.dom.unfreeze(); + if (r.exc) { + frappe.msgprint({ + title: __('Fehler'), + indicator: 'red', + message: __('Windows 11 Kompatibilitätsprüfung konnte nicht durchgeführt werden. Bitte versuchen Sie es erneut.') + }); + return; + } + if (r.message && r.message.success) { + let stats = r.message.stats; + let details = `${stats.total_non_win11} Systeme ohne Windows 11 analysiert: ` + + `${stats.compatible_cpus} kompatible CPUs, ` + + `${stats.incompatible_cpus} inkompatible CPUs, ` + + `${stats.unknown_cpus} unbekannte CPUs`; + + frappe.show_alert({ + message: __('Windows 11 Kompatibilitätsprüfung abgeschlossen. ') + details, + indicator: 'green' + }); + frm.reload_doc(); + } + } + }); + }, 'Workflow'); + + // Add debug button for CPU compatibility + frm.add_custom_button('🔍 CPU Debug', function(){ + frappe.prompt([ + { + 'fieldname': 'test_cpu', + 'label': 'Test CPU (leer = automatisch)', + 'fieldtype': 'Data', + 'reqd': 0, + 'description': 'Z.B: Intel(R) Core(TM) i3-8100 CPU @ 3.60GHz, 4C/4T' + } + ], function(values) { + frappe.dom.freeze('CPU-Kompatibilität wird debuggt...'); + frappe.call({ + method: 'msp.tactical-rmm.debug_cpu_compatibility', + args: { + documentation_name: frm.doc.name, + test_cpu_string: values.test_cpu || null + }, + callback: function(r) { + frappe.dom.unfreeze(); + if (r.exc) { + frappe.msgprint({ + title: __('Fehler'), + indicator: 'red', + message: __('CPU-Debug konnte nicht durchgeführt werden: ') + r.exc + }); + return; + } + if (r.message && r.message.success) { + let debug_info = r.message.debug_info; + let result = r.message.result; + let test_cpu = r.message.test_cpu; + + // Debug-Dialog erstellen + let debug_html = ` +
+

🔍 CPU-Kompatibilitäts Debug

+
Test-CPU: ${test_cpu}
+
System-CPU (uppercase): ${debug_info.system_cpu_upper || 'N/A'}
+
Vendor: ${debug_info.vendor || 'N/A'}
+
Suchset-Größe: ${debug_info.search_set_size || 0}
+
Ergebnis: + ${result.compatible ? '✅ KOMPATIBEL' : '❌ NICHT KOMPATIBEL'} (${result.status}) +
+ ${debug_info.match_found ? `
Gefundene CPU: ${debug_info.matching_cpu}
` : ''} + +
📁 Dateipfad-Informationen:
+
+
App-Pfad: ${debug_info.app_path || 'N/A'}
+ ${debug_info.files_info ? Object.keys(debug_info.files_info).map(vendor => { + const info = debug_info.files_info[vendor]; + const statusColor = info.exists ? 'green' : 'red'; + const statusIcon = info.exists ? '✅' : '❌'; + return ` +
+
${vendor.toUpperCase()} CPUs: ${statusIcon}
+
+ Pfad: ${info.path}
+ Existiert: ${info.exists ? 'Ja' : 'Nein'}
+ ${info.exists ? `Dateigröße: ${info.size} Bytes` : ''} +
+
+ `; + }).join('') : 'Keine Dateipfad-Informationen verfügbar'} + ${debug_info.loaded_counts ? ` +
+ Geladene CPUs: + AMD: ${debug_info.loaded_counts.amd}, + Intel: ${debug_info.loaded_counts.intel} +
+ ` : ''} + ${debug_info.error ? ` +
+ Fehler: ${debug_info.error} +
+ ` : ''} +
+ +
🔎 Vergleiche (erste ${debug_info.comparisons ? debug_info.comparisons.length : 0}):
+
+ `; + + if (debug_info.comparisons) { + debug_info.comparisons.forEach((comp, i) => { + let color = comp.match_result ? 'green' : '#666'; + let icon = comp.match_result ? '✅' : '❌'; + debug_html += ` +
+
${icon} ${comp.match_type}: ${comp.supported_cpu}
+ ${comp.extracted_part ? `
Extrahiert: ${comp.extracted_part}
` : ''} +
Details: ${comp.details}
+
+ `; + }); + } + + if (debug_info.truncated) { + debug_html += '
... weitere Vergleiche abgeschnitten ...
'; + } + + debug_html += ` +
+
+ `; + + frappe.msgprint({ + title: 'CPU-Kompatibilitäts Debug', + message: debug_html, + indicator: result.compatible ? 'green' : 'red' + }); + } + } + }); + }, 'CPU Debug Test', 'Testen'); + }, 'Workflow'); + + // Add Excel Export button + frm.add_custom_button('📊 Excel Export', function(){ + frappe.dom.freeze('Excel-Export wird erstellt...'); + frappe.call({ + method: 'msp.tactical-rmm.export_tables_to_excel', + args: { + documentation_name: frm.doc.name + }, + callback: function(r) { + frappe.dom.unfreeze(); + if (r.exc) { + frappe.msgprint({ + title: __('Fehler'), + indicator: 'red', + message: __('Excel-Export konnte nicht erstellt werden: ') + r.exc + }); + return; + } + if (r.message && r.message.success) { + let filename = r.message.filename; + let content = r.message.content; + + // Excel-Datei herunterladen + try { + // Base64 zu Blob konvertieren + const byteCharacters = atob(content); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + const blob = new Blob([byteArray], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }); + + // Download-Link erstellen + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + const timestamp = new Date().toLocaleString('de-DE'); + a.style.display = 'none'; + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + frappe.show_alert({ + message: __('Excel-Export erfolgreich heruntergeladen: ') + filename, + indicator: 'green' + }); + + } catch (download_error) { + console.error('Download-Fehler:', download_error); + frappe.msgprint({ + title: __('Download-Fehler'), + indicator: 'red', + message: __('Die Excel-Datei konnte nicht heruntergeladen werden. Bitte versuchen Sie es erneut.') + }); + } + + } + } + }); + }, 'Export'); } }); diff --git a/msp/msp/doctype/msp_documentation/msp_documentation.json b/msp/msp/doctype/msp_documentation/msp_documentation.json index 9fea7c3..edc1b18 100644 --- a/msp/msp/doctype/msp_documentation/msp_documentation.json +++ b/msp/msp/doctype/msp_documentation/msp_documentation.json @@ -18,7 +18,19 @@ "server_list", "workstation_list", "backup", - "aditional_data" + "aditional_data", + "data_acquisition_section", + "credentials_for_ldap_acquisistion", + "domain_controller_for_ldap_acquisition", + "column_break_ydaj", + "upn", + "ip_address", + "json_data_section", + "rmm_data_json", + "ad_computer_data_json", + "ad_user_data_json", + "output", + "windows_11_check_output" ], "fields": [ { @@ -88,14 +100,78 @@ "fieldname": "tactical_rmm_site_name", "fieldtype": "Data", "label": "Tactical RMM Site Name" + }, + { + "collapsible": 1, + "fieldname": "json_data_section", + "fieldtype": "Section Break", + "label": "JSON Data" + }, + { + "fieldname": "rmm_data_json", + "fieldtype": "Long Text", + "label": "RMM Data JSON" + }, + { + "fieldname": "ad_computer_data_json", + "fieldtype": "Long Text", + "label": "AD Computer Data JSON" + }, + { + "fieldname": "ad_user_data_json", + "fieldtype": "Long Text", + "label": "AD User Data JSON" + }, + { + "fieldname": "data_acquisition_section", + "fieldtype": "Section Break", + "label": "Data Acquisition" + }, + { + "fieldname": "credentials_for_ldap_acquisistion", + "fieldtype": "Link", + "label": "Credentials for LDAP Acquisistion", + "options": "IT User Account" + }, + { + "fieldname": "domain_controller_for_ldap_acquisition", + "fieldtype": "Link", + "label": "Domain Controller for LDAP Acquisition", + "options": "IT Object" + }, + { + "fieldname": "column_break_ydaj", + "fieldtype": "Column Break" + }, + { + "fieldname": "upn", + "fieldtype": "Data", + "label": "UPN" + }, + { + "fieldname": "ip_address", + "fieldtype": "Data", + "label": "IP Address" + }, + { + "fieldname": "output", + "fieldtype": "Text Editor", + "label": "Output", + "read_only": 1 + }, + { + "fieldname": "windows_11_check_output", + "fieldtype": "HTML", + "label": "Windows 11 Check Output" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-03-11 08:06:34.303790", + "modified": "2025-07-31 18:45:31.838030", "modified_by": "Administrator", "module": "MSP", "name": "MSP Documentation", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { diff --git a/msp/tactical-rmm.py b/msp/tactical-rmm.py index 609d637..540abc9 100644 --- a/msp/tactical-rmm.py +++ b/msp/tactical-rmm.py @@ -5,6 +5,9 @@ import requests import json from pprint import pprint import re +import datetime +import os +from ldap3 import Server, Connection, ALL, NTLM, SUBTREE from .tools import render_card_html, render_single_card @frappe.whitelist() @@ -250,6 +253,2508 @@ def render_model(model): return model +@frappe.whitelist() +def fetch_and_store_all_agent_data(documentation_name): + """ + Holt Agent-Daten aus dem RMM (gefiltert nach Mandant und optional Site) + und speichert sie als JSON im rmm_data_json Feld der angegebenen MSP Documentation. + + Args: + documentation_name (str): Name der MSP Documentation, in der die Daten gespeichert werden sollen + + Returns: + dict: Erfolgsmeldung mit Anzahl der gespeicherten Agents + """ + try: + # MSP Documentation Dokument laden + documentation_doc = frappe.get_doc("MSP Documentation", documentation_name) + + # Prüfen ob Tenant Caption vorhanden ist + if not documentation_doc.tactical_rmm_tenant_caption: + frappe.throw("Tenant Caption fehlt in der MSP Documentation") + + client_name = documentation_doc.tactical_rmm_tenant_caption + site_name = documentation_doc.tactical_rmm_site_name + + # Alle Agent-Daten vom RMM holen + all_agents = get_all_agents() + + # Nach Mandant und optional nach Site filtern + filtered_agents = [] + for agent in all_agents: + # Filter by client_name and optionally by site_name if provided + if agent["client_name"] == client_name and (not site_name or agent["site_name"] == site_name): + filtered_agents.append(agent) + + # Gefilterte Daten als JSON serialisieren und im rmm_data_json Feld speichern + documentation_doc.rmm_data_json = json.dumps(filtered_agents, indent=4, default=str) + + # Dokument speichern + documentation_doc.save() + + # Erfolgsmeldung zusammenstellen + filter_info = f"Mandant '{client_name}'" + if site_name: + filter_info += f" und Site '{site_name}'" + + # Erfolgsmeldung zurückgeben + return { + "success": True, + "message": f"RMM-Daten erfolgreich gespeichert. {len(filtered_agents)} Agents gefunden für {filter_info}.", + "agent_count": len(filtered_agents), + "documentation": documentation_name, + "filter": { + "client_name": client_name, + "site_name": site_name + } + } + + except Exception as e: + frappe.log_error(f"Fehler beim Speichern der RMM-Daten: {str(e)}", "fetch_and_store_all_agent_data") + frappe.throw(f"Fehler beim Speichern der RMM-Daten: {str(e)}") + + +@frappe.whitelist() +def fetch_and_store_ad_computer_data(documentation_name): + """ + Holt Computer-Daten aus dem Active Directory (gefiltert nach Domain) + und speichert sie als JSON im ad_computer_data_json Feld der angegebenen MSP Documentation. + + Args: + documentation_name (str): Name der MSP Documentation, in der die Daten gespeichert werden sollen + + Returns: + dict: Erfolgsmeldung mit Anzahl der gefundenen Computer + """ + try: + # MSP Documentation Dokument laden + documentation_doc = frappe.get_doc("MSP Documentation", documentation_name) + + # Prüfen ob LDAP-Credentials vorhanden sind + if not documentation_doc.credentials_for_ldap_acquisistion: + frappe.throw("LDAP-Credentials fehlen in der MSP Documentation") + + # IT User Account mit LDAP-Credentials laden + ldap_credentials = frappe.get_doc("IT User Account", documentation_doc.credentials_for_ldap_acquisistion) + + # Prüfen ob alle notwendigen Daten vorhanden sind + if not ldap_credentials.username: + frappe.throw("Benutzername fehlt in den LDAP-Credentials") + if not ldap_credentials.password: + frappe.throw("Passwort fehlt in den LDAP-Credentials") + if not ldap_credentials.domain: + frappe.throw("Domain fehlt in den LDAP-Credentials") + + # LDAP-Verbindungsparameter extrahieren + domain = ldap_credentials.domain + username = f"{domain}\\{ldap_credentials.username}" + password = ldap_credentials.get_password() + + # Server-Name ermitteln - entweder aus Domain Controller oder Domain + server_name = domain # Standard-Fallback + + # Prüfen ob Domain Controller für LDAP-Acquisition angegeben ist + if documentation_doc.domain_controller_for_ldap_acquisition: + try: + # IT Object (Domain Controller) laden + domain_controller = frappe.get_doc("IT Object", documentation_doc.domain_controller_for_ldap_acquisition) + + # Prüfen ob eine Haupt-IP-Adresse angegeben ist + if domain_controller.main_ip: + # IP Address Dokument laden + ip_address_doc = frappe.get_doc("IP Address", domain_controller.main_ip) + + # IP-Adresse als Server-Name verwenden + if ip_address_doc.ip_address: + server_name = ip_address_doc.ip_address + print(f"INFO: Domain Controller IP-Adresse verwendet: {server_name}") + else: + print("WARNING: IP Address Dokument hat keine ip_address - verwende Domain als Fallback") + else: + print("WARNING: IT Object hat keine main_ip - verwende Domain als Fallback") + except Exception as dc_error: + frappe.log_error(f"Fehler beim Laden des Domain Controllers: {str(dc_error)} - verwende Domain als Fallback", "fetch_and_store_ad_computer_data") + else: + print("INFO: Kein Domain Controller angegeben - verwende Domain als Server-Name") + + # Search Base aus Domain konstruieren + domain_parts = domain.split('.') + search_base = ','.join([f'DC={part}' for part in domain_parts]) + + # LDAP-Server verbinden + server = Server(server_name, get_info=ALL) + conn = Connection(server, user=username, password=password, authentication=NTLM) + + # Verbindung herstellen + if not conn.bind(): + frappe.throw(f"LDAP-Verbindung fehlgeschlagen. Prüfen Sie Server, Benutzername und Passwort.") + + # Server-Info für automatische Domain-Erkennung + server_info = server.info + + # Search Base validieren und ggf. aus Server-Info extrahieren + if hasattr(server_info, 'naming_contexts') and server_info.naming_contexts: + for nc in server_info.naming_contexts: + nc_str = str(nc) + if nc_str.startswith('DC=') and 'CN=Configuration' not in nc_str and 'CN=Schema' not in nc_str: + search_base = nc_str + break + + # Computer-Filter und Attribute definieren + search_filter = '(objectClass=computer)' + attributes = [ + 'sAMAccountName', 'lastLogon', 'lastLogonTimestamp', 'objectGUID', 'userAccountControl', + 'cn', 'displayName', 'distinguishedName', 'dNSHostName', 'operatingSystem', + 'operatingSystemVersion', 'operatingSystemServicePack', 'description', + 'lockoutTime', 'pwdLastSet', 'whenCreated', 'whenChanged' + ] + + # LDAP-Suche durchführen + success = conn.search(search_base, search_filter, search_scope=SUBTREE, attributes=attributes) + + if not success or not conn.entries: + conn.unbind() + frappe.throw(f"Keine Computer-Accounts gefunden. Search Base: {search_base}") + + # Duplikate basierend auf distinguishedName entfernen + seen_dns = set() + unique_entries = [] + for entry in conn.entries: + dn = str(entry.distinguishedName) + if dn not in seen_dns: + seen_dns.add(dn) + unique_entries.append(entry) + + # Ergebnisse sammeln + results = [] + + for entry in unique_entries: + # Nur Computer-Accounts (die mit $ enden) berücksichtigen + computer_name = entry.sAMAccountName.value + if not computer_name or not computer_name.endswith('$'): + continue + + # Zeitstempel konvertieren + last_logon = _convert_filetime(entry.lastLogon.value) + last_logon_timestamp = _convert_filetime(entry.lastLogonTimestamp.value) + pwd_last_set = _convert_filetime(entry.pwdLastSet.value) + + # Neueste Anmeldung ermitteln + if last_logon and last_logon_timestamp: + most_recent_logon = max(last_logon, last_logon_timestamp) + else: + most_recent_logon = last_logon or last_logon_timestamp or 'Never logged in' + + # Computer-Status ermitteln + user_account_control = entry.userAccountControl.value or 0 + disabled_status = "Disabled" if (user_account_control & 0x0002) else "Enabled" + lockout_time = entry.lockoutTime.value or 0 + locked_status = "Locked" if lockout_time and lockout_time != 0 else "Unlocked" + + # Daten sammeln + computer_data = { + 'Computer Name': computer_name, + 'Common Name': entry.cn.value if entry.cn else 'N/A', + 'Display Name': entry.displayName.value if entry.displayName else 'N/A', + 'Distinguished Name': entry.distinguishedName.value if entry.distinguishedName else 'N/A', + 'DNS Host Name': entry.dNSHostName.value if entry.dNSHostName else 'N/A', + 'Operating System': entry.operatingSystem.value if entry.operatingSystem else 'N/A', + 'OS Version': entry.operatingSystemVersion.value if entry.operatingSystemVersion else 'N/A', + 'OS Service Pack': entry.operatingSystemServicePack.value if entry.operatingSystemServicePack else 'N/A', + 'Description': entry.description.value if entry.description else 'N/A', + 'Most Recent Logon': most_recent_logon.isoformat() if isinstance(most_recent_logon, datetime.datetime) else str(most_recent_logon), + 'Password Last Set': pwd_last_set.isoformat() if isinstance(pwd_last_set, datetime.datetime) else str(pwd_last_set) if pwd_last_set else 'N/A', + 'When Created': entry.whenCreated.value.isoformat() if entry.whenCreated and entry.whenCreated.value else 'N/A', + 'When Changed': entry.whenChanged.value.isoformat() if entry.whenChanged and entry.whenChanged.value else 'N/A', + 'GUID': str(entry.objectGUID.value) if entry.objectGUID.value else 'N/A', + 'Status': disabled_status, + 'Lockout Status': locked_status + } + + results.append(computer_data) + + # LDAP-Verbindung schließen + conn.unbind() + + # Daten als JSON serialisieren und im ad_computer_data_json Feld speichern + documentation_doc.ad_computer_data_json = json.dumps(results, indent=4, default=str) + + # Dokument speichern + documentation_doc.save() + + # Erfolgsmeldung zurückgeben + server_info = f" über Server '{server_name}'" if server_name != domain else "" + return { + "success": True, + "message": f"AD-Computer-Daten erfolgreich gespeichert. {len(results)} Computer gefunden in Domain '{domain}'{server_info}.", + "computer_count": len(results), + "documentation": documentation_name, + "domain": domain, + "server": server_name, + "search_base": search_base + } + + except Exception as e: + frappe.log_error(f"Fehler beim Speichern der AD-Computer-Daten: {str(e)}", "fetch_and_store_ad_computer_data") + frappe.throw(f"Fehler beim Speichern der AD-Computer-Daten: {str(e)}") + + +@frappe.whitelist() +def fetch_and_store_ad_user_data(documentation_name): + """ + Holt Benutzer-Daten aus dem Active Directory (gefiltert nach Domain) + und speichert sie als JSON im ad_user_data_json Feld der angegebenen MSP Documentation. + + Args: + documentation_name (str): Name der MSP Documentation, in der die Daten gespeichert werden sollen + + Returns: + dict: Erfolgsmeldung mit Anzahl der gefundenen Benutzer + """ + try: + # MSP Documentation Dokument laden + documentation_doc = frappe.get_doc("MSP Documentation", documentation_name) + + # Prüfen ob LDAP-Credentials vorhanden sind + if not documentation_doc.credentials_for_ldap_acquisistion: + frappe.throw("LDAP-Credentials fehlen in der MSP Documentation") + + # IT User Account mit LDAP-Credentials laden + ldap_credentials = frappe.get_doc("IT User Account", documentation_doc.credentials_for_ldap_acquisistion) + + # Prüfen ob alle notwendigen Daten vorhanden sind + if not ldap_credentials.username: + frappe.throw("Benutzername fehlt in den LDAP-Credentials") + if not ldap_credentials.password: + frappe.throw("Passwort fehlt in den LDAP-Credentials") + if not ldap_credentials.domain: + frappe.throw("Domain fehlt in den LDAP-Credentials") + + # LDAP-Verbindungsparameter extrahieren + domain = ldap_credentials.domain + username = f"{domain}\\{ldap_credentials.username}" + password = ldap_credentials.get_password() + + # Server-Name ermitteln - entweder aus Domain Controller oder Domain + server_name = domain # Standard-Fallback + + # Prüfen ob Domain Controller für LDAP-Acquisition angegeben ist + if documentation_doc.domain_controller_for_ldap_acquisition: + try: + # IT Object (Domain Controller) laden + domain_controller = frappe.get_doc("IT Object", documentation_doc.domain_controller_for_ldap_acquisition) + + # Prüfen ob eine Haupt-IP-Adresse angegeben ist + if domain_controller.main_ip: + # IP Address Dokument laden + ip_address_doc = frappe.get_doc("IP Address", domain_controller.main_ip) + + # IP-Adresse als Server-Name verwenden + if ip_address_doc.ip_address: + server_name = ip_address_doc.ip_address + print(f"INFO: Domain Controller IP-Adresse verwendet: {server_name}") + else: + print("WARNING: IP Address Dokument hat keine ip_address - verwende Domain als Fallback") + else: + print("WARNING: IT Object hat keine main_ip - verwende Domain als Fallback") + except Exception as dc_error: + frappe.log_error(f"Fehler beim Laden des Domain Controllers: {str(dc_error)} - verwende Domain als Fallback", "fetch_and_store_ad_user_data") + else: + print("INFO: Kein Domain Controller angegeben - verwende Domain als Server-Name") + + # Search Base aus Domain konstruieren + domain_parts = domain.split('.') + search_base = ','.join([f'DC={part}' for part in domain_parts]) + + # LDAP-Server verbinden + server = Server(server_name, get_info=ALL) + conn = Connection(server, user=username, password=password, authentication=NTLM) + + # Verbindung herstellen + if not conn.bind(): + frappe.throw(f"LDAP-Verbindung fehlgeschlagen. Prüfen Sie Server, Benutzername und Passwort.") + + # Server-Info für automatische Domain-Erkennung + server_info = server.info + + # Search Base validieren und ggf. aus Server-Info extrahieren + if hasattr(server_info, 'naming_contexts') and server_info.naming_contexts: + for nc in server_info.naming_contexts: + nc_str = str(nc) + if nc_str.startswith('DC=') and 'CN=Configuration' not in nc_str and 'CN=Schema' not in nc_str: + search_base = nc_str + break + + # Benutzer-Filter und Attribute definieren (Computer-Accounts ausschließen) + search_filter = '(&(objectClass=user)(!(sAMAccountName=*$)))' + attributes = [ + 'sAMAccountName', 'lastLogon', 'lastLogonTimestamp', 'objectGUID', 'userAccountControl', + 'givenName', 'sn', 'cn', 'displayName', 'distinguishedName', + 'userPrincipalName', 'proxyAddresses', 'mail', 'lockoutTime' + ] + + # LDAP-Suche durchführen + success = conn.search(search_base, search_filter, search_scope=SUBTREE, attributes=attributes) + + if not success or not conn.entries: + conn.unbind() + frappe.throw(f"Keine Benutzer-Accounts gefunden. Search Base: {search_base}") + + # Duplikate basierend auf distinguishedName entfernen + seen_dns = set() + unique_entries = [] + for entry in conn.entries: + dn = str(entry.distinguishedName) + if dn not in seen_dns: + seen_dns.add(dn) + unique_entries.append(entry) + + # Ergebnisse sammeln + results = [] + + for entry in unique_entries: + # Nur echte Benutzer-Accounts (die nicht mit $ enden) + username_sam = entry.sAMAccountName.value + if not username_sam or username_sam.endswith('$'): + continue + + # Zeitstempel konvertieren + last_logon = _convert_filetime(entry.lastLogon.value) + last_logon_timestamp = _convert_filetime(entry.lastLogonTimestamp.value) + + # Neueste Anmeldung ermitteln + if last_logon and last_logon_timestamp: + most_recent_logon = max(last_logon, last_logon_timestamp) + else: + most_recent_logon = last_logon or last_logon_timestamp or 'Never logged in' + + # Benutzer-Status ermitteln + user_account_control = entry.userAccountControl.value or 0 + disabled_status = "Disabled" if (user_account_control & 0x0002) else "Enabled" + lockout_time = entry.lockoutTime.value or 0 + locked_status = "Locked" if lockout_time and lockout_time != 0 else "Unlocked" + + # Daten sammeln + user_data = { + 'User': username_sam, + 'Given Name': entry.givenName.value if entry.givenName else 'N/A', + 'Surname': entry.sn.value if entry.sn else 'N/A', + 'Common Name': entry.cn.value if entry.cn else 'N/A', + 'Display Name': entry.displayName.value if entry.displayName else 'N/A', + 'Distinguished Name': entry.distinguishedName.value if entry.distinguishedName else 'N/A', + 'User Principal Name': entry.userPrincipalName.value if entry.userPrincipalName else 'N/A', + 'Proxy Addresses': ', '.join(entry.proxyAddresses.values) if entry.proxyAddresses else 'N/A', + 'Mail': entry.mail.value if entry.mail else 'N/A', + 'Most Recent Logon': most_recent_logon.isoformat() if isinstance(most_recent_logon, datetime.datetime) else str(most_recent_logon), + 'Last Logon': last_logon.isoformat() if isinstance(last_logon, datetime.datetime) else str(last_logon) if last_logon else 'N/A', + 'Last Logon Timestamp': last_logon_timestamp.isoformat() if isinstance(last_logon_timestamp, datetime.datetime) else str(last_logon_timestamp) if last_logon_timestamp else 'N/A', + 'GUID': str(entry.objectGUID.value) if entry.objectGUID.value else 'N/A', + 'Status': disabled_status, + 'Lockout Status': locked_status + } + + results.append(user_data) + + # LDAP-Verbindung schließen + conn.unbind() + + # Daten als JSON serialisieren und im ad_user_data_json Feld speichern + documentation_doc.ad_user_data_json = json.dumps(results, indent=4, default=str) + + # Dokument speichern + documentation_doc.save() + + # Erfolgsmeldung zurückgeben + server_info = f" über Server '{server_name}'" if server_name != domain else "" + return { + "success": True, + "message": f"AD-Benutzer-Daten erfolgreich gespeichert. {len(results)} Benutzer gefunden in Domain '{domain}'{server_info}.", + "user_count": len(results), + "documentation": documentation_name, + "domain": domain, + "server": server_name, + "search_base": search_base + } + + except Exception as e: + frappe.log_error(f"Fehler beim Speichern der AD-Benutzer-Daten: {str(e)}", "fetch_and_store_ad_user_data") + frappe.throw(f"Fehler beim Speichern der AD-Benutzer-Daten: {str(e)}") + + +@frappe.whitelist() +def compare_rmm_and_ad_data(documentation_name): + """ + Vergleicht RMM- und AD-Computer-Daten und erstellt eine HTML-Tabelle mit Statistiken. + + Args: + documentation_name (str): Name der MSP Documentation + + Returns: + dict: Erfolgsmeldung mit Zusammenfassung + """ + try: + # MSP Documentation Dokument laden + documentation_doc = frappe.get_doc("MSP Documentation", documentation_name) + + # JSON-Daten laden und parsen + rmm_data = [] + ad_data = [] + + if documentation_doc.rmm_data_json: + try: + rmm_data = json.loads(documentation_doc.rmm_data_json) + except json.JSONDecodeError: + frappe.throw("RMM Data JSON ist nicht gültig") + else: + frappe.throw("Keine RMM-Daten vorhanden. Bitte zuerst RMM-Daten speichern.") + + if documentation_doc.ad_computer_data_json: + try: + ad_data = json.loads(documentation_doc.ad_computer_data_json) + except json.JSONDecodeError: + frappe.throw("AD Computer Data JSON ist nicht gültig") + else: + frappe.throw("Keine AD-Computer-Daten vorhanden. Bitte zuerst AD-Computer-Daten speichern.") + + # Daten für Abgleich vorbereiten + rmm_by_hostname = {} + for rmm_item in rmm_data: + hostname = rmm_item.get('hostname', '').upper() + if hostname: + rmm_by_hostname[hostname] = rmm_item + + # AD-Daten mit intelligentem Matching vorbereiten (ohne Duplikate) + ad_by_hostname = {} + ad_items_processed = set() # Verhindert Duplikate + + # Erstelle ein Mapping aller möglichen Namen zu AD-Items + name_to_ad_item = {} + for ad_item in ad_data: + # Eindeutige ID für dieses AD-Item erstellen + ad_id = ad_item.get('GUID', str(id(ad_item))) + + # Alle verfügbaren Namen sammeln + potential_names = [] + + # 1. Common Name (ohne $-Zeichen) + common_name = ad_item.get('Common Name', '').upper() + if common_name and common_name != 'N/A': + potential_names.append(common_name) + + # 2. Computer Name ohne $ (sAMAccountName ohne $) + computer_name = ad_item.get('Computer Name', '').upper() + if computer_name and computer_name != 'N/A' and computer_name.endswith('$'): + computer_name_clean = computer_name[:-1] # Entferne $ + potential_names.append(computer_name_clean) + + # 3. DNS Host Name + dns_host_name = ad_item.get('DNS Host Name', '').upper() + if dns_host_name and dns_host_name != 'N/A': + potential_names.append(dns_host_name) + + # 4. Display Name + display_name = ad_item.get('Display Name', '').upper() + if display_name and display_name != 'N/A': + potential_names.append(display_name) + + # Alle Namen zu diesem AD-Item mappen + for name in potential_names: + if name: + name_to_ad_item[name] = (ad_item, ad_id) + + # Jetzt das finale Mapping erstellen (jeder RMM-Hostname wird nur einmal gemappt) + for rmm_hostname in rmm_by_hostname.keys(): + # Prüfe direkte Matches über alle Namen + if rmm_hostname in name_to_ad_item: + ad_item, ad_id = name_to_ad_item[rmm_hostname] + if ad_id not in ad_items_processed: + ad_by_hostname[rmm_hostname] = ad_item + ad_items_processed.add(ad_id) + else: + # Fuzzy-Matching für gekürzte Namen + best_match = _find_best_ad_match(rmm_hostname, ad_data) + if best_match: + best_match_id = best_match.get('GUID', str(id(best_match))) + if best_match_id not in ad_items_processed: + ad_by_hostname[rmm_hostname] = best_match + ad_items_processed.add(best_match_id) + + # Sammle alle eindeutigen Hostnamen (RMM + ungematchte AD) + all_hostnames = set(rmm_by_hostname.keys()) + + # Füge AD-Items hinzu, die nicht gematcht wurden (nur in AD vorhanden) + for ad_item in ad_data: + ad_id = ad_item.get('GUID', str(id(ad_item))) + if ad_id not in ad_items_processed: + # Verwende den besten verfügbaren Namen für dieses AD-Item + best_name = _get_best_ad_name(ad_item) + if best_name and best_name not in all_hostnames: + ad_by_hostname[best_name] = ad_item + all_hostnames.add(best_name) + + # Funktion zur Bestimmung des Computer-Typs für Sortierung + def get_computer_type_priority(hostname): + rmm_item = rmm_by_hostname.get(hostname) + if rmm_item: + monitoring_type = rmm_item.get('monitoring_type', 'workstation') + # Server haben Priorität 0, Workstations haben Priorität 1 + return 0 if monitoring_type == 'server' else 1 + else: + # Wenn nur in AD vorhanden, als Workstation behandeln + return 1 + + # Sortiere: erst nach Server/Workstation, dann alphabetisch + sorted_hostnames = sorted(all_hostnames, key=lambda x: (get_computer_type_priority(x), x)) + + # Tabellendaten erstellen + table_rows = [] + stats = { + 'total_computers': len(all_hostnames), + 'in_both': 0, + 'only_in_rmm': 0, + 'only_in_ad': 0, + 'ad_disabled': 0 + } + + for hostname in sorted_hostnames: + rmm_item = rmm_by_hostname.get(hostname) + ad_item = ad_by_hostname.get(hostname) + + # Daten extrahieren + os_info = "N/A" + cpu_info = "N/A" + last_ad_login = "N/A" + rmm_last_seen = "N/A" + last_user = "N/A" + + if rmm_item: + os_info = rmm_item.get('operating_system', 'N/A') + cpu_model = rmm_item.get('cpu_model', []) + if isinstance(cpu_model, list) and cpu_model: + cpu_info = cpu_model[0] + elif isinstance(cpu_model, str): + cpu_info = cpu_model + + # Letzter Benutzer aus RMM + logged_username = rmm_item.get('logged_username', 'N/A') + if logged_username and logged_username != 'N/A': + last_user = logged_username + + # RMM zuletzt online formatieren + last_seen = rmm_item.get('last_seen', 'N/A') + if last_seen and last_seen != 'N/A': + try: + # ISO-Format zu deutschem Datum konvertieren + dt = datetime.datetime.fromisoformat(last_seen.replace('Z', '+00:00')) + rmm_last_seen = dt.strftime('%d.%m.%Y %H:%M') + except: + rmm_last_seen = last_seen + elif ad_item: + os_info = ad_item.get('Operating System', 'N/A') + + if ad_item: + # AD letzter Login formatieren + most_recent_logon = ad_item.get('Most Recent Logon', 'N/A') + if most_recent_logon and most_recent_logon != 'N/A' and most_recent_logon != 'Never logged in': + try: + # ISO-Format zu deutschem Datum konvertieren + if isinstance(most_recent_logon, str) and 'T' in most_recent_logon: + dt = datetime.datetime.fromisoformat(most_recent_logon.replace('Z', '+00:00')) + last_ad_login = dt.strftime('%d.%m.%Y %H:%M') + else: + last_ad_login = str(most_recent_logon) + except: + last_ad_login = str(most_recent_logon) + else: + last_ad_login = most_recent_logon if most_recent_logon else "N/A" + + # Status ermitteln + in_ad = bool(ad_item) + in_rmm = bool(rmm_item) + is_ad_disabled = ad_item and ad_item.get('Status') == 'Disabled' + + # Statistiken aktualisieren + if in_ad and in_rmm: + stats['in_both'] += 1 + elif in_rmm and not in_ad: + stats['only_in_rmm'] += 1 + elif in_ad and not in_rmm: + stats['only_in_ad'] += 1 + + if is_ad_disabled: + stats['ad_disabled'] += 1 + + # Computer-Typ für Anzeige bestimmen + computer_type = "Workstation" # Standard + type_icon = "💻" # Standard-Icon für Workstation + if rmm_item: + monitoring_type = rmm_item.get('monitoring_type', 'workstation') + if monitoring_type == 'server': + computer_type = "Server" + type_icon = "🖥️" # Server-Icon + + # Tabellen-Row erstellen + row = { + 'hostname': hostname, + 'computer_type': computer_type, + 'type_icon': type_icon, + 'os': os_info, + 'cpu': cpu_info, + 'in_ad': '✓' if in_ad else '✗', + 'in_rmm': '✓' if in_rmm else '✗', + 'last_user': last_user, + 'last_ad_login': last_ad_login, + 'rmm_last_seen': rmm_last_seen, + 'css_class': _get_row_css_class(in_ad, in_rmm, is_ad_disabled) + } + table_rows.append(row) + + # HTML-Tabelle und Statistiken generieren + html_output = _generate_comparison_html(table_rows, stats) + + # Ergebnis in output Feld speichern + documentation_doc.output = html_output + documentation_doc.save() + + return { + "success": True, + "message": f"Datenabgleich erfolgreich. {stats['total_computers']} Computer analysiert.", + "stats": stats, + "documentation": documentation_name + } + + except Exception as e: + frappe.log_error(f"Fehler beim Datenabgleich: {str(e)}", "compare_rmm_and_ad_data") + frappe.throw(f"Fehler beim Datenabgleich: {str(e)}") + + +def _get_row_css_class(in_ad, in_rmm, is_ad_disabled): + """Bestimmt CSS-Klasse für Tabellenzeile basierend auf Status""" + if in_ad and in_rmm and not is_ad_disabled: + return "table-success" # Grün - in beiden vorhanden und aktiv + elif in_ad and in_rmm and is_ad_disabled: + return "table-warning" # Gelb - in beiden aber AD deaktiviert + elif in_rmm and not in_ad: + return "table-info" # Blau - nur in RMM + elif in_ad and not in_rmm: + return "table-danger" # Rot - nur in AD + else: + return "" + + +def _generate_comparison_html(table_rows, stats): + """Generiert HTML-Tabelle und Statistiken für den Datenabgleich""" + + # Statistiken HTML + stats_html = f""" +
+
+
Statistiken
+
+
+
+
+
+
+

{stats['total_computers']}

+

Gesamt Computer

+
+
+
+
+
+
+

{stats['in_both']}

+

In beiden Systemen

+
+
+
+
+
+
+

{stats['only_in_rmm']}

+

Nur im RMM

+
+
+
+
+
+
+

{stats['only_in_ad']}

+

Nur im AD

+
+
+
+
+
+
+
+
+

{stats['ad_disabled']}

+

AD-Computer deaktiviert

+
+
+
+
+
+
+

{round((stats['in_both'] / stats['total_computers'] * 100) if stats['total_computers'] > 0 else 0, 1)}%

+

Übereinstimmung

+
+
+
+
+
+
+

{stats['only_in_rmm'] + stats['only_in_ad']}

+

Abweichungen

+
+
+
+
+
+
+ """ + + # Legende + legend_html = """ +
+
+
Legende
+
+
+
+
+ Zeilenfärbung:
+ Grün In beiden Systemen vorhanden und aktiv
+ Gelb In beiden Systemen, aber AD-Computer deaktiviert
+ Blau Nur im RMM vorhanden
+ Rot Nur im Active Directory vorhanden
+
+
+ Computer-Typen:
+ 🖥️ Server Server-Systeme
+ 💻 Workstation Arbeitsplatz-PCs

+ Sortierung: Server zuerst, dann Workstations +
+
+ Status-Icons:
+ ✓ Vorhanden
+ ✗ Nicht vorhanden

+ Benutzerinformationen:
+ Letzter Benutzer: Aus RMM-System (logged_username)
+ Zeitstempel im deutschen Format (DD.MM.YYYY HH:MM) +
+
+
+
+ """ + + # Tabelle HTML + table_html = """ +
+
+
Computer-Vergleich (RMM ↔ Active Directory)
+
+
+
+ + + + + + + + + + + + + + + + """ + + # Tabellenzeilen hinzufügen + for row in table_rows: + table_html += f""" + + + + + + + + + + + + """ + + table_html += """ + +
HostnameTypBetriebssystemCPUIn ADIn RMMLetzter BenutzerLetzter AD LoginRMM zuletzt online
{row['hostname']}{row['type_icon']} {row['computer_type']}{row['os']}{row['cpu']}{row['in_ad']}{row['in_rmm']}{row['last_user']}{row['last_ad_login']}{row['rmm_last_seen']}
+
+
+
+ """ + + # Gesamtes HTML zusammenfügen + return stats_html + legend_html + table_html + + +@frappe.whitelist() +def debug_cpu_compatibility(documentation_name, test_cpu_string=None): + """Debuggt CPU-Kompatibilität für eine einzelne CPU""" + try: + if not test_cpu_string: + # Verwende die erste nicht-Windows-11 CPU aus RMM als Test + documentation_doc = frappe.get_doc("MSP Documentation", documentation_name) + if not documentation_doc.rmm_data_json: + frappe.throw("Keine RMM-Daten vorhanden. Bitte zuerst RMM-Daten abrufen.") + + rmm_data = json.loads(documentation_doc.rmm_data_json) + + # Finde erste Test-CPU + for agent in rmm_data: + os = agent.get('operating_system', '').lower() + if 'windows' in os and 'windows 11' not in os: + test_cpu_string = agent.get('cpu_model', ['N/A'])[0] if agent.get('cpu_model') else 'N/A' + break + + if not test_cpu_string or test_cpu_string == 'N/A': + frappe.throw("Keine Test-CPU gefunden") + + # Lade CPU-Listen mit Debug-Info + debug_info = {} + supported_cpus = _load_win11_cpu_lists(debug_info) + + # CPU-Kompatibilität prüfen + result = _check_cpu_compatibility(test_cpu_string, supported_cpus, debug_info) + + # Begrenze Vergleiche für bessere Lesbarkeit + if len(debug_info.get('comparisons', [])) > 20: + debug_info['comparisons'] = debug_info['comparisons'][:20] + debug_info['truncated'] = True + + return { + 'success': True, + 'debug_info': debug_info, + 'result': result, + 'test_cpu': test_cpu_string + } + + except Exception as e: + frappe.log_error(f"Fehler bei CPU-Debug: {str(e)}", "debug_cpu_compatibility") + frappe.throw(f"Fehler bei CPU-Debug: {str(e)}") + + +@frappe.whitelist() +def export_tables_to_excel(documentation_name): + """Exportiert RMM/AD Abgleich, Windows 11 Check und AD Benutzer-Daten als Excel""" + try: + from io import BytesIO + import base64 + + # Prüfe auf pandas, verwende alternativ openpyxl direkt + use_pandas = True + try: + import pandas as pd + except ImportError: + use_pandas = False + try: + from openpyxl import Workbook + except ImportError: + frappe.throw("Weder pandas noch openpyxl sind installiert. Bitte installieren Sie eine der Bibliotheken für Excel-Export.") + + # MSP Documentation Dokument laden + documentation_doc = frappe.get_doc("MSP Documentation", documentation_name) + + # RMM, AD Computer und AD Benutzer Daten laden + rmm_data = [] + ad_data = [] + ad_user_data = [] + + if documentation_doc.rmm_data_json: + try: + rmm_data = json.loads(documentation_doc.rmm_data_json) + except json.JSONDecodeError: + frappe.throw("RMM Data JSON ist nicht gültig") + + if documentation_doc.ad_computer_data_json: + try: + ad_data = json.loads(documentation_doc.ad_computer_data_json) + except json.JSONDecodeError: + frappe.throw("AD Computer Data JSON ist nicht gültig") + + if documentation_doc.ad_user_data_json: + try: + ad_user_data = json.loads(documentation_doc.ad_user_data_json) + except json.JSONDecodeError: + frappe.throw("AD User Data JSON ist nicht gültig") + + if not rmm_data and not ad_data and not ad_user_data: + frappe.throw("Keine RMM-, AD Computer- oder AD Benutzer-Daten vorhanden") + + # Excel-Datei erstellen + excel_buffer = BytesIO() + + if use_pandas: + # Pandas-basierte Erstellung + with pd.ExcelWriter(excel_buffer, engine='openpyxl') as writer: + + # 1. RMM ↔ AD Abgleich Tabelle + if rmm_data and ad_data: + comparison_data = _generate_comparison_excel_data(rmm_data, ad_data) + if comparison_data: + df_comparison = pd.DataFrame(comparison_data) + df_comparison.to_excel(writer, sheet_name='RMM AD Abgleich', index=False) + _format_pandas_worksheet(writer, 'RMM AD Abgleich', comparison_data) + + # 2. Windows 11 Kompatibilitätstabelle + if rmm_data: + win11_data = _generate_win11_excel_data(rmm_data) + if win11_data: + df_win11 = pd.DataFrame(win11_data) + df_win11.to_excel(writer, sheet_name='Windows 11 Check', index=False) + _format_pandas_worksheet(writer, 'Windows 11 Check', win11_data) + + # 3. RMM Rohdaten (optional) + if rmm_data: + rmm_export_data = _generate_rmm_excel_data(rmm_data) + if rmm_export_data: + df_rmm = pd.DataFrame(rmm_export_data) + df_rmm.to_excel(writer, sheet_name='RMM Rohdaten', index=False) + _format_pandas_worksheet(writer, 'RMM Rohdaten', rmm_export_data) + + # 4. AD Rohdaten (optional) + if ad_data: + ad_export_data = _generate_ad_excel_data(ad_data) + if ad_export_data: + df_ad = pd.DataFrame(ad_export_data) + df_ad.to_excel(writer, sheet_name='AD Rohdaten', index=False) + _format_pandas_worksheet(writer, 'AD Rohdaten', ad_export_data) + + # 5. AD Benutzer-Daten (optional) + if ad_user_data: + ad_user_export_data = _generate_ad_user_excel_data(ad_user_data) + if ad_user_export_data: + df_ad_users = pd.DataFrame(ad_user_export_data) + df_ad_users.to_excel(writer, sheet_name='AD Benutzer', index=False) + _format_pandas_worksheet(writer, 'AD Benutzer', ad_user_export_data) + else: + # Openpyxl-basierte Alternative + wb = Workbook() + # Entferne standard Worksheet + wb.remove(wb.active) + + # 1. RMM ↔ AD Abgleich Tabelle + if rmm_data and ad_data: + comparison_data = _generate_comparison_excel_data(rmm_data, ad_data) + if comparison_data: + _add_worksheet_with_data(wb, 'RMM AD Abgleich', comparison_data) + + # 2. Windows 11 Kompatibilitätstabelle + if rmm_data: + win11_data = _generate_win11_excel_data(rmm_data) + if win11_data: + _add_worksheet_with_data(wb, 'Windows 11 Check', win11_data) + + # 3. RMM Rohdaten (optional) + if rmm_data: + rmm_export_data = _generate_rmm_excel_data(rmm_data) + if rmm_export_data: + _add_worksheet_with_data(wb, 'RMM Rohdaten', rmm_export_data) + + # 4. AD Rohdaten (optional) + if ad_data: + ad_export_data = _generate_ad_excel_data(ad_data) + if ad_export_data: + _add_worksheet_with_data(wb, 'AD Rohdaten', ad_export_data) + + # 5. AD Benutzer-Daten (optional) + if ad_user_data: + ad_user_export_data = _generate_ad_user_excel_data(ad_user_data) + if ad_user_export_data: + _add_worksheet_with_data(wb, 'AD Benutzer', ad_user_export_data) + + # Workbook in Buffer speichern + wb.save(excel_buffer) + + # Excel-Datei als Base64 kodieren + excel_buffer.seek(0) + excel_content = excel_buffer.getvalue() + excel_base64 = base64.b64encode(excel_content).decode('utf-8') + + # Dateiname generieren + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"MSP_Export_{documentation_name}_{timestamp}.xlsx" + + return { + 'success': True, + 'filename': filename, + 'content': excel_base64, + 'message': f'Excel-Export erfolgreich erstellt: {filename}', + 'method': 'pandas' if use_pandas else 'openpyxl' + } + + except Exception as e: + frappe.log_error(f"Fehler beim Excel-Export: {str(e)}", "export_tables_to_excel") + frappe.throw(f"Fehler beim Excel-Export: {str(e)}") + + +@frappe.whitelist() +def check_windows11_compatibility(documentation_name): + """ + Prüft Windows 11 CPU-Kompatibilität für alle Systeme, die noch nicht Windows 11 haben. + + Args: + documentation_name (str): Name der MSP Documentation + + Returns: + dict: Erfolgsmeldung mit Zusammenfassung + """ + try: + # MSP Documentation Dokument laden + documentation_doc = frappe.get_doc("MSP Documentation", documentation_name) + + # RMM-Daten laden + rmm_data = [] + if documentation_doc.rmm_data_json: + try: + rmm_data = json.loads(documentation_doc.rmm_data_json) + except json.JSONDecodeError: + frappe.throw("RMM Data JSON ist nicht gültig") + else: + frappe.throw("Keine RMM-Daten vorhanden. Bitte zuerst RMM-Daten speichern.") + + # CPU-Listen laden + supported_cpus = _load_win11_cpu_lists() + + # Systeme analysieren + analysis_results = [] + stats = { + 'total_non_win11': 0, + 'compatible_cpus': 0, + 'incompatible_cpus': 0, + 'unknown_cpus': 0, + 'servers_excluded': 0 + } + + for rmm_item in rmm_data: + # Nur Client-Systeme (Workstations) berücksichtigen + monitoring_type = rmm_item.get('monitoring_type', 'workstation') + if monitoring_type == 'server': + stats['servers_excluded'] += 1 + continue + + hostname = rmm_item.get('hostname', 'Unknown') + os_info = rmm_item.get('operating_system', 'N/A') + cpu_model = rmm_item.get('cpu_model', []) + + # CPU-String extrahieren + cpu_string = "N/A" + if isinstance(cpu_model, list) and cpu_model: + cpu_string = cpu_model[0] + elif isinstance(cpu_model, str): + cpu_string = cpu_model + + # Prüfen ob System bereits Windows 11 hat + is_windows11 = 'Windows 11' in os_info + + # Nur Systeme ohne Windows 11 analysieren + if not is_windows11: + stats['total_non_win11'] += 1 + + # CPU-Kompatibilität prüfen + compatibility = _check_cpu_compatibility(cpu_string, supported_cpus) + + if compatibility['compatible']: + stats['compatible_cpus'] += 1 + elif compatibility['status'] == 'unknown': + stats['unknown_cpus'] += 1 + else: + stats['incompatible_cpus'] += 1 + + # Letzter Benutzer + last_user = rmm_item.get('logged_username', 'N/A') + + # Letztes Online-Datum + last_seen = rmm_item.get('last_seen', 'N/A') + rmm_last_seen = "N/A" + if last_seen and last_seen != 'N/A': + try: + dt = datetime.datetime.fromisoformat(last_seen.replace('Z', '+00:00')) + rmm_last_seen = dt.strftime('%d.%m.%Y %H:%M') + except: + rmm_last_seen = last_seen + + result = { + 'hostname': hostname, + 'os': os_info, + 'cpu': cpu_string, + 'cpu_vendor': compatibility['vendor'], + 'compatible': compatibility['compatible'], + 'compatibility_status': compatibility['status'], + 'last_user': last_user, + 'last_seen': rmm_last_seen, + 'css_class': _get_compatibility_css_class(compatibility['compatible'], compatibility['status']) + } + + analysis_results.append(result) + + # Nach Kompatibilität und dann alphabetisch sortieren + analysis_results.sort(key=lambda x: ( + 0 if x['compatible'] else 1, # Kompatible zuerst + x['hostname'].upper() + )) + + # HTML-Output generieren + html_output = _generate_win11_compatibility_html(analysis_results, stats) + + # Ergebnis in output Feld anhängen (oder ersetzen wenn schon vorhanden) + existing_output = documentation_doc.output or "" + separator = "\n\n
\n\n" + + # Entferne bereits vorhandenen Windows 11 Check (falls vorhanden) + if "Windows 11 CPU-Kompatibilitätsprüfung" in existing_output: + # Teile bei HR-Tags und filtere Windows 11 Abschnitt heraus + parts = existing_output.split(" max_length: + max_length = len(str(cell.value)) + except: + pass + adjusted_width = min(max_length + 2, 50) + ws.column_dimensions[column_letter].width = adjusted_width + return + + # Spezifische Breiten setzen + for col_num, header in enumerate(headers, 1): + column_letter = ws.cell(row=1, column=col_num).column_letter + width = width_config.get(header, 20) # Standard 20 wenn nicht definiert + ws.column_dimensions[column_letter].width = width + + except Exception as e: + frappe.log_error(f"Fehler beim Setzen der Spaltenbreiten: {str(e)}", "_set_column_widths") + + +def _apply_zebra_stripes(ws, data_rows): + """Fügt Zebrastreifen für bessere Lesbarkeit hinzu (nur bei Spalten ohne Farbformatierung)""" + try: + from openpyxl.styles import PatternFill + + # Leichte graue Füllung für ungerade Zeilen (ab Zeile 3, da 1=Header, 2=erste Datenzeile) + zebra_fill = PatternFill(start_color="F8F9FA", end_color="F8F9FA", fill_type="solid") + + for row_num in range(3, data_rows + 2, 2): # Jede zweite Zeile + for cell in ws[row_num]: + # Nur wenn Zelle noch keine spezielle Hintergrundfarbe hat + current_fill = cell.fill.start_color.index if cell.fill else '00000000' + if current_fill in ['00000000', 'FFFFFFFF']: # Transparente oder weiße Füllung + cell.fill = zebra_fill + + except Exception: + pass # Ignoriere Formatierungsfehler + + +def _format_pandas_worksheet(writer, sheet_name, data): + """Formatiert Pandas-generierte Worksheets""" + try: + from openpyxl.styles import PatternFill, Font, Alignment + + # Worksheet aus Writer abrufen + ws = writer.sheets[sheet_name] + headers = list(data[0].keys()) if data else [] + + # Header-Style + header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid") + header_font = Font(color="FFFFFF", bold=True) + center_alignment = Alignment(horizontal="center", vertical="center") + + # Header formatieren + for col_num, header in enumerate(headers, 1): + cell = ws.cell(row=1, column=col_num) + cell.fill = header_fill + cell.font = header_font + cell.alignment = center_alignment + + # Spezifische Spaltenbreiten setzen + _set_column_widths(ws, sheet_name, headers) + + # Deutsche Datum/Zeit-Formatierung für relevante Spalten + datetime_columns = [ + 'RMM zuletzt online', 'Letzter AD Login', 'Zuletzt online', 'Letzter Login', + 'Passwort gesetzt', 'Erstellt', 'Geändert', 'Neuester Login', + 'lastLogon (DC-spezifisch)', 'lastLogonTimestamp (repliziert)' + ] + + for col_num, header in enumerate(headers, 1): + if header in datetime_columns: + # Deutsche Datum/Zeit-Formatierung: TT.MM.JJJJ HH:MM + for row_num in range(2, len(data) + 2): + cell = ws.cell(row=row_num, column=col_num) + if cell.value: # Nur formatieren wenn Wert vorhanden + cell.number_format = 'DD.MM.YYYY HH:MM' + + # Erst Zebrastreifen anwenden + _apply_zebra_stripes(ws, len(data)) + + # Dann spezielle Formatierung (überschreibt Zebrastreifen) + for row_num, row_data in enumerate(data, 2): + for col_num, header in enumerate(headers, 1): + cell = ws.cell(row=row_num, column=col_num) + + if sheet_name == 'RMM AD Abgleich': + _apply_comparison_formatting(cell, row_data, header) + elif sheet_name == 'Windows 11 Check': + _apply_win11_formatting(cell, row_data, header) + + except Exception as e: + frappe.log_error(f"Fehler beim Formatieren von Pandas Worksheet {sheet_name}: {str(e)}", "_format_pandas_worksheet") + + +def _check_cpu_compatibility(cpu_string, supported_cpus, debug_info=None): + """Prüft ob eine CPU Windows 11 kompatibel ist - ultra-einfache Suche""" + if not cpu_string or cpu_string == 'N/A': + return {'compatible': False, 'status': 'unknown', 'vendor': 'Unknown'} + + cpu_upper = cpu_string.upper() + + if debug_info is not None: + debug_info['system_cpu'] = cpu_string + debug_info['system_cpu_upper'] = cpu_upper + debug_info['comparisons'] = [] + + # Vendor erkennen + vendor = 'Unknown' + if 'AMD' in cpu_upper or 'RYZEN' in cpu_upper or 'ATHLON' in cpu_upper or 'EPYC' in cpu_upper: + vendor = 'AMD' + search_set = supported_cpus['amd'] + elif 'INTEL' in cpu_upper or 'CORE' in cpu_upper or 'XEON' in cpu_upper or 'CELERON' in cpu_upper or 'PENTIUM' in cpu_upper: + vendor = 'Intel' + search_set = supported_cpus['intel'] + else: + return {'compatible': False, 'status': 'unknown', 'vendor': vendor} + + if debug_info is not None: + debug_info['vendor'] = vendor + debug_info['search_set_size'] = len(search_set) + + # Ultra-einfache Suche: Prüfe für jede unterstützte CPU + match_found = False + for supported_cpu in search_set: + match_result = _simple_cpu_match(cpu_upper, supported_cpu.upper(), debug_info) + if match_result: + match_found = True + if debug_info is not None: + debug_info['match_found'] = True + debug_info['matching_cpu'] = supported_cpu + return {'compatible': True, 'status': 'compatible', 'vendor': vendor} + + if debug_info is not None: + debug_info['match_found'] = False + + return {'compatible': False, 'status': 'incompatible', 'vendor': vendor} + + +def _clean_cpu_string(cpu_string): + """Bereinigt CPU-String für bessere Kompatibilitätsprüfung""" + # Entferne häufige Zusätze die nicht in den Listen stehen + cleaned = cpu_string + + # Entferne Markenzeichen + cleaned = re.sub(r'\(R\)', '', cleaned) + cleaned = re.sub(r'\(TM\)', '', cleaned) + cleaned = re.sub(r'®', '', cleaned) + cleaned = re.sub(r'™', '', cleaned) + + # Entferne Geschwindigkeitsangaben + cleaned = re.sub(r'\s*@\s*[\d\.]+\s*GHz', '', cleaned) + cleaned = re.sub(r'\s*\d+\.\d+\s*GHz', '', cleaned) + + # Entferne Core/Thread Informationen + cleaned = re.sub(r'\s*,\s*\d+C/\d+T', '', cleaned) + cleaned = re.sub(r'\s*\d+C/\d+T', '', cleaned) + + # Entferne GPU-Zusätze bei AMD + cleaned = re.sub(r'\s+with\s+Radeon\s+Graphics.*', '', cleaned, flags=re.IGNORECASE) + cleaned = re.sub(r'\s+with\s+.*Graphics.*', '', cleaned, flags=re.IGNORECASE) + + # Entferne Zusätze wie "CPU", "Processor" + cleaned = re.sub(r'\s+(CPU|Processor)\s*', ' ', cleaned) + + # Entferne mehrfache Leerzeichen und führende/nachfolgende Leerzeichen + cleaned = re.sub(r'\s+', ' ', cleaned).strip() + + return cleaned + + +def _simple_cpu_match(system_cpu_upper, supported_cpu_upper, debug_info=None): + """Ultra-einfacher CPU-Match ohne komplexe Logik""" + + comparison = { + 'supported_cpu': supported_cpu_upper, + 'match_type': '', + 'extracted_part': '', + 'match_result': False, + 'details': '' + } + + # Intel CPUs: Extrahiere iX-XXXX aus "CORE(TM) iX-XXXX" + if 'CORE(TM)' in supported_cpu_upper: + comparison['match_type'] = 'Intel Core' + # Finde iX-XXXX Pattern + parts = supported_cpu_upper.split() + for part in parts: + if part.startswith('I') and '-' in part and len(part) >= 6: # i3-8100 etc. + comparison['extracted_part'] = part + if part in system_cpu_upper: + comparison['match_result'] = True + comparison['details'] = f"'{part}' gefunden in System-CPU" + if debug_info is not None: + debug_info['comparisons'].append(comparison) + return True + else: + comparison['details'] = f"'{part}' NICHT gefunden in System-CPU" + + if not comparison['extracted_part']: + comparison['details'] = "Kein iX-XXXX Pattern gefunden" + + if debug_info is not None: + debug_info['comparisons'].append(comparison) + return False + + # AMD Ryzen CPUs: Direkter Match + elif 'RYZEN' in supported_cpu_upper: + comparison['match_type'] = 'AMD Ryzen' + + # Für "Ryzen 5 PRO 5650GE" suche nach "RYZEN 5 PRO 5650GE" + if supported_cpu_upper in system_cpu_upper: + comparison['match_result'] = True + comparison['details'] = f"Direkter Match: '{supported_cpu_upper}' gefunden" + if debug_info is not None: + debug_info['comparisons'].append(comparison) + return True + + # Auch Fallback ohne "with Radeon Graphics" etc. + # Entferne common suffixes from system CPU for comparison + system_cleaned = system_cpu_upper + system_cleaned = system_cleaned.replace('WITH RADEON GRAPHICS', '') + system_cleaned = system_cleaned.replace('WITH RADEON VEGA MOBILE GFX', '') + system_cleaned = system_cleaned.replace('WITH RADEON', '') + system_cleaned = ' '.join(system_cleaned.split()) # normalize spaces + + if supported_cpu_upper in system_cleaned: + comparison['match_result'] = True + comparison['details'] = f"Bereinigter Match: '{supported_cpu_upper}' in '{system_cleaned}'" + if debug_info is not None: + debug_info['comparisons'].append(comparison) + return True + + comparison['details'] = f"Kein Match: '{supported_cpu_upper}' weder in Original noch in bereinigter Version" + if debug_info is not None: + debug_info['comparisons'].append(comparison) + return False + + # Andere CPUs (Athlon, EPYC, Xeon, Celeron, Pentium): Direkter Match + else: + comparison['match_type'] = 'Andere CPU' + match_result = supported_cpu_upper in system_cpu_upper + comparison['match_result'] = match_result + if match_result: + comparison['details'] = f"Direkter Match: '{supported_cpu_upper}' gefunden" + else: + comparison['details'] = f"Kein Match: '{supported_cpu_upper}' nicht gefunden" + + if debug_info is not None: + debug_info['comparisons'].append(comparison) + return match_result + + +def _extract_simple_core_model(supported_cpu): + """Extrahiert einfaches Kernmodell für Substring-Suche - sehr vereinfacht""" + if not supported_cpu: + return None + + cpu_upper = supported_cpu.upper().strip() + + # Intel CPUs: "Core(TM) i3-8100" → "I3-8100" + if 'CORE(TM)' in cpu_upper: + # Finde i3-XXXX, i5-XXXX, i7-XXXX, i9-XXXX + parts = cpu_upper.split() + for part in parts: + if part.startswith('I') and '-' in part: + return part + + # Intel andere: Xeon, Celeron, Pentium - verwende den ganzen String ohne Core(TM) + if cpu_upper.startswith('CORE(TM)'): + return cpu_upper.replace('CORE(TM)', '').strip() + + # AMD CPUs: Suche nach "Ryzen X XXXX" oder "Ryzen X PRO XXXX" + if 'RYZEN' in cpu_upper: + # Finde Ryzen Pattern + if 'PRO' in cpu_upper: + # "Ryzen 5 PRO 5650GE" → "RYZEN 5 PRO 5650GE" + return cpu_upper + else: + # "Ryzen 7 5800H" → "RYZEN 7 5800H" + return cpu_upper + + # Andere AMD: Athlon, EPYC + if 'ATHLON' in cpu_upper or 'EPYC' in cpu_upper: + return cpu_upper + + # Fallback - ganzer String + return cpu_upper + + +def _create_cpu_search_variants(cpu_string): + """Erstellt verschiedene Suchvarianten eines CPU-Strings""" + variants = [] + + # Original uppercase + variants.append(cpu_string.upper()) + + # Bereinigt (entfernt (R), (TM), Geschwindigkeit, etc.) + cleaned = _clean_cpu_string(cpu_string) + variants.append(cleaned.upper()) + + # Weitere Varianten mit verschiedenen Markenzeichen-Behandlungen + # Version mit (TM) aber ohne (R) + temp = cpu_string + temp = re.sub(r'\(R\)', '', temp) + temp = re.sub(r'®', '', temp) + temp = _clean_cpu_string_keep_tm(temp) + variants.append(temp.upper()) + + # Version ohne Intel/AMD Präfix für bessere Suche + no_prefix = re.sub(r'^(INTEL|AMD)\s+', '', cleaned.upper()) + variants.append(no_prefix) + + # Kernmodell extrahieren + core_model = _extract_cpu_core_model(cpu_string) + if core_model: + variants.append(core_model.upper()) + + # Entferne leere und doppelte Einträge + variants = list(set([v.strip() for v in variants if v and v.strip()])) + + return variants + + +def _clean_cpu_string_keep_tm(cpu_string): + """Bereinigt CPU-String aber behält (TM) bei""" + cleaned = cpu_string + + # Entferne Geschwindigkeitsangaben + cleaned = re.sub(r'\s*@\s*[\d\.]+\s*GHz', '', cleaned) + cleaned = re.sub(r'\s*\d+\.\d+\s*GHz', '', cleaned) + + # Entferne Core/Thread Informationen + cleaned = re.sub(r'\s*,\s*\d+C/\d+T', '', cleaned) + cleaned = re.sub(r'\s*\d+C/\d+T', '', cleaned) + + # Entferne GPU-Zusätze bei AMD + cleaned = re.sub(r'\s+with\s+Radeon\s+Graphics.*', '', cleaned, flags=re.IGNORECASE) + cleaned = re.sub(r'\s+with\s+.*Graphics.*', '', cleaned, flags=re.IGNORECASE) + + # Entferne Zusätze wie "CPU", "Processor" + cleaned = re.sub(r'\s+(CPU|Processor)\s*', ' ', cleaned) + + # Entferne mehrfache Leerzeichen und führende/nachfolgende Leerzeichen + cleaned = re.sub(r'\s+', ' ', cleaned).strip() + + return cleaned + + +def _cpu_strings_match(search_variant, supported_cpu): + """Prüft ob zwei CPU-Strings übereinstimmen""" + if not search_variant or not supported_cpu: + return False + + search_upper = search_variant.upper().strip() + supported_upper = supported_cpu.upper().strip() + + # Exakte Übereinstimmung + if search_upper == supported_upper: + return True + + # Teilstring-Suche in beide Richtungen + if supported_upper in search_upper or search_upper in supported_upper: + return True + + # Spezielle Behandlung für Core-CPUs + # Entferne "INTEL" und "AMD" Präfixe für Vergleich + search_no_vendor = re.sub(r'^(INTEL|AMD)\s+', '', search_upper) + supported_no_vendor = re.sub(r'^(INTEL|AMD)\s+', '', supported_upper) + + if search_no_vendor in supported_no_vendor or supported_no_vendor in search_no_vendor: + return True + + # Kernmodell-Vergleich + search_core = _extract_cpu_core_model(search_variant) + supported_core = _extract_cpu_core_model(supported_cpu) + + if search_core and supported_core: + # Normalisiere beide Kernmodelle für Vergleich + search_core_clean = re.sub(r'\(TM\)', '', search_core.upper()).strip() + supported_core_clean = re.sub(r'\(TM\)', '', supported_core.upper()).strip() + + if search_core_clean == supported_core_clean: + return True + + # Auch mit (TM) vergleichen + if search_core.upper() == supported_core.upper(): + return True + + return False + + +def _normalize_cpu_string(cpu_string): + """Normalisiert CPU-String für erweiterte Kompatibilitätsprüfung""" + normalized = _clean_cpu_string(cpu_string) + + # Zusätzliche Normalisierungen + # Entferne "Intel" und "AMD" Präfixe für bessere Suche + normalized = re.sub(r'^(INTEL|AMD)\s+', '', normalized.upper()) + + # Normalisiere Core-Bezeichnungen + normalized = re.sub(r'CORE\s*\(TM\)\s*', 'CORE(TM) ', normalized) + + # Entferne doppelte Leerzeichen + normalized = re.sub(r'\s+', ' ', normalized).strip() + + return normalized + + +def _extract_cpu_core_model(cpu_string): + """Extrahiert das Kernmodell einer CPU für präzise Suche""" + if not cpu_string: + return None + + cpu_upper = cpu_string.upper() + + # Intel CPU-Modelle extrahieren + intel_patterns = [ + r'I\d+-\d+[A-Z]*', # i3-8100, i7-9700K, etc. + r'XEON.*?[A-Z]-\d+[A-Z]*', # Xeon Gold 6134, etc. + r'CELERON.*?[A-Z]?\d+[A-Z]*', # Celeron G4900, etc. + r'PENTIUM.*?[A-Z]?\d+[A-Z]*' # Pentium Gold G5400, etc. + ] + + for pattern in intel_patterns: + match = re.search(pattern, cpu_upper) + if match: + return match.group() + + # AMD CPU-Modelle extrahieren + amd_patterns = [ + r'RYZEN\s+\d+\s+\d+[A-Z]*', # Ryzen 7 5800H, etc. + r'RYZEN\s+\d+\s+PRO\s+\d+[A-Z]*', # Ryzen 7 PRO 5850U, etc. + r'ATHLON.*?\d+[A-Z]*', # Athlon Gold 3150G, etc. + r'EPYC\s+\d+[A-Z]*' # EPYC 7502, etc. + ] + + for pattern in amd_patterns: + match = re.search(pattern, cpu_upper) + if match: + return match.group() + + return None + + +def _get_compatibility_css_class(compatible, status): + """Bestimmt CSS-Klasse für Kompatibilitätszeile""" + if compatible: + return "table-success" # Grün - kompatibel + elif status == 'unknown': + return "table-warning" # Gelb - unbekannt + else: + return "table-danger" # Rot - nicht kompatibel + + +def _generate_win11_compatibility_html(results, stats): + """Generiert HTML für Windows 11 Kompatibilitätsprüfung""" + + # Header + header_html = """ +
+
+

Windows 11 CPU-Kompatibilitätsprüfung

+ Systeme mit älteren Windows-Versionen und deren Windows 11 Kompatibilität +
+
+ """ + + # Statistiken + stats_html = f""" +
+
+
Kompatibilitäts-Statistiken
+
+
+
+
+
+
+

{stats['total_non_win11']}

+

Systeme ohne Win11

+
+
+
+
+
+
+

{stats['compatible_cpus']}

+

Kompatible CPUs

+
+
+
+
+
+
+

{stats['incompatible_cpus']}

+

Inkompatible CPUs

+
+
+
+
+
+
+

{stats['unknown_cpus']}

+

Unbekannte CPUs

+
+
+
+
+
+
+
+
+

{round((stats['compatible_cpus'] / stats['total_non_win11'] * 100) if stats['total_non_win11'] > 0 else 0, 1)}%

+

Upgrade-bereit

+
+
+
+
+
+
+

{stats['servers_excluded']}

+

Server ausgeschlossen

+
+
+
+
+
+
+ """ + + # Legende + legend_html = """ +
+
+
Legende
+
+
+
+
+ Kompatibilität:
+ Grün CPU ist Windows 11 kompatibel
+ Rot CPU ist NICHT Windows 11 kompatibel
+ Gelb CPU-Kompatibilität unbekannt
+
+
+ Hinweise:
+ • Nur Workstations werden geprüft
+ • Server sind ausgeschlossen
+ • Systeme mit Windows 11 werden nicht angezeigt
+
+
+ Empfehlung:
+ • Grüne Systeme können auf Windows 11 aktualisiert werden
+ • Rote Systeme benötigen Hardware-Upgrade
+ • Gelbe Systeme einzeln prüfen
+
+
+
+
+ """ + + # Tabelle + table_html = """ +
+
+
Windows 11 Kompatibilitäts-Details
+
+
+
+ + + + + + + + + + + + + + """ + + # Tabellenzeilen + for result in results: + compatibility_badge = "" + if result['compatible']: + compatibility_badge = '✓ Kompatibel' + elif result['compatibility_status'] == 'unknown': + compatibility_badge = '? Unbekannt' + else: + compatibility_badge = '✗ Nicht kompatibel' + + vendor_badge = f'{result["cpu_vendor"]}' + + table_html += f""" + + + + + + + + + + """ + + table_html += """ + +
HostnameAktuelles OSCPUHerstellerWin11 KompatibelLetzter BenutzerZuletzt online
{result['hostname']}{result['os']}{result['cpu']}{vendor_badge}{compatibility_badge}{result['last_user']}{result['last_seen']}
+
+
+
+ """ + + # Zusammenfügen + return header_html + stats_html + legend_html + table_html + + +def _get_best_ad_name(ad_item): + """ + Bestimmt den besten verfügbaren Namen für ein AD-Item für die Anzeige. + Priorität: DNS Host Name > Common Name > Computer Name ohne $ > Display Name + """ + # 1. DNS Host Name (meist vollständig) + dns_host_name = ad_item.get('DNS Host Name', '') + if dns_host_name and dns_host_name != 'N/A': + return dns_host_name.upper() + + # 2. Common Name + common_name = ad_item.get('Common Name', '') + if common_name and common_name != 'N/A': + return common_name.upper() + + # 3. Computer Name ohne $ + computer_name = ad_item.get('Computer Name', '') + if computer_name and computer_name != 'N/A' and computer_name.endswith('$'): + return computer_name[:-1].upper() + + # 4. Display Name + display_name = ad_item.get('Display Name', '') + if display_name and display_name != 'N/A': + return display_name.upper() + + return None + + +def _find_best_ad_match(rmm_hostname, ad_data): + """ + Findet den besten AD-Match für einen RMM-Hostnamen mit Fuzzy-Matching. + Behandelt gekürzte Computer-Namen in Active Directory. + """ + if not rmm_hostname or not ad_data: + return None + + rmm_hostname_upper = rmm_hostname.upper() + best_match = None + best_score = 0 + + for ad_item in ad_data: + # Alle verfügbaren Namen aus AD-Item sammeln + ad_names = [] + + # Common Name + common_name = ad_item.get('Common Name', '') + if common_name and common_name != 'N/A': + ad_names.append(common_name.upper()) + + # Computer Name ohne $ + computer_name = ad_item.get('Computer Name', '') + if computer_name and computer_name != 'N/A' and computer_name.endswith('$'): + ad_names.append(computer_name[:-1].upper()) + + # DNS Host Name + dns_host_name = ad_item.get('DNS Host Name', '') + if dns_host_name and dns_host_name != 'N/A': + ad_names.append(dns_host_name.upper()) + + # Display Name + display_name = ad_item.get('Display Name', '') + if display_name and display_name != 'N/A': + ad_names.append(display_name.upper()) + + # Prüfe jeden AD-Namen gegen RMM-Hostname + for ad_name in ad_names: + if not ad_name: + continue + + # 1. Exakte Übereinstimmung (höchste Priorität) + if ad_name == rmm_hostname_upper: + return ad_item + + # 2. RMM-Hostname beginnt mit AD-Name (gekürzte AD-Namen) + if rmm_hostname_upper.startswith(ad_name) and len(ad_name) >= 8: + score = len(ad_name) / len(rmm_hostname_upper) * 100 + if score > best_score: + best_score = score + best_match = ad_item + + # 3. AD-Name beginnt mit RMM-Hostname (seltener Fall) + elif ad_name.startswith(rmm_hostname_upper) and len(rmm_hostname_upper) >= 8: + score = len(rmm_hostname_upper) / len(ad_name) * 90 # Etwas niedrigere Priorität + if score > best_score: + best_score = score + best_match = ad_item + + # 4. Teilweise Übereinstimmung (niedrigste Priorität) + elif len(ad_name) >= 6 and len(rmm_hostname_upper) >= 6: + # Gemeinsame Zeichen zählen + common_chars = sum(1 for a, b in zip(ad_name, rmm_hostname_upper) if a == b) + if common_chars >= min(6, min(len(ad_name), len(rmm_hostname_upper)) * 0.8): + score = (common_chars / max(len(ad_name), len(rmm_hostname_upper))) * 70 + if score > best_score and score >= 50: # Mindest-Score für partielle Matches + best_score = score + best_match = ad_item + + # Nur zurückgeben wenn Score hoch genug ist + if best_score >= 60: + return best_match + + return None + + +def _convert_filetime(filetime): + """Konvertiert Windows File Time zu Python datetime""" + if filetime and isinstance(filetime, int) and filetime != 0: + try: + return datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=filetime / 10) + except (ValueError, OverflowError): + return None + elif filetime and isinstance(filetime, datetime.datetime): + return filetime + else: + return None + """ {'agent_id': 'mXXJYhUHwrMPcAAuvsmGMFhcVsjWVMQqHKaVCfBN', diff --git a/msp/win11-amd-cpus.txt b/msp/win11-amd-cpus.txt new file mode 100644 index 0000000..b58765b --- /dev/null +++ b/msp/win11-amd-cpus.txt @@ -0,0 +1,305 @@ +2.6.1.0 +3015e +3020e +Athlon 3000G +Athlon 300GE +Athlon 300U +Athlon 320GE +Athlon 7120e +Athlon 7120U +Athlon 7220e +Athlon 7220U +Athlon Gold 3150C +Athlon Gold 3150G +Athlon Gold 3150GE +Athlon Gold 3150U +Athlon Silver 3050C +Athlon Silver 3050e +Athlon Silver 3050GE +Athlon Silver 3050U +Athlon Gold PRO 3125GE +Athlon Gold PRO 3150G +Athlon Gold PRO 3150GE +Athlon Gold PRO 4150GE +Athlon PRO 300GE +Athlon PRO 300U +Athlon PRO 3045B +EPYC 7252 +EPYC 7262 +EPYC 7272 +EPYC 7282 +EPYC 7302 +EPYC 7313 +EPYC 7343 +EPYC 7352 +EPYC 7402 +EPYC 7413 +EPYC 7443 +EPYC 7452 +EPYC 7453 +EPYC 7502 +EPYC 7513 +EPYC 7532 +EPYC 7542 +EPYC 7543 +EPYC 7552 +EPYC 7642 +EPYC 7643 +EPYC 7662 +EPYC 7663 +EPYC 7702 +EPYC 7713 +EPYC 7742 +EPYC 7763 +EPYC 7232P +EPYC 72F3 +EPYC 7302P +EPYC 7313P +EPYC 73F3 +EPYC 7402P +EPYC 7443P +EPYC 74F3 +EPYC 7502P +EPYC 7543P +EPYC 75F3 +EPYC 7702P +EPYC 7713P +EPYC 7F32 +EPYC 7F52 +EPYC 7F72 +EPYC 7H12 +Ryzen Embedded R2000 Series R2312 +Ryzen Embedded R2000 Series R2314 +Ryzen Embedded R2000 Series R2514 +Ryzen Embedded R2000 Series R2544 +Ryzen Z1 +Ryzen Z1 Extreme +Ryzen 3 3100 +Ryzen 3 4100 +Ryzen 3 2300X +Ryzen 3 3200G +Ryzen 3 3200GE +Ryzen 3 3200U +Ryzen 3 3250C +Ryzen 3 3250U +Ryzen 3 3300U +Ryzen 3 3350U +Ryzen 3 4300G +Ryzen 3 4300GE +Ryzen 3 4300U +Ryzen 3 5125C +Ryzen 3 5300G +Ryzen 3 5300GE +Ryzen 3 5300U +Ryzen 3 5400U +Ryzen 3 5425C +Ryzen 3 5425U +Ryzen 3 7320e +Ryzen 3 7320U +Ryzen 3 7330U +Ryzen 3 7335U +Ryzen 3 7440U +Ryzen 3 5380U +Ryzen 3 PRO 3200G +Ryzen 3 PRO 3200GE +Ryzen 3 PRO 3300U +Ryzen 3 PRO 4350G +Ryzen 3 PRO 4350GE +Ryzen 3 PRO 4450U +Ryzen 3 PRO 5350G +Ryzen 3 PRO 5350GE +Ryzen 3 PRO 5450U +Ryzen 3 PRO 5475U +Ryzen 3 PRO 7330U +Ryzen 3 PRO 4355G +Ryzen 3 PRO 4355GE +Ryzen 5 2600 +Ryzen 5 3600 +Ryzen 5 4500 +Ryzen 5 5500 +Ryzen 5 5600 +Ryzen 5 7600 +Ryzen 5 2500X +Ryzen 5 2600E +Ryzen 5 2600X +Ryzen 5 3350G +Ryzen 5 3350GE +Ryzen 5 3400G +Ryzen 5 3400GE +Ryzen 5 3450U +Ryzen 5 3500 +Ryzen 5 3500C +Ryzen 5 3500U +Ryzen 5 3500X +Ryzen 5 3550H +Ryzen 5 3580U +Ryzen 5 3600X +Ryzen 5 3600XT +Ryzen 5 4500U +Ryzen 5 4600G +Ryzen 5 4600GE +Ryzen 5 4600H +Ryzen 5 4600HS +Ryzen 5 4600U +Ryzen 5 5300G +Ryzen 5 5300GE +Ryzen 5 5425U +Ryzen 5 5500U +Ryzen 5 5560U +Ryzen 5 5600G +Ryzen 5 5600GE +Ryzen 5 5600H +Ryzen 5 5600HS +Ryzen 5 5600U +Ryzen 5 5600X +Ryzen 5 5625C +Ryzen 5 5625U +Ryzen 5 6600H +Ryzen 5 6600HS +Ryzen 5 6600U +Ryzen 5 7520U +Ryzen 5 7530U +Ryzen 5 7535HS +Ryzen 5 7535U +Ryzen 5 7540U +Ryzen 5 7600X +Ryzen 5 7640HS +Ryzen 5 7640S +Ryzen 5 7640U +Ryzen 5 7645HX +Ryzen 5 7640H +Ryzen 5 PRO 2600 +Ryzen 5 PRO 3600 +Ryzen 5 PRO 5645 +Ryzen 5 PRO 3350G +Ryzen 5 PRO 3350GE +Ryzen 5 PRO 3400G +Ryzen 5 PRO 3400GE +Ryzen 5 PRO 3500U +Ryzen 5 PRO 4650G +Ryzen 5 PRO 4650GE +Ryzen 5 PRO 4650U +Ryzen 5 PRO 5475U +Ryzen 5 PRO 5650G +Ryzen 5 PRO 5650GE +Ryzen 5 PRO 5650HS +Ryzen 5 PRO 5650HX +Ryzen 5 PRO 5650U +Ryzen 5 PRO 5675U +Ryzen 5 PRO 5750G +Ryzen 5 PRO 5750GE +Ryzen 5 PRO 6650H +Ryzen 5 PRO 6650HS +Ryzen 5 PRO 6650U +Ryzen 5 PRO 7530U +Ryzen 5 PRO 7540U +Ryzen 5 PRO 7640U +Ryzen 5 PRO 4655G +Ryzen 5 PRO 4655GE +Ryzen 7 2700 +Ryzen 7 5800 +Ryzen 7 5800 +Ryzen 7 7700 +Ryzen 7 2700E +Ryzen 7 2700X +Ryzen 7 3700C +Ryzen 7 3700U +Ryzen 7 3700X +Ryzen 7 3750H +Ryzen 7 3780U +Ryzen 7 3800X +Ryzen 7 3800XT +Ryzen 7 4700G +Ryzen 7 4700GE +Ryzen 7 4700U +Ryzen 7 4800H +Ryzen 7 4800HS +Ryzen 7 4800U +Ryzen 7 5700G +Ryzen 7 5700GE +Ryzen 7 5700U +Ryzen 7 5700X +Ryzen 7 5800H +Ryzen 7 5800HS +Ryzen 7 5800U +Ryzen 7 5800X +Ryzen 7 5800X3D +Ryzen 7 5825C +Ryzen 7 5825U +Ryzen 7 6800H +Ryzen 7 6800HS +Ryzen 7 6800U +Ryzen 7 6810U +Ryzen 7 7700X +Ryzen 7 7730U +Ryzen 7 7735HS +Ryzen 7 7735U +Ryzen 7 7736U +Ryzen 7 7745HX +Ryzen 7 7800X3D +Ryzen 7 7840H +Ryzen 7 7840HS +Ryzen 7 7840S +Ryzen 7 7840U +Ryzen 7 PRO 2700 +Ryzen 7 PRO 3700 +Ryzen 7 PRO 5845 +Ryzen 7 PRO 2700X +Ryzen 7 PRO 3700U +Ryzen 7 PRO 4750G +Ryzen 7 PRO 4750GE +Ryzen 7 PRO 4750U +Ryzen 7 PRO 5850HS +Ryzen 7 PRO 5850HX +Ryzen 7 PRO 5850U +Ryzen 7 PRO 5875U +Ryzen 7 PRO 6850H +Ryzen 7 PRO 6850HS +Ryzen 7 PRO 6850U +Ryzen 7 PRO 6860Z +Ryzen 7 PRO 7730U +Ryzen 7 PRO 7840U +Ryzen 9 5900 +Ryzen 9 7900 +Ryzen 9 3900 +Ryzen 9 3900X +Ryzen 9 3900XT +Ryzen 9 3950X +Ryzen 9 4900H +Ryzen 9 4900HS +Ryzen 9 5900HS +Ryzen 9 5900HX +Ryzen 9 5900X +Ryzen 9 5950X +Ryzen 9 5980HS +Ryzen 9 5980HX +Ryzen 9 6900HS +Ryzen 9 6900HX +Ryzen 9 6980HS +Ryzen 9 6980HX +Ryzen 9 7845HX +Ryzen 9 7900X +Ryzen 9 7900X3D +Ryzen 9 7940H +Ryzen 9 7940HS +Ryzen 9 7945HX +Ryzen 9 7950X +Ryzen 9 7950X3D +Ryzen 9 PRO 3900 +Ryzen 9 PRO 5945 +Ryzen 9 PRO 6950H +Ryzen 9 PRO 6950HS +Ryzen Embedded V2516 +Ryzen Embedded V2546 +Ryzen Embedded V2718 +Ryzen Embedded V2748 +Ryzen Threadripper PRO 3945WX +Ryzen Threadripper PRO 3955WX +Ryzen Threadripper PRO 3975WX +Ryzen Threadripper PRO 3995WX +Ryzen Threadripper PRO 5945WX +Ryzen Threadripper PRO 5955WX +Ryzen Threadripper PRO 5965WX +Ryzen Threadripper PRO 5975WX +Ryzen Threadripper PRO 5995WX +EOF \ No newline at end of file diff --git a/msp/win11-intel-cpus.txt b/msp/win11-intel-cpus.txt new file mode 100644 index 0000000..cc0a3d9 --- /dev/null +++ b/msp/win11-intel-cpus.txt @@ -0,0 +1,790 @@ +2.5.0.4 +Atom(R) x6200FE +Atom(R) x6211E +Atom(R) x6212RE +Atom(R) x6413E +Atom(R) x6414RE +Atom(R) x6425E +Atom(R) x6425RE +Atom(R) x6427FE +Celeron(R) 6305 +Celeron(R) 7300 +Celeron(R) 7305 +Celeron(R) 3867U +Celeron(R) 4205U +Celeron(R) 4305U +Celeron(R) 4305UE +Celeron(R) 5205U +Celeron(R) 5305U +Celeron(R) 6305E +Celeron(R) 6600HE +Celeron(R) 7305E +Celeron(R) 7305L +Celeron(R) G4900 +Celeron(R) G4900T +Celeron(R) G4920 +Celeron(R) G4930 +Celeron(R) G4930E +Celeron(R) G4930T +Celeron(R) G4932E +Celeron(R) G4950 +Celeron(R) G5900 +Celeron(R) G5900E +Celeron(R) G5900T +Celeron(R) G5900TE +Celeron(R) G5905 +Celeron(R) G5905T +Celeron(R) G5920 +Celeron(R) G5925 +Celeron(R) G6900 +Celeron(R) G6900E +Celeron(R) G6900T +Celeron(R) G6900TE +Celeron(R) J4005 +Celeron(R) J4025 +Celeron(R) J4105 +Celeron(R) J4115 +Celeron(R) J4125 +Celeron(R) J6412 +Celeron(R) J6413 +Celeron(R) N4000 +Celeron(R) N4020 +Celeron(R) N4100 +Celeron(R) N4120 +Celeron(R) N4500 +Celeron(R) N4505 +Celeron(R) N5095 +Celeron(R) N5100 +Celeron(R) N5105 +Celeron(R) N6210 +Celeron(R) N6211 +Core(TM) i3-1000G1 +Core(TM) i3-1000G4 +Core(TM) i3-1005G1 +Core(TM) i3-10100 +Core(TM) i3-10100E +Core(TM) i3-10100F +Core(TM) i3-10100T +Core(TM) i3-10100TE +Core(TM) i3-10100Y +Core(TM) i3-10105 +Core(TM) i3-10105F +Core(TM) i3-10105T +Core(TM) i3-10110U +Core(TM) i3-10110Y +Core(TM) i3-10300 +Core(TM) i3-10300T +Core(TM) i3-10305 +Core(TM) i3-10305T +Core(TM) i3-10320 +Core(TM) i3-10325 +Core(TM) i3-11100HE +Core(TM) i3-1110G4 +Core(TM) i3-1115G4 +Core(TM) i3-1115G4E +Core(TM) i3-1115GRE +Core(TM) i3-1120G4 +Core(TM) i3-1125G4 +Core(TM) i3-12100 +Core(TM) i3-12100E +Core(TM) i3-12100F +Core(TM) i3-12100T +Core(TM) i3-12100TE +Core(TM) i3-1210U +Core(TM) i3-1215U +Core(TM) i3-1215UE +Core(TM) i3-1215UL +Core(TM) i3-1220P +Core(TM) i3-1220PE +Core(TM) i3-12300 +Core(TM) i3-12300HE +Core(TM) i3-12300HL +Core(TM) i3-12300T +Core(TM) i3-1305U +Core(TM) i3-13100 +Core(TM) i3-13100E +Core(TM) i3-13100F +Core(TM) i3-13100T +Core(TM) i3-13100TE +Core(TM) i3-1315U +Core(TM) i3-1315UE +Core(TM) i3-1320PE +Core(TM) i3-13300HE +Core(TM) i3-8100 +Core(TM) i3-8100B +Core(TM) i3-8100H +Core(TM) i3-8100T +Core(TM) i3-8109U +Core(TM) i3-8130U +Core(TM) i3-8140U +Core(TM) i3-8145U +Core(TM) i3-8145UE +Core(TM) i3-8300 +Core(TM) i3-8300T +Core(TM) i3-8350K +Core(TM) i3-9100 +Core(TM) i3-9100E +Core(TM) i3-9100F +Core(TM) i3-9100HL +Core(TM) i3-9100T +Core(TM) i3-9100TE +Core(TM) i3-9300 +Core(TM) i3-9300T +Core(TM) i3-9320 +Core(TM) i3-9350K +Core(TM) i3-9350KF +Core(TM) i3-L13G4 +Core(TM) i3-N300 +Core(TM) i3-N305 +Core(TM) i5-10200H +Core(TM) i5-10210U +Core(TM) i5-10210Y +Core(TM) i5-10300H +Core(TM) i5-1030G4 +Core(TM) i5-1030G7 +Core(TM) i5-10310U +Core(TM) i5-10310Y +Core(TM) i5-1035G1 +Core(TM) i5-1035G4 +Core(TM) i5-1035G7 +Core(TM) i5-1038NG7 +Core(TM) i5-10400 +Core(TM) i5-10400F +Core(TM) i5-10400H +Core(TM) i5-10400T +Core(TM) i5-10500 +Core(TM) i5-10500E +Core(TM) i5-10500H +Core(TM) i5-10500T +Core(TM) i5-10500TE +Core(TM) i5-10505 +Core(TM) i5-10600 +Core(TM) i5-10600K +Core(TM) i5-10600KF +Core(TM) i5-10600T +Core(TM) i5-11260H +Core(TM) i5-11300H +Core(TM) i5-1130G7 +Core(TM) i5-11320H +Core(TM) i5-1135G7 +Core(TM) i5-1135G7 +Core(TM) i5-11400 +Core(TM) i5-11400F +Core(TM) i5-11400H +Core(TM) i5-11400T +Core(TM) i5-1140G7 +Core(TM) i5-1145G7 +Core(TM) i5-1145G7E +Core(TM) i5-1145GRE +Core(TM) i5-11500 +Core(TM) i5-11500H +Core(TM) i5-11500HE +Core(TM) i5-11500T +Core(TM) i5-1155G7 +Core(TM) i5-11600 +Core(TM) i5-11600K +Core(TM) i5-11600KF +Core(TM) i5-11600T +Core(TM) i5-1230U +Core(TM) i5-1235U +Core(TM) i5-1235UL +Core(TM) i5-12400 +Core(TM) i5-12400F +Core(TM) i5-12400T +Core(TM) i5-1240P +Core(TM) i5-1240U +Core(TM) i5-12450H +Core(TM) i5-12450HX +Core(TM) i5-1245U +Core(TM) i5-1245UE +Core(TM) i5-1245UL +Core(TM) i5-12500 +Core(TM) i5-12500E +Core(TM) i5-12500H +Core(TM) i5-12500HL +Core(TM) i5-12500T +Core(TM) i5-12500TE +Core(TM) i5-1250P +Core(TM) i5-1250PE +Core(TM) i5-12600 +Core(TM) i5-12600H +Core(TM) i5-12600HE +Core(TM) i5-12600HL +Core(TM) i5-12600HX +Core(TM) i5-12600K +Core(TM) i5-12600KF +Core(TM) i5-12600T +Core(TM) i5-1334U +Core(TM) i5-1335U +Core(TM) i5-1335UE +Core(TM) i5-13400 +Core(TM) i5-13400E +Core(TM) i5-13400F +Core(TM) i5-13400T +Core(TM) i5-1340P +Core(TM) i5-1340PE +Core(TM) i5-13420H +Core(TM) i5-13450HX +Core(TM) i5-1345U +Core(TM) i5-1345UE +Core(TM) i5-13490F +Core(TM) i5-13500 +Core(TM) i5-13500E +Core(TM) i5-13500H +Core(TM) i5-13500HX +Core(TM) i5-13500T +Core(TM) i5-13500TE +Core(TM) i5-13505H +Core(TM) i5-1350P +Core(TM) i5-1350PE +Core(TM) i5-13600 +Core(TM) i5-13600H +Core(TM) i5-13600HE +Core(TM) i5-13600HX +Core(TM) i5-13600K +Core(TM) i5-13600KF +Core(TM) i5-13600T +Core(TM) i5-8200Y +Core(TM) i5-8210Y +Core(TM) i5-8250U +Core(TM) i5-8257U +Core(TM) i5-8259U +Core(TM) i5-8260U +Core(TM) i5-8265U +Core(TM) i5-8269U +Core(TM) i5-8279U +Core(TM) i5-8300H +Core(TM) i5-8305G +Core(TM) i5-8310Y +Core(TM) i5-8350U +Core(TM) i5-8365U +Core(TM) i5-8365UE +Core(TM) i5-8400 +Core(TM) i5-8400B +Core(TM) i5-8400H +Core(TM) i5-8400T +Core(TM) i5-8500 +Core(TM) i5-8500B +Core(TM) i5-8500T +Core(TM) i5-8600 +Core(TM) i5-8600K +Core(TM) i5-8600T +Core(TM) i5-9300H +Core(TM) i5-9300HF +Core(TM) i5-9400 +Core(TM) i5-9400F +Core(TM) i5-9400H +Core(TM) i5-9400T +Core(TM) i5-9500 +Core(TM) i5-9500E +Core(TM) i5-9500F +Core(TM) i5-9500T +Core(TM) i5-9500TE +Core(TM) i5-9600 +Core(TM) i5-9600K +Core(TM) i5-9600KF +Core(TM) i5-9600T +Core(TM) i5-L16G7 +Core(TM) i7-10510U +Core(TM) i7-10510Y +Core(TM) i7-1060G7 +Core(TM) i7-10610U +Core(TM) i7-1065G7 +Core(TM) i7-1068G7 +Core(TM) i7-1068NG7 +Core(TM) i7-10700 +Core(TM) i7-10700E +Core(TM) i7-10700F +Core(TM) i7-10700K +Core(TM) i7-10700KF +Core(TM) i7-10700T +Core(TM) i7-10700TE +Core(TM) i7-10710U +Core(TM) i7-10750H +Core(TM) i7-10810U +Core(TM) i7-10850H +Core(TM) i7-10870H +Core(TM) i7-10875H +Core(TM) i7-11370H +Core(TM) i7-11375H +Core(TM) i7-11390H +Core(TM) i7-11600H +Core(TM) i7-1160G7 +Core(TM) i7-1165G7 +Core(TM) i7-1165G7 +Core(TM) i7-11700 +Core(TM) i7-11700F +Core(TM) i7-11700K +Core(TM) i7-11700KF +Core(TM) i7-11700T +Core(TM) i7-11800H +Core(TM) i7-1180G7 +Core(TM) i7-11850H +Core(TM) i7-11850HE +Core(TM) i7-1185G7 +Core(TM) i7-1185G7E +Core(TM) i7-1185GRE +Core(TM) i7-1195G7 +Core(TM) i7-1250U +Core(TM) i7-1255U +Core(TM) i7-1255UL +Core(TM) i7-1260P +Core(TM) i7-1260U +Core(TM) i7-12650HX +Core(TM) i7-1265U +Core(TM) i7-1265UE +Core(TM) i7-1265UL +Core(TM) i7-12700 +Core(TM) i7-12700E +Core(TM) i7-12700F +Core(TM) i7-12700H +Core(TM) i7-12700HL +Core(TM) i7-12700K +Core(TM) i7-12700KF +Core(TM) i7-12700T +Core(TM) i7-12700TE +Core(TM) i7-1270P +Core(TM) i7-1270PE +Core(TM) i7-12800H +Core(TM) i7-12800HE +Core(TM) i7-12800HL +Core(TM) i7-12800HX +Core(TM) i7-1280P +Core(TM) i7-12850HX +Core(TM) i7-1355U +Core(TM) i7-1360P +Core(TM) i7-13620H +Core(TM) i7-13650HX +Core(TM) i7-1365U +Core(TM) i7-1365UE +Core(TM) i7-13700 +Core(TM) i7-13700E +Core(TM) i7-13700F +Core(TM) i7-13700H +Core(TM) i7-13700HX +Core(TM) i7-13700K +Core(TM) i7-13700KF +Core(TM) i7-13700T +Core(TM) i7-13700TE +Core(TM) i7-13705H +Core(TM) i7-1370P +Core(TM) i7-1370PE +Core(TM) i7-13790F +Core(TM) i7-13800H +Core(TM) i7-13800HE +Core(TM) i7-13850HX +Core(TM) i7-7800X +Core(TM) i7-7820HQ +Core(TM) i7-7820X +Core(TM) i7-8086K +Core(TM) i7-8500Y +Core(TM) i7-8550U +Core(TM) i7-8557U +Core(TM) i7-8559U +Core(TM) i7-8565U +Core(TM) i7-8569U +Core(TM) i7-8650U +Core(TM) i7-8665U +Core(TM) i7-8665UE +Core(TM) i7-8700 +Core(TM) i7-8700B +Core(TM) i7-8700K +Core(TM) i7-8700T +Core(TM) i7-8705G +Core(TM) i7-8706G +Core(TM) i7-8709G +Core(TM) i7-8750H +Core(TM) i7-8809G +Core(TM) i7-8850H +Core(TM) i7-9700 +Core(TM) i7-9700E +Core(TM) i7-9700F +Core(TM) i7-9700K +Core(TM) i7-9700KF +Core(TM) i7-9700T +Core(TM) i7-9700TE +Core(TM) i7-9750H +Core(TM) i7-9750HF +Core(TM) i7-9800X +Core(TM) i7-9850H +Core(TM) i7-9850HE +Core(TM) i7-9850HL +Core(TM) i9-10850K +Core(TM) i9-10885H +Core(TM) i9-10900 +Core(TM) i9-10900E +Core(TM) i9-10900F +Core(TM) i9-10900K +Core(TM) i9-10900KF +Core(TM) i9-10900T +Core(TM) i9-10900TE +Core(TM) i9-10900X +Core(TM) i9-10920X +Core(TM) i9-10940X +Core(TM) i9-10980HK +Core(TM) i9-10980XE +Core(TM) i9-11900 +Core(TM) i9-11900F +Core(TM) i9-11900H +Core(TM) i9-11900K +Core(TM) i9-11900KF +Core(TM) i9-11900T +Core(TM) i9-11950H +Core(TM) i9-11980HK +Core(TM) i9-12900 +Core(TM) i9-12900E +Core(TM) i9-12900F +Core(TM) i9-12900H +Core(TM) i9-12900HK +Core(TM) i9-12900HX +Core(TM) i9-12900K +Core(TM) i9-12900KF +Core(TM) i9-12900KS +Core(TM) i9-12900T +Core(TM) i9-12900TE +Core(TM) i9-12950HX +Core(TM) i9-13900 +Core(TM) i9-13900F +Core(TM) i9-13900K +Core(TM) i9-13900KF +Core(TM) i9-13900T +Core(TM) i9-7900X +Core(TM) i9-7920X +Core(TM) i9-7940X +Core(TM) i9-7960X +Core(TM) i9-7980XE +Core(TM) i9-8950HK +Core(TM) i9-9820X +Core(TM) i9-9880H +Core(TM) i9-9900 +Core(TM) i9-9900K +Core(TM) i9-9900KF +Core(TM) i9-9900KS +Core(TM) i9-9900T +Core(TM) i9-9900X +Core(TM) i9-9920X +Core(TM) i9-9940X +Core(TM) i9-9960X +Core(TM) i9-9980HK +Core(TM) i9-9980XE +Core(TM) m3-8100Y +Pentium(R) Gold 4417U +Pentium(R) Gold 4425Y +Pentium(R) Gold 5405U +Pentium(R) Gold 6405U +Pentium(R) Gold 6500Y +Pentium(R) Gold 6805 +Pentium(R) Gold 7505 +Pentium(R) Gold 8500 +Pentium(R) Gold 8505 +Pentium(R) Gold G5400 +Pentium(R) Gold G5400T +Pentium(R) Gold G5420 +Pentium(R) Gold G5420T +Pentium(R) Gold G5500 +Pentium(R) Gold G5500T +Pentium(R) Gold G5600 +Pentium(R) Gold G5600E +Pentium(R) Gold G5600T +Pentium(R) Gold G5620 +Pentium(R) Gold G6400 +Pentium(R) Gold G6400E +Pentium(R) Gold G6400T +Pentium(R) Gold G6400TE +Pentium(R) Gold G6405 +Pentium(R) Gold G6405T +Pentium(R) Gold G6500 +Pentium(R) Gold G6500T +Pentium(R) Gold G6505 +Pentium(R) Gold G6505T +Pentium(R) Gold G6600 +Pentium(R) Gold G6605 +Pentium(R) Gold G7400 +Pentium(R) Gold G7400E +Pentium(R) Gold G7400T +Pentium(R) Gold G7400TE +Pentium(R) J6426 +Pentium(R) N6415 +Pentium(R) Silver J5005 +Pentium(R) Silver J5040 +Pentium(R) Silver N5000 +Pentium(R) Silver N5030 +Pentium(R) Silver N6000 +Pentium(R) Silver N6005 +Processor N100 +Processor N200 +Xeon(R) Bronze 3104 +Xeon(R) Bronze 3106 +Xeon(R) Bronze 3204 +Xeon(R) Bronze 3206R +Xeon(R) D-1702 +Xeon(R) D-1712TR +Xeon(R) D-1713NT +Xeon(R) D-1713NTE +Xeon(R) D-1714 +Xeon(R) D-1715TER +Xeon(R) D-1718T +Xeon(R) D-1722NE +Xeon(R) D-1726 +Xeon(R) D-1732TE +Xeon(R) D-1733NT +Xeon(R) D-1735TR +Xeon(R) D-1736 +Xeon(R) D-1736NT +Xeon(R) D-1739 +Xeon(R) D-1746TER +Xeon(R) D-1747NTE +Xeon(R) D-1748TE +Xeon(R) D-1749NT +Xeon(R) D-2712T +Xeon(R) D-2733NT +Xeon(R) D-2738 +Xeon(R) D-2752NTE +Xeon(R) D-2752TER +Xeon(R) D-2753NT +Xeon(R) D-2766NT +Xeon(R) D-2775TE +Xeon(R) D-2776NT +Xeon(R) D-2779 +Xeon(R) D-2786NTE +Xeon(R) D-2795NT +Xeon(R) D-2796NT +Xeon(R) D-2796TE +Xeon(R) D-2798NT +Xeon(R) D-2799 +Xeon(R) Gold 5115 +Xeon(R) Gold 5118 +Xeon(R) Gold 5119T +Xeon(R) Gold 5120 +Xeon(R) Gold 5120T +Xeon(R) Gold 5122 +Xeon(R) Gold 5215 +Xeon(R) Gold 5215L +Xeon(R) Gold 5215M +Xeon(R) Gold 5217 +Xeon(R) Gold 5218 +Xeon(R) Gold 5218B +Xeon(R) Gold 5218N +Xeon(R) Gold 5218R +Xeon(R) Gold 5218T +Xeon(R) Gold 5220 +Xeon(R) Gold 5220R +Xeon(R) Gold 5220S +Xeon(R) Gold 5220T +Xeon(R) Gold 5222 +Xeon(R) Gold 5315Y +Xeon(R) Gold 5317 +Xeon(R) Gold 5318N +Xeon(R) Gold 5318S +Xeon(R) Gold 5318Y +Xeon(R) Gold 5320 +Xeon(R) Gold 5320T +Xeon(R) Gold 6126 +Xeon(R) Gold 6126F +Xeon(R) Gold 6126T +Xeon(R) Gold 6128 +Xeon(R) Gold 6130 +Xeon(R) Gold 6130F +Xeon(R) Gold 6130T +Xeon(R) Gold 6132 +Xeon(R) Gold 6134 +Xeon(R) Gold 6136 +Xeon(R) Gold 6138 +Xeon(R) Gold 6138F +Xeon(R) Gold 6138P +Xeon(R) Gold 6138T +Xeon(R) Gold 6140 +Xeon(R) Gold 6142 +Xeon(R) Gold 6142F +Xeon(R) Gold 6144 +Xeon(R) Gold 6146 +Xeon(R) Gold 6148 +Xeon(R) Gold 6148F +Xeon(R) Gold 6150 +Xeon(R) Gold 6152 +Xeon(R) Gold 6154 +Xeon(R) Gold 6208U +Xeon(R) Gold 6209U +Xeon(R) Gold 6210U +Xeon(R) Gold 6212U +Xeon(R) Gold 6222V +Xeon(R) Gold 6226 +Xeon(R) Gold 6226R +Xeon(R) Gold 6230 +Xeon(R) Gold 6230N +Xeon(R) Gold 6230R +Xeon(R) Gold 6230T +Xeon(R) Gold 6234 +Xeon(R) Gold 6238 +Xeon(R) Gold 6238L +Xeon(R) Gold 6238M +Xeon(R) Gold 6238R +Xeon(R) Gold 6238T +Xeon(R) Gold 6240 +Xeon(R) Gold 6240L +Xeon(R) Gold 6240M +Xeon(R) Gold 6240R +Xeon(R) Gold 6240Y +Xeon(R) Gold 6242 +Xeon(R) Gold 6242R +Xeon(R) Gold 6244 +Xeon(R) Gold 6246 +Xeon(R) Gold 6246R +Xeon(R) Gold 6248 +Xeon(R) Gold 6248R +Xeon(R) Gold 6250 +Xeon(R) Gold 6250L +Xeon(R) Gold 6252 +Xeon(R) Gold 6252N +Xeon(R) Gold 6254 +Xeon(R) Gold 6256 +Xeon(R) Gold 6258R +Xeon(R) Gold 6262V +Xeon(R) Gold 6312U +Xeon(R) Gold 6314U +Xeon(R) Gold 6326 +Xeon(R) Gold 6330 +Xeon(R) Gold 6330N +Xeon(R) Gold 6334 +Xeon(R) Gold 6336Y +Xeon(R) Gold 6338 +Xeon(R) Gold 6338N +Xeon(R) Gold 6338T +Xeon(R) Gold 6342 +Xeon(R) Gold 6346 +Xeon(R) Gold 6348 +Xeon(R) Gold 6354 +Xeon(R) Platinum 8153 +Xeon(R) Platinum 8156 +Xeon(R) Platinum 8158 +Xeon(R) Platinum 8160 +Xeon(R) Platinum 8160F +Xeon(R) Platinum 8160T +Xeon(R) Platinum 8164 +Xeon(R) Platinum 8168 +Xeon(R) Platinum 8170 +Xeon(R) Platinum 8171M +Xeon(R) Platinum 8176 +Xeon(R) Platinum 8176F +Xeon(R) Platinum 8180 +Xeon(R) Platinum 8253 +Xeon(R) Platinum 8256 +Xeon(R) Platinum 8260 +Xeon(R) Platinum 8260L +Xeon(R) Platinum 8260M +Xeon(R) Platinum 8260Y +Xeon(R) Platinum 8268 +Xeon(R) Platinum 8270 +Xeon(R) Platinum 8272CL +Xeon(R) Platinum 8276 +Xeon(R) Platinum 8276L +Xeon(R) Platinum 8276M +Xeon(R) Platinum 8280 +Xeon(R) Platinum 8280L +Xeon(R) Platinum 8280M +Xeon(R) Platinum 8351N +Xeon(R) Platinum 8352M +Xeon(R) Platinum 8352S +Xeon(R) Platinum 8352V +Xeon(R) Platinum 8352Y +Xeon(R) Platinum 8358 +Xeon(R) Platinum 8358P +Xeon(R) Platinum 8360Y +Xeon(R) Platinum 8362 +Xeon(R) Platinum 8368 +Xeon(R) Platinum 8368Q +Xeon(R) Platinum 8380 +Xeon(R) Platinum 9221 +Xeon(R) Platinum 9222 +Xeon(R) Platinum 9242 +Xeon(R) Platinum 9282 +Xeon(R) Silver 4108 +Xeon(R) Silver 4109T +Xeon(R) Silver 4110 +Xeon(R) Silver 4112 +Xeon(R) Silver 4114 +Xeon(R) Silver 4114T +Xeon(R) Silver 4116 +Xeon(R) Silver 4116T +Xeon(R) Silver 4208 +Xeon(R) Silver 4209T +Xeon(R) Silver 4210 +Xeon(R) Silver 4210R +Xeon(R) Silver 4210T +Xeon(R) Silver 4214 +Xeon(R) Silver 4214R +Xeon(R) Silver 4214Y +Xeon(R) Silver 4215 +Xeon(R) Silver 4215R +Xeon(R) Silver 4216 +Xeon(R) Silver 4309Y +Xeon(R) Silver 4310 +Xeon(R) Silver 4310T +Xeon(R) Silver 4314 +Xeon(R) Silver 4316 +Xeon(R) W-10855M +Xeon(R) W-10885M +Xeon(R) W-11055M +Xeon(R) W-11155MLE +Xeon(R) W-11155MRE +Xeon(R) W-11555MLE +Xeon(R) W-11555MRE +Xeon(R) W-11855M +Xeon(R) W-11855M +Xeon(R) W-11865MLE +Xeon(R) W-11865MRE +Xeon(R) W-11955M +Xeon(R) W-1250 +Xeon(R) W-1250E +Xeon(R) W-1250P +Xeon(R) W-1250TE +Xeon(R) W-1270 +Xeon(R) W-1270E +Xeon(R) W-1270P +Xeon(R) W-1270TE +Xeon(R) W-1290 +Xeon(R) W-1290E +Xeon(R) W-1290P +Xeon(R) W-1290T +Xeon(R) W-1290TE +Xeon(R) W-1350 +Xeon(R) W-1350P +Xeon(R) W-1370 +Xeon(R) W-1370P +Xeon(R) W-1390 +Xeon(R) W-1390P +Xeon(R) W-1390T +Xeon(R) W-2102 +Xeon(R) W-2104 +Xeon(R) W-2123 +Xeon(R) W-2125 +Xeon(R) W-2133 +Xeon(R) W-2135 +Xeon(R) W-2145 +Xeon(R) W-2155 +Xeon(R) W-2175 +Xeon(R) W-2195 +Xeon(R) W-2223 +Xeon(R) W-2225 +Xeon(R) W-2235 +Xeon(R) W-2245 +Xeon(R) W-2255 +Xeon(R) W-2265 +Xeon(R) W-2275 +Xeon(R) W-2295 +Xeon(R) W-3175X +Xeon(R) W-3223 +Xeon(R) W-3225 +Xeon(R) W-3235 +Xeon(R) W-3245 +Xeon(R) W-3245M +Xeon(R) W-3265 +Xeon(R) W-3265M +Xeon(R) W-3275 +Xeon(R) W-3275M +Xeon(R) W-3323 +Xeon(R) W-3335 +Xeon(R) W-3345 +Xeon(R) W-3365 +Xeon(R) W-3375 +EOF