Смотри!
PS C:\Users\Max\Desktop\PythonBox> py .\main.py

1. Создать ВМ
2. Запустить ВМ
3. Список ВМ
4. Завершить работу ВМ (ACPI shutdown)
5. Перезагрузить ВМ
6. Удалить ВМ
7. Выход
8. Отсоединить ISO-диск (detach medium)
9. Принудительное завершение работы ВМ (poweroff)
10. Сохранить снимок
11. Загрузить снимок
12. Подключить USB-планшет(реальная мышка)

Здесь убери пункты с функциями, которые скажу и скажу новые!
Убери(4, 12)
Измени порядок в следующем порядке: 1, 3, 2, 6, 9, 5, 8, 10, 11!
И добавь пункт Настройка после 1 и до 3! При его выборе мы должны написать имя ВМ и потом откроется диалоговое окно с настройками(от VirtualBox)!
Вот код:
import os
import json
import subprocess
import time
import socket

data_file = "vms_data.json"

# Функция для поиска VBoxManage через PATH
def find_vboxmanage():
    vboxmanage_path = "VBoxManage"
    result = subprocess.run([vboxmanage_path, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if result.returncode == 0:
        return vboxmanage_path
    print("Не удалось найти VBoxManage.exe в переменной среды PATH. Убедитесь, что VirtualBox установлен.")
    return None

# Попытаться найти VBoxManage через PATH
VBOXMANAGE_PATH = find_vboxmanage()
if VBOXMANAGE_PATH is None:
    exit("Программа завершена из-за отсутствия VBoxManage.exe.")

# Функция для выбора операционной системы для ВМ (расширенный список всех поддерживаемых версий VirtualBox)
def get_os_type():
    print("Выберите операционную систему для ВМ:")
    print("1.  Windows 95")
    print("2.  Windows 98")
    print("3.  Windows ME")
    print("4.  Windows NT")
    print("5.  Windows 2000")
    print("6.  Windows XP (32-bit)")
    print("7.  Windows XP (64-bit)")
    print("8.  Windows Vista (32-bit)")
    print("9.  Windows Vista (64-bit)")
    print("10. Windows 7 (32-bit)")
    print("11. Windows 7 (64-bit)")
    print("12. Windows 8/8.1 (32-bit)")
    print("13. Windows 8/8.1 (64-bit)")
    print("14. Windows 10 (32-bit)")
    print("15. Windows 10 (64-bit)")
    print("16. Windows Server 2003")
    print("17. Windows Server 2008")
    print("18. Windows Server 2012")
    print("19. Windows Server 2016")
    print("20. Windows Server 2019")
    print("21. OpenSolaris")
    print("22. OpenSolaris (64-bit)")
    print("23. Solaris")
    print("24. Solaris (64-bit)")
    print("25. Linux (32-bit)")
    print("26. Linux (64-bit)")
    print("27. Ubuntu (32-bit)")
    print("28. Ubuntu (64-bit)")
    print("29. Debian (32-bit)")
    print("30. Debian (64-bit)")
    print("31. Fedora (32-bit)")
    print("32. Fedora (64-bit)")
    print("33. Red Hat (32-bit)")
    print("34. Red Hat (64-bit)")
    print("35. CentOS (32-bit)")
    print("36. CentOS (64-bit)")
    print("37. openSUSE (32-bit)")
    print("38. openSUSE (64-bit)")
    print("39. Oracle (32-bit)")
    print("40. Oracle (64-bit)")
    print("41. FreeBSD (32-bit)")
    print("42. FreeBSD (64-bit)")
    print("43. NetBSD (32-bit)")
    print("44. NetBSD (64-bit)")
    print("45. OpenBSD (32-bit)")
    print("46. OpenBSD (64-bit)")
    print("47. OS/2 Warp")
    print("48. OS/2")
    print("49. eComStation")
    print("50. ArcaOS")
    print("51. DOS")
    print("52. Mac OS X (Intel)")
    print("53. Mac OS X (64-bit)")
    print("54. Другие (Other 32-bit)")
    print("55. Другие (Other 64-bit)")
    choice = input("Ваш выбор [1-55]: ").strip()
    mapping = {
        "1":  "Windows95",
        "2":  "Windows98",
        "3":  "WindowsME",
        "4":  "WindowsNT",
        "5":  "Windows2000",
        "6":  "WindowsXP",
        "7":  "WindowsXP_64",
        "8":  "WindowsVista",
        "9":  "WindowsVista_64",
        "10": "Windows7",
        "11": "Windows7_64",
        "12": "Windows8",
        "13": "Windows8_64",
        "14": "Windows10",
        "15": "Windows10_64",
        "16": "Windows2003",
        "17": "Windows2008",
        "18": "Windows2012",
        "19": "Windows2016",
        "20": "Windows2019",
        "21": "OpenSolaris",
        "22": "OpenSolaris_64",
        "23": "Solaris",
        "24": "Solaris_64",
        "25": "Linux",
        "26": "Linux_64",
        "27": "Ubuntu",
        "28": "Ubuntu_64",
        "29": "Debian",
        "30": "Debian_64",
        "31": "Fedora",
        "32": "Fedora_64",
        "33": "RedHat",
        "34": "RedHat_64",
        "35": "CentOS",
        "36": "CentOS_64",
        "37": "openSUSE",
        "38": "openSUSE_64",
        "39": "Oracle",
        "40": "Oracle_64",
        "41": "FreeBSD",
        "42": "FreeBSD_64",
        "43": "NetBSD",
        "44": "NetBSD_64",
        "45": "OpenBSD",
        "46": "OpenBSD_64",
        "47": "OS2Warp",
        "48": "OS2",
        "49": "eComStation",
        "50": "ArcaOS",
        "51": "DOS",
        "52": "MacOSX",
        "53": "MacOSX_64",
        "54": "Other",
        "55": "Other_64"
    }
    return mapping.get(choice, "Other_64")

# Функции для работы с данными виртуальных машин
def save_vm_data(vm_data):
    print(f"Сохраняем данные: {vm_data}")  # Отладка
    with open(data_file, "w") as f:
        json.dump(vm_data, f, indent=4)
    print("Данные ВМ сохранены в vms_data.json.")

def load_vm_data():
    if os.path.exists(data_file):
        with open(data_file, "r") as f:
            try:
                data = json.load(f)
                print(f"Загруженные данные ВМ: {data}")  # Отладка
                return data
            except json.JSONDecodeError:
                print("Ошибка декодирования JSON. Файл поврежден, создаем новый.")
                return {}
    return {}

# Функция для получения IP-адреса хоста
def get_host_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(("8.8.8.8", 80))
        ip = s.getsockname()[0]
    except Exception:
        ip = "127.0.0.1"
    finally:
        s.close()
    return ip

# Функция для получения VRDP порта из информации о ВМ
def get_vrde_port(vm_name):
    result = subprocess.run(f'{VBOXMANAGE_PATH} showvminfo "{vm_name}" --machinereadable', 
                              shell=True, capture_output=True, text=True)
    for line in result.stdout.splitlines():
        if line.startswith("vrdeport="):
            port = line.split("=")[1].strip().strip('"')
            return port
    return "Не определён"

def create_vm(name, iso, ram, cpus, disk_size):
    # Проверка, существует ли уже ВМ с таким именем
    existing_vms = subprocess.run(f'{VBOXMANAGE_PATH} list vms', shell=True, capture_output=True, text=True)
    if name in existing_vms.stdout:
        print(f"Виртуальная машина с именем '{name}' уже существует. Пожалуйста, выберите другое имя.")
        return

    # Запрашиваем выбор операционной системы
    ostype = get_os_type()

    iso_folder = "ISO"
    if not os.path.exists(iso_folder):
        os.makedirs(iso_folder)

    iso_file = os.path.basename(iso)
    iso_path = os.path.join(iso_folder, iso_file)
    if not os.path.exists(iso_path):
        print(f"ISO-файл '{iso_file}' не найден в папке '{iso_folder}'.\n"
              "Пожалуйста, поместите ISO-файл в папку и попробуйте снова.")
        return

    # Формируем абсолютный путь к дисковому образу с уникальным именем
    disk_image_name = f"{name}_{int(time.time())}.vdi"  # Используем текущее время для уникальности
    disk_image_path = os.path.abspath(disk_image_name)
    disk_image_path_quoted = f'"{disk_image_path}"'
    iso_path_quoted = f'"{iso_path}"'

    try:
        subprocess.run(f'{VBOXMANAGE_PATH} createvm --name "{name}" --register', shell=True, check=True)
        subprocess.run(f'{VBOXMANAGE_PATH} createhd --filename {disk_image_path_quoted} --size {disk_size * 1024}', 
                       shell=True, check=True)

        # Добавление необходимых параметров конфигурации
        subprocess.run(
            f'{VBOXMANAGE_PATH} modifyvm "{name}" '
            f'--memory {ram} '
            f'--cpus {cpus} '
            f'--ostype "{ostype}" '
            f'--vram 256 '  # Убедитесь, что видеопамять равна 256 MB
            f'--vrde on '   # Включаем VRDE для удаленного рабочего стола
            f'--graphicscontroller VBoxSVGA '  # Установка графического контроллера VBoxSVGA
            f'--accelerate3d on '  # Включаем 3D-ускорение
            f'--nic1 nat '         # Подключение к NAT для доступа в интернет
            f'--pae on '
            f'--chipset ich9 '
            f'--rtcuseutc on',
            shell=True, check=True)

        # Добавляем SATA Controller и прикрепляем виртуальный диск
        subprocess.run(f'{VBOXMANAGE_PATH} storagectl "{name}" --name "SATA Controller" --add sata --controller IntelAhci', 
                       shell=True, check=True)
        subprocess.run(f'{VBOXMANAGE_PATH} storageattach "{name}" --storagectl "SATA Controller" '
                       f'--port 0 --device 0 --type hdd --medium {disk_image_path_quoted}', 
                       shell=True, check=True)
        # Добавляем IDE Controller и монтируем ISO-образ
        subprocess.run(f'{VBOXMANAGE_PATH} storagectl "{name}" --name "IDE Controller" --add ide', 
                       shell=True, check=True)
        subprocess.run(f'{VBOXMANAGE_PATH} storageattach "{name}" --storagectl "IDE Controller" '
                       f'--port 0 --device 0 --type dvddrive --medium {iso_path_quoted}', 
                       shell=True, check=True)

    except subprocess.CalledProcessError as e:
        print("Ошибка при выполнении команды:", e)
        return

    vm_data = load_vm_data()  
    vm_data[name] = {
        "iso": iso,
        "ram": ram,
        "cpus": cpus,
        "disk_size": disk_size,
        "disk_image": disk_image_name  # Обновите на уникальное имя образа диска
    }
    save_vm_data(vm_data)
    print(f"ВМ '{name}' успешно создана и добавлена в список!")
    
    # Подключаем USB multi-touch планшет
    connect_usb_tablet(name)  # Здесь добавляем вызов функции подключения

# Функция запуска ВМ с проверкой порта и выбором типа подключения (RDP или SSH)
def run_vm(name, mode="headless"):
    vm_data = load_vm_data()
    if name not in vm_data:
        print("ВМ не найдена!")
        return

    connection_protocol = "RDP"  # По умолчанию

    if mode == "headless":
        conn_choice = input("Выберите тип подключения для headless (RDP/SSH): ").strip().lower()
        if conn_choice == "ssh":
            connection_protocol = "SSH"
            # Включаем VRDE с установкой порта по умолчанию для SSH (22)
            subprocess.run(f'{VBOXMANAGE_PATH} modifyvm "{name}" --vrde on --vrdeport 22', shell=True, check=True)
            print(f"VRDE включен для ВМ '{name}'. Подключайтесь через SSH-клиент.")
        else:
            connection_protocol = "RDP"
            # Включаем VRDE с установкой порта по умолчанию для RDP (3389)
            subprocess.run(f'{VBOXMANAGE_PATH} modifyvm "{name}" --vrde on --vrdeport 3389', shell=True, check=True)
            print(f"VRDE включен для ВМ '{name}'. Подключайтесь через RDP-клиент.")
    
    # Задаем загрузку с DVD, чтобы монтировать ISO
    subprocess.run(f'{VBOXMANAGE_PATH} modifyvm "{name}" --boot1 dvd', shell=True, check=True)

    try:
        subprocess.Popen(f'{VBOXMANAGE_PATH} startvm "{name}" --type {mode}', shell=True)
    except Exception as e:
        print("Ошибка при запуске ВМ:", e)
        return
    print(f"ВМ '{name}' запущена в режиме {mode}.")
    
    # Проверка свободного порта
    host_ip = get_host_ip()
    vrde_port = get_vrde_port(name)
    if vrde_port == "Не определён":
        if connection_protocol == "SSH":
            print("Не удалось получить VRDP порт. По умолчанию используйте порт 22.")
            vrde_port = "22"
        else:
            print("Не удалось получить VRDP порт. По умолчанию используйте порт 3389.")
            vrde_port = "3389"
    
    # Пытаемся найти свободный порт
    while True:
        try:
            vrde_port_int = int(vrde_port)
            if not (0 <= vrde_port_int <= 65535):
                raise ValueError(f"Порт {vrde_port} не входит в допустимый диапазон.")
        except ValueError:
            if connection_protocol == "SSH":
                print(f"Неверное значение порта {vrde_port}. Устанавливаем порт по умолчанию (22).")
                vrde_port = "22"
            else:
                print(f"Неверное значение порта {vrde_port}. Устанавливаем порт по умолчанию (3389).")
                vrde_port = "3389"
            continue

        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        result = sock.connect_ex((host_ip, vrde_port_int))
        if result != 0:
            break  # Порт свободен
        sock.close()
        vrde_port = str(vrde_port_int + 1)
        print(f"Порт {vrde_port} занят, пытаемся следующий...")

    print(f"Подключайтесь к ВМ по адресу {host_ip}:{vrde_port} через {connection_protocol}-клиент.")

# Функция завершения работы ВМ (ACPI shutdown)
def shutdown_vm(name):
    vm_data = load_vm_data()
    if name not in vm_data:
        print("ВМ не найдена!")
        return
    try:
        subprocess.run(f'{VBOXMANAGE_PATH} controlvm "{name}" acpipowerbutton', shell=True, check=True)
    except subprocess.CalledProcessError as e:
        print("Ошибка при отправке команды ACPI shutdown:", e)
        return
    print(f"Команда ACPI shutdown отправлена для ВМ '{name}'.")
    
    wait_seconds = 10
    print(f"Ожидание {wait_seconds} секунд для завершения работы ВМ...")
    time.sleep(wait_seconds)
    result = subprocess.run(f'{VBOXMANAGE_PATH} showvminfo "{name}" --machinereadable', 
                            shell=True, capture_output=True, text=True)
    if "VMState=\"running\"" in result.stdout:
        print("ВМ всё ещё работает. Возможно, гостевая ОС не обработала команду ACPI shutdown.")
    else:
        print("ВМ успешно завершила работу.")

# Функция принудительного завершения работы ВМ (poweroff)
def force_shutdown_vm(name):
    vm_data = load_vm_data()
    if name not in vm_data:
        print("ВМ не найдена!")
        return
    try:
        subprocess.run(f'{VBOXMANAGE_PATH} controlvm "{name}" poweroff', shell=True, check=True)
    except subprocess.CalledProcessError as e:
        print(f"Ошибка при отправке команды poweroff для ВМ '{name}':", e)
        return
    print(f"Команда poweroff отправлена для ВМ '{name}'.")

# Функция перезагрузки ВМ
def reboot_vm(name):
    vm_data = load_vm_data()
    if name not in vm_data:
        print("ВМ не найдена!")
        return
    try:
        subprocess.run(f'{VBOXMANAGE_PATH} controlvm "{name}" reset', shell=True, check=True)
    except subprocess.CalledProcessError as e:
        print("Ошибка при отправке команды перезагрузки:", e)
        return
    print(f"Команда перезагрузки отправлена для ВМ '{name}'.")

# Функция удаления ВМ
def delete_vm(name):
    vm_data = load_vm_data()
    if name not in vm_data:
        print("ВМ не найдена!")
        return
    try:
        subprocess.run(f'{VBOXMANAGE_PATH} unregistervm "{name}" --delete-all', shell=True, check=True)
    except subprocess.CalledProcessError as e:
        print("Ошибка при удалении ВМ:", e)
        return
    del vm_data[name]
    save_vm_data(vm_data)
    print(f"ВМ '{name}' удалена.")

# Функция отображения списка ВМ
def list_vms():
    vm_data = load_vm_data()
    if not vm_data:
        print("Нет виртуальных машин.")
        return
    print("Список ВМ:")
    for name, vm in vm_data.items():
        print(f"Имя: {name}, ISO: {vm['iso']}, RAM: {vm['ram']}MB, CPU: {vm['cpus']} ядер, Диск: {vm['disk_size']}GB")

# Функция отсоединения (снятия) ISO-диска с ВМ
def detach_medium(vm_name):
    try:
        subprocess.run(f'{VBOXMANAGE_PATH} storageattach "{vm_name}" --storagectl "IDE Controller" --port 0 --device 0 --type dvddrive --medium emptydrive', 
                       shell=True, check=True)
        print(f"ISO-диск отсоединён от ВМ '{vm_name}'.")
    except subprocess.CalledProcessError as e:
        print(f"Ошибка при отсоединении ISO-диска от ВМ '{vm_name}':", e)

# Функция сохранения (создания) снимка ВМ
def take_snapshot(vm_name, snapshot_name):
    vm_data = load_vm_data()
    if vm_name not in vm_data:
        print("ВМ не найдена!")
        return
    try:
        subprocess.run(f'{VBOXMANAGE_PATH} snapshot "{vm_name}" take "{snapshot_name}"', shell=True, check=True)
        print(f"Снимок '{snapshot_name}' успешно сохранён для ВМ '{vm_name}'.")
    except subprocess.CalledProcessError as e:
        print(f"Ошибка при сохранении снимка для ВМ '{vm_name}':", e)

# Функция загрузки (восстановления) снимка ВМ
def restore_snapshot(vm_name, snapshot_name):
    vm_data = load_vm_data()
    if vm_name not in vm_data:
        print("ВМ не найдена!")
        return
    try:
        subprocess.run(f'{VBOXMANAGE_PATH} snapshot "{vm_name}" restore "{snapshot_name}"', shell=True, check=True)
        print(f"Снимок '{snapshot_name}' успешно загружен для ВМ '{vm_name}'.")
    except subprocess.CalledProcessError as e:
        print(f"Ошибка при загрузке снимка для ВМ '{vm_name}':", e)

# А вот исправленная функция подключения USB-планшета
def connect_usb_tablet(vm_name):
    vendor_id = "046D"  # Замените на фактический Vendor ID
    product_id = "C542"  # Замените на фактический Product ID
    usb_device_name = "Wireless Receiver"  # Имя вашего устройства

    # Добавляем USB-фильтр
    try:
        subprocess.run(f'{VBOXMANAGE_PATH} usbfilter add 0 --name "{usb_device_name}" --target "{vm_name}" --active=yes --vendorid {vendor_id} --productid {product_id}', shell=True, check=True)
        print(f"USB-фильтр '{usb_device_name}' добавлен к ВМ '{vm_name}'.")
        
    except subprocess.CalledProcessError as e:
        print(f"Ошибка при подключении USB-устройства '{usb_device_name}' к ВМ '{vm_name}':", e)

# Основной блок программы
def main():
    while True:
        print("\n1. Создать ВМ")
        print("2. Запустить ВМ")
        print("3. Список ВМ")
        print("4. Завершить работу ВМ (ACPI shutdown)")
        print("5. Перезагрузить ВМ")
        print("6. Удалить ВМ")
        print("7. Выход")
        print("8. Отсоединить ISO-диск (detach medium)")
        print("9. Принудительное завершение работы ВМ (poweroff)")
        print("10. Сохранить снимок")
        print("11. Загрузить снимок")
        print("12. Подключить USB-планшет(реальная мышка)")  # Нова опция для подключения планшета
        choice = input("Выберите действие: ").strip()

        # Остальной код остается неизменным
        
        if choice == "12":
            vm_name = input("Введите имя ВМ для подключения USB-планшета: ").strip()
            connect_usb_tablet(vm_name)  # Подключаем USB-планшет
            
        elif choice == "1":
            name = input("Имя ВМ: ").strip()
            iso = input("Имя ISO-файла (файл должен быть в папке 'ISO'): ").strip()
            try:
                ram = int(input("ОЗУ (МБ): ").strip())
                cpus = int(input("Ядра CPU: ").strip())
                disk_size = int(input("Размер диска (ГБ): ").strip())
            except ValueError:
                print("Некорректный ввод числовых параметров.")
                continue
            create_vm(name, iso, ram, cpus, disk_size)
        elif choice == "2":
            name = input("Имя ВМ для запуска: ").strip()
            mode = input("Режим запуска (headless/gui): ").strip().lower()
            if mode not in ["headless", "gui"]:
                mode = "headless"
            run_vm(name, mode)
        elif choice == "3":
            list_vms()
        elif choice == "4":
            vm_name = input("Имя ВМ для завершения работы (ACPI shutdown): ").strip()
            shutdown_vm(vm_name)
        elif choice == "5":
            vm_name = input("Имя ВМ для перезагрузки: ").strip()
            reboot_vm(vm_name)
        elif choice == "6":
            vm_name = input("Имя ВМ для удаления: ").strip()
            delete_vm(vm_name)
        elif choice == "7":
            print("Выход из программы.")
            break
        elif choice == "8":
            vm_name = input("Введите имя ВМ для отсоединения ISO-диска: ").strip()
            detach_medium(vm_name)
        elif choice == "9":
            vm_name = input("Введите имя ВМ для принудительного завершения работы (poweroff): ").strip()
            force_shutdown_vm(vm_name)
        elif choice == "10":
            vm_name = input("Имя ВМ для сохранения снимка: ").strip()
            snapshot_name = input("Имя снимка: ").strip()
            take_snapshot(vm_name, snapshot_name)
        elif choice == "11":
            vm_name = input("Имя ВМ для загрузки снимка: ").strip()
            snapshot_name = input("Имя снимка для загрузки: ").strip()
            restore_snapshot(vm_name, snapshot_name)
        else:
            print("Некорректный ввод.")

if __name__ == "__main__":
    main()