[CmdletBinding()]
param(
    [ValidateSet('ru', 'uk', 'en')]
    [string]$Language = 'ru',
    [string]$AdbTarget = '',
    [switch]$VerifyOnly,
    [switch]$FastbootSelfTest,
    [switch]$AdbWaitSelfTest,
    [switch]$MagiskStageSelfTest,
    [switch]$RootRecoverySelfTest,
    [switch]$UnlockGuardSelfTest
)

$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$kitRoot = Split-Path -Parent $PSScriptRoot
$adb = Join-Path $kitRoot 'platform-tools\adb.exe'
$fastboot = Join-Path $kitRoot 'platform-tools\fastboot.exe'
$expectedDevice = 'ross'
$expectedIncremental = '250926144031'
$expectedBootloader = 'kylin'
$script:AdbSerial = ''

$messages = @{
    ru = @{
        title = 'TECHNOZON Modifier X11 - автоматическая установка'
        warning = 'ТОЛЬКО чистый NEXON X11, device ross, сборка 250926144031. Данные могут быть удалены при разблокировке.'
        confirmX11 = 'Введите X11 и нажмите ENTER, чтобы подтвердить модель'
        adbTarget = 'Адрес ADB (например 192.168.32.78:1325) или оставьте пустым для USB'
        cableEnter = 'Когда USB A-to-A и питание подключены, нажмите ENTER'
        verify = 'Шаг 1/10. Проверяю целостность комплекта.'
        connect = 'Шаг 2/10. Подключаюсь к Android и проверяю точную модель.'
        cable = 'Подключите USB A-to-A к заднему USB-порту приставки и к ПК. Питание не отключать.'
        alreadyFastboot = 'Приставка уже находится в fastboot. Перезагружаю её в Android для обязательной проверки модели и прошивки.'
        multipleFastboot = 'Подключено несколько fastboot-устройств. Отключите посторонние устройства и запустите установщик снова.'
        unlock = 'Загрузчик заблокирован. Разблокировка удалит пользовательские данные. Введите UNLOCK для продолжения.'
        unlockPrompt = 'ВВЕДИТЕ UNLOCK И НАЖМИТЕ ENTER'
        oemUnlockDisabled = 'OEM-разблокировка запрещена в Android. На приставке откройте Настройки -> Система -> Для разработчиков, включите Разблокировку загрузчика (OEM unlocking), подтвердите предупреждение и снова запустите установщик.'
        unlockRejected = 'Приставка отклонила разблокировку загрузчика. Никакие разделы не записывались. Включите OEM unlocking в параметрах разработчика, перезагрузите приставку и повторите установку.'
        unlockVerificationFailed = 'После команды разблокировки загрузчик остался заблокирован. Никакие разделы не записывались. Включите OEM unlocking в параметрах разработчика и повторите установку.'
        wrongToken = 'Введено неверно. Нужно ввести точно: {0}'
        magisk = 'Шаг 3/10. Устанавливаю Magisk Manager и заранее подготавливаю безопасное окружение root.'
        flash = 'Шаг 4/10. Восстанавливаю стоковый неактивный init_boot и прошиваю Magisk в активный.'
        env = 'Шаг 5/10. Проверяю запуск Magisk и постоянное разрешение локального USB ADB.'
        magiskReboot = 'Окружение Magisk создано. Перезагружаю приставку для штатного запуска Magisk daemon.'
        apps = 'Шаг 6/10. Устанавливаю production-модификатор TECHNOZON.'
        profile = 'Шаг 7/10. Применяю профиль X11: AISR, громкость, Katniss и Google TTS.'
        modules = 'Шаг 8/10. Устанавливаю приложения STESH, TV SMB Manager, ускорение, двойной HOME, анимацию, Light Sleep, hardening, удаление рекламы и очистку каналов.'
        final = 'Шаг 9/10. Всё подготовлено. После перезагрузки Aptoide и небезопасный стоковый X-plore исчезнут, неизвестные источники и TCP ADB будут закрыты. Введите REBOOT.'
        check = 'Шаг 10/10. Проверяю, что сетевой ADB больше не отвечает.'
        done = 'Установка завершена. Для обслуживания используйте USB и Scripts\Enable_Maintenance_TCP_ADB_USB.cmd.'
        adbRecovery = 'Локальный ADB-сервер остановился. Перезапускаю его и повторяю прерванную команду.'
        rootWait = 'Ожидаю root. Если на ТВ появится запрос Magisk, выберите постоянное разрешение и нажмите Разрешить.'
        rootRecovery = 'Обнаружен Magisk без рабочего root. Автоматически возвращаю активный init_boot в проверенный сток для безопасной подготовки и повторной установки.'
    }
    uk = @{
        title = 'TECHNOZON Modifier X11 - автоматичне встановлення'
        warning = 'ЛИШЕ чистий NEXON X11, device ross, збірка 250926144031. Під час розблокування дані можуть бути видалені.'
        confirmX11 = 'Введіть X11 і натисніть ENTER, щоб підтвердити модель'
        adbTarget = 'Адреса ADB (наприклад 192.168.32.78:1325) або залиште порожньою для USB'
        cableEnter = 'Коли USB A-to-A та живлення підключені, натисніть ENTER'
        verify = 'Крок 1/10. Перевіряю цілісність комплекту.'
        connect = 'Крок 2/10. Підключаюся до Android і перевіряю точну модель.'
        cable = 'Підключіть USB A-to-A до заднього USB-порту приставки та ПК. Живлення не вимикати.'
        alreadyFastboot = 'Приставка вже перебуває у fastboot. Перезавантажую її в Android для обов''язкової перевірки моделі та прошивки.'
        multipleFastboot = 'Підключено кілька fastboot-пристроїв. Від''єднайте сторонні пристрої та запустіть інсталятор знову.'
        unlock = 'Завантажувач заблоковано. Розблокування видалить дані. Введіть UNLOCK для продовження.'
        unlockPrompt = 'ВВЕДІТЬ UNLOCK І НАТИСНІТЬ ENTER'
        oemUnlockDisabled = 'OEM-розблокування заборонене в Android. На приставці відкрийте Налаштування -> Система -> Для розробників, увімкніть Розблокування завантажувача (OEM unlocking), підтвердьте попередження та знову запустіть інсталятор.'
        unlockRejected = 'Приставка відхилила розблокування завантажувача. Жоден розділ не записувався. Увімкніть OEM unlocking у параметрах розробника, перезавантажте приставку та повторіть встановлення.'
        unlockVerificationFailed = 'Після команди розблокування завантажувач залишився заблокованим. Жоден розділ не записувався. Увімкніть OEM unlocking у параметрах розробника та повторіть встановлення.'
        wrongToken = 'Введено неправильно. Потрібно ввести точно: {0}'
        magisk = 'Крок 3/10. Встановлюю Magisk Manager і заздалегідь готую безпечне середовище root.'
        flash = 'Крок 4/10. Відновлюю стоковий неактивний init_boot і встановлюю Magisk в активний.'
        env = 'Крок 5/10. Перевіряю запуск Magisk і постійний дозвіл локального USB ADB.'
        magiskReboot = 'Середовище Magisk створено. Перезавантажую приставку для штатного запуску Magisk daemon.'
        apps = 'Крок 6/10. Встановлюю production-модифікатор TECHNOZON.'
        profile = 'Крок 7/10. Застосовую профіль X11: AISR, гучність, Katniss і Google TTS.'
        modules = 'Крок 8/10. Встановлюю програми STESH, TV SMB Manager, прискорення, подвійний HOME, анімацію, Light Sleep, hardening, видалення реклами й очищення каналів.'
        final = 'Крок 9/10. Усе готово. Після перезапуску Aptoide і небезпечний стоковий X-plore зникнуть, невідомі джерела і TCP ADB будуть закриті. Введіть REBOOT.'
        check = 'Крок 10/10. Перевіряю, що мережевий ADB більше не відповідає.'
        done = 'Встановлення завершено. Для обслуговування використовуйте USB і Scripts\Enable_Maintenance_TCP_ADB_USB.cmd.'
        adbRecovery = 'Локальний ADB-сервер зупинився. Перезапускаю його та повторюю перервану команду.'
        rootWait = 'Очікую root. Якщо на ТВ з''явиться запит Magisk, виберіть постійний дозвіл і натисніть Дозволити.'
        rootRecovery = 'Виявлено Magisk без робочого root. Автоматично повертаю активний init_boot у перевірений сток для безпечної підготовки та повторного встановлення.'
    }
    en = @{
        title = 'TECHNOZON Modifier X11 - automated installer'
        warning = 'ONLY for clean NEXON X11, device ross, build 250926144031. Unlocking may erase user data.'
        confirmX11 = 'Type X11 and press ENTER to confirm the model'
        adbTarget = 'ADB address (for example 192.168.32.78:1325), or leave empty for USB'
        cableEnter = 'When USB A-to-A and power are connected, press ENTER'
        verify = 'Step 1/10. Verifying kit integrity.'
        connect = 'Step 2/10. Connecting to Android and validating the exact model.'
        cable = 'Connect USB A-to-A to the rear USB port and the PC. Do not disconnect power.'
        alreadyFastboot = 'The box is already in fastboot. Rebooting it to Android for mandatory model and firmware verification.'
        multipleFastboot = 'More than one fastboot device is connected. Disconnect unrelated devices and run the installer again.'
        unlock = 'The bootloader is locked. Unlocking erases user data. Type UNLOCK to continue.'
        unlockPrompt = 'TYPE UNLOCK AND PRESS ENTER'
        oemUnlockDisabled = 'OEM unlocking is disabled in Android. On the box open Settings -> System -> Developer options, enable OEM unlocking, confirm the warning, and run the installer again.'
        unlockRejected = 'The box rejected the bootloader unlock. No partition was written. Enable OEM unlocking in Developer options, reboot the box, and run the installer again.'
        unlockVerificationFailed = 'The bootloader remained locked after the unlock command. No partition was written. Enable OEM unlocking in Developer options and run the installer again.'
        wrongToken = 'Incorrect input. Enter exactly: {0}'
        magisk = 'Step 3/10. Installing Magisk Manager and pre-staging the safe root environment.'
        flash = 'Step 4/10. Restoring the inactive stock init_boot and flashing Magisk to the active one.'
        env = 'Step 5/10. Verifying Magisk and the persistent local USB ADB grant.'
        magiskReboot = 'Magisk environment is ready. Rebooting the device to start the Magisk daemon normally.'
        apps = 'Step 6/10. Installing the production TECHNOZON modifier.'
        profile = 'Step 7/10. Applying the X11 profile: AISR, local volume, Katniss, and Google TTS.'
        modules = 'Step 8/10. Installing STESH apps, TV SMB Manager, performance, double-HOME, boot animation, Light Sleep, hardening, ad removal, and channel cleanup.'
        final = 'Step 9/10. Ready. After reboot Aptoide and the untrusted stock X-plore mod are removed, unknown sources and TCP ADB are closed. Type REBOOT.'
        check = 'Step 10/10. Verifying that network ADB no longer responds.'
        done = 'Installation complete. For maintenance use USB and Scripts\Enable_Maintenance_TCP_ADB_USB.cmd.'
        adbRecovery = 'The local ADB server stopped. Restarting it and retrying the interrupted command.'
        rootWait = 'Waiting for root. If Magisk prompts on the TV, select the permanent option and press Grant.'
        rootRecovery = 'Magisk is active without working root. Restoring the active init_boot to verified stock for safe preparation and automatic reinstall.'
    }
}

function Show-Step([string]$key) {
    Write-Host ''
    Write-Host ('=' * 78) -ForegroundColor DarkCyan
    Write-Host $messages[$Language][$key] -ForegroundColor Cyan
    Write-Host ('=' * 78) -ForegroundColor DarkCyan
}

function Read-ExactToken([string]$prompt, [string]$expected) {
    while ($true) {
        $value = Read-Host $prompt
        if ([string]::Equals($value.Trim(), $expected, [StringComparison]::OrdinalIgnoreCase)) {
            return
        }
        Write-Host ($messages[$Language].wrongToken -f $expected) -ForegroundColor Red
    }
}

function ConvertTo-NativeArgument([AllowEmptyString()][string]$value) {
    if ([string]::IsNullOrEmpty($value)) { return '""' }
    if ($value -notmatch '[\s"]') { return $value }

    $quoted = '"'
    $backslashes = 0
    foreach ($character in $value.ToCharArray()) {
        if ($character -eq '\') {
            $backslashes++
            continue
        }
        if ($character -eq '"') {
            $quoted += ('\' * (($backslashes * 2) + 1)) + '"'
            $backslashes = 0
            continue
        }
        if ($backslashes -gt 0) {
            $quoted += '\' * $backslashes
            $backslashes = 0
        }
        $quoted += $character
    }
    if ($backslashes -gt 0) { $quoted += '\' * ($backslashes * 2) }
    return $quoted + '"'
}

function Invoke-NativeCapture([string]$file, [string[]]$arguments = @()) {
    $startInfo = [Diagnostics.ProcessStartInfo]::new()
    $startInfo.FileName = $file
    $startInfo.Arguments = (($arguments | ForEach-Object { ConvertTo-NativeArgument $_ }) -join ' ')
    $startInfo.UseShellExecute = $false
    $startInfo.CreateNoWindow = $true
    $startInfo.RedirectStandardOutput = $true
    $startInfo.RedirectStandardError = $true

    $process = [Diagnostics.Process]::new()
    $process.StartInfo = $startInfo
    if (-not $process.Start()) { throw "Unable to start $file" }
    $stdoutTask = $process.StandardOutput.ReadToEndAsync()
    $stderrTask = $process.StandardError.ReadToEndAsync()
    $process.WaitForExit()
    $stdout = $stdoutTask.Result
    $stderr = $stderrTask.Result
    $exitCode = $process.ExitCode
    $process.Dispose()

    $output = @()
    if (-not [string]::IsNullOrWhiteSpace($stdout)) {
        $output += @($stdout.TrimEnd() -split "`r?`n")
    }
    if (-not [string]::IsNullOrWhiteSpace($stderr)) {
        $output += @($stderr.TrimEnd() -split "`r?`n")
    }
    return [pscustomobject]@{ ExitCode = [int]$exitCode; Output = [object[]]$output }
}

function Restart-AdbServerAndWait([int]$timeoutSeconds = 120) {
    Invoke-NativeCapture $adb @('kill-server') | Out-Null
    Start-Sleep -Seconds 2
    $deadline = (Get-Date).AddSeconds($timeoutSeconds)
    do {
        $startResult = Invoke-NativeCapture $adb @('start-server')
        if ($startResult.ExitCode -eq 0) {
            $devicesResult = Invoke-NativeCapture $adb @('devices')
            if ($devicesResult.ExitCode -eq 0) {
                $online = @($devicesResult.Output | Select-Object -Skip 1 | ForEach-Object {
                    if ($_ -match '^(\S+)\s+device$') { $Matches[1] }
                })
                if ($online -contains $script:AdbSerial) { return }
                $usb = @($online | Where-Object { $_ -notmatch ':' })
                if ($usb.Count -eq 1) {
                    $script:AdbSerial = $usb[0]
                    return
                }
                if ($online.Count -eq 1) {
                    $script:AdbSerial = $online[0]
                    return
                }
            }
        }
        Start-Sleep -Seconds 3
    } while ((Get-Date) -lt $deadline)
    throw 'ADB server did not recover or the Android device did not return. Keep USB and power connected, then run the installer again.'
}

function Invoke-AdbCapture([string[]]$arguments = @(), [switch]$NoCommandRetry) {
    $result = Invoke-NativeCapture $adb $arguments
    if ($result.ExitCode -eq 0) { return $result }

    $failureText = (@($result.Output) -join "`n")
    $transportExit = ($result.ExitCode -eq 255)
    if (-not $transportExit -and $failureText -notmatch '(?i)daemon still not running|cannot connect to daemon|tcp:5037|127\.0\.0\.1:5037|10060|device .* not found|no devices/emulators found|device offline|transport error|connection reset|closed') {
        return $result
    }

    Write-Host $messages[$Language].adbRecovery -ForegroundColor Yellow
    Restart-AdbServerAndWait 120
    if ($NoCommandRetry) { return $result }
    return Invoke-NativeCapture $adb $arguments
}

function Invoke-External([string]$file, [string[]]$arguments, [string]$failure) {
    $result = Invoke-NativeCapture $file $arguments
    $result.Output | ForEach-Object { Write-Host $_ }
    if ($result.ExitCode -ne 0) {
        throw "$failure (exit $($result.ExitCode))"
    }
    return @($result.Output)
}

function Verify-Manifest {
    $manifest = Join-Path $kitRoot 'SHA256SUMS.txt'
    if (-not (Test-Path -LiteralPath $manifest)) {
        throw "Missing SHA256SUMS.txt"
    }
    foreach ($line in Get-Content -LiteralPath $manifest) {
        if ($line -notmatch '^([0-9A-Fa-f]{64})\s+\*(.+)$') { continue }
        $expected = $matches[1].ToUpperInvariant()
        $relative = $matches[2].Replace('/', '\')
        $path = Join-Path $kitRoot $relative
        if (-not (Test-Path -LiteralPath $path)) { throw "Missing file: $relative" }
        $actual = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash
        if ($actual -ne $expected) { throw "SHA256 mismatch: $relative" }
    }
}

function Get-OnlineAdbSerial([string]$preferred = '') {
    if ($preferred) {
        $stateResult = Invoke-AdbCapture @('-s', $preferred, 'get-state')
        $state = @($stateResult.Output | Where-Object { $_ -eq 'device' } | Select-Object -First 1)
        if ($state -eq 'device') { return $preferred }
    }
    $devicesResult = Invoke-AdbCapture @('devices')
    $devices = @($devicesResult.Output | Select-Object -Skip 1 | ForEach-Object {
        if ($_ -match '^(\S+)\s+device$') { $matches[1] }
    })
    if ($devices.Count -eq 1) { return $devices[0] }
    if ($devices.Count -gt 1) {
        $usbDevices = @($devices | Where-Object { $_ -notmatch ':' })
        if ($usbDevices.Count -eq 1) { return $usbDevices[0] }
        throw "More than one USB ADB device is online. USB=$($usbDevices.Count), all=$($devices.Count). Disconnect unrelated USB devices or specify ADB target."
    }
    return ''
}

function Wait-Adb([string]$preferred = '', [int]$timeoutSeconds = 240) {
    $deadline = (Get-Date).AddSeconds($timeoutSeconds)
    do {
        $serial = Get-OnlineAdbSerial $preferred
        if ($serial) { return $serial }
        Start-Sleep -Seconds 3
    } while ((Get-Date) -lt $deadline)
    throw 'Android did not return to ADB. Keep USB and power connected, then use the recovery guide.'
}

function Wait-AndroidReady([int]$timeoutSeconds = 300) {
    $deadline = (Get-Date).AddSeconds($timeoutSeconds)
    do {
        $boot = Invoke-AdbCapture @('-s', $script:AdbSerial, 'shell', 'getprop', 'sys.boot_completed')
        $bootCompleted = ($boot.ExitCode -eq 0 -and ((@($boot.Output) -join '').Trim() -eq '1'))
        if ($bootCompleted) {
            $package = Invoke-AdbCapture @('-s', $script:AdbSerial, 'shell', 'cmd', 'package', 'list', 'packages', 'android')
            if ($package.ExitCode -eq 0 -and ((@($package.Output) -join "`n") -match 'package:android')) {
                return
            }
        }
        Start-Sleep -Seconds 3
    } while ((Get-Date) -lt $deadline)
    throw 'Android booted to ADB, but Package Manager did not become ready. Keep USB and power connected.'
}

function Install-FastbootDriverPackage {
    $driverInstaller = Join-Path $kitRoot 'Scripts\Install_Fastboot_Driver.ps1'
    if (-not (Test-Path -LiteralPath $driverInstaller -PathType Leaf)) {
        throw "Fastboot driver installer is missing: $driverInstaller"
    }

    Write-Host 'Preparing the signed Android Bootloader Interface driver...' -ForegroundColor Yellow
    $result = Invoke-NativeCapture 'powershell.exe' @(
        '-NoProfile',
        '-ExecutionPolicy', 'Bypass',
        '-File', $driverInstaller
    )
    $result.Output | ForEach-Object { Write-Host $_ }
    if ($result.ExitCode -ne 0) {
        throw "Fastboot driver installation failed (exit $($result.ExitCode))."
    }
}

function Wait-Fastboot([int]$timeoutSeconds = 90) {
    $deadline = (Get-Date).AddSeconds($timeoutSeconds)
    do {
        $result = Invoke-NativeCapture $fastboot @('devices')
        $line = [string](@($result.Output | Select-Object -First 1) -join '')
        if ($line -match '^(\S+)\s+fastboot\s*$') { return [string]$Matches[1] }
        Start-Sleep -Seconds 2
    } while ((Get-Date) -lt $deadline)
    $bootloader = Get-PnpDevice -PresentOnly -ErrorAction SilentlyContinue | Where-Object {
        $_.InstanceId -match '^USB\\VID_18D1&PID_4E40' -or
        $_.FriendlyName -match 'Android Bootloader Interface|Android.*Fastboot'
    } | Select-Object -First 1
    if ($bootloader) {
        throw "Windows sees $($bootloader.FriendlyName) ($($bootloader.InstanceId)), but fastboot.exe cannot open it. Reconnect the USB A-to-A cable and check the driver in Device Manager."
    }
    throw 'No USB fastboot interface detected. Windows did not enumerate USB\VID_18D1&PID_4E40; reconnect the rear-port USB A-to-A cable.'
}

function Resume-AndroidIfAlreadyInFastboot {
    if (Get-OnlineAdbSerial '') { return }

    $result = Invoke-NativeCapture $fastboot @('devices')
    $devices = @($result.Output | ForEach-Object {
        if ($_ -match '^(\S+)\s+fastboot\s*$') { $Matches[1] }
    })
    if ($devices.Count -eq 0) { return }
    if ($devices.Count -gt 1) {
        throw $messages[$Language].multipleFastboot
    }

    Write-Host $messages[$Language].alreadyFastboot -ForegroundColor Yellow
    Assert-FastbootProduct
    Invoke-External $fastboot @('reboot') 'Fastboot reboot to Android failed' | Out-Null
    $script:AdbSerial = Wait-Adb '' 300
    Wait-AndroidReady 300
    Write-Host "Android ADB is online: $script:AdbSerial" -ForegroundColor Green
}

function Adb([string[]]$arguments, [string]$failure = 'ADB command failed') {
    $result = Invoke-AdbCapture (@('-s', $script:AdbSerial) + $arguments)
    $result.Output | ForEach-Object { Write-Host $_ }
    if ($result.ExitCode -ne 0) {
        throw "$failure (exit $($result.ExitCode))"
    }
    return @($result.Output)
}

function Get-AdbScalar([string[]]$arguments) {
    $result = Invoke-AdbCapture (@('-s', $script:AdbSerial) + $arguments)
    if ($result.ExitCode -ne 0) { return '' }
    return ((@($result.Output) -join '').Trim())
}

function Assert-OemUnlockAllowed {
    $propertyValue = Get-AdbScalar @('shell', 'getprop', 'sys.oem_unlock_allowed')
    $settingValue = Get-AdbScalar @('shell', 'settings', 'get', 'global', 'oem_unlock_allowed')
    $values = @($propertyValue, $settingValue) | Where-Object { $_ -in @('0', '1') }
    if ($values -contains '1') { return }
    if ($values -contains '0') {
        throw $messages[$Language].oemUnlockDisabled
    }
}

function Test-FastbootUnlockAccepted([int]$exitCode, [string]$outputText) {
    if ($exitCode -ne 0) { return $false }
    return ($outputText -notmatch "(?i)can't set unlock|cannot set unlock|unlock.*(?:denied|not allowed|failed)|flash command is denied|FAILED")
}

function Invoke-FastbootUnlock([string[]]$arguments, [string]$label) {
    $result = Invoke-NativeCapture $fastboot $arguments
    $result.Output | ForEach-Object { Write-Host $_ }
    $text = (@($result.Output) -join "`n")
    if (-not (Test-FastbootUnlockAccepted $result.ExitCode $text)) {
        throw "$($messages[$Language].unlockRejected) [$label]"
    }
}

function Assert-AndroidBootloaderUnlocked {
    $currentFlashLocked = Read-Prop 'ro.boot.flash.locked'
    $currentVbmetaState = Read-Prop 'ro.boot.vbmeta.device_state'
    Write-Host "unlock readback: flash.locked=$currentFlashLocked vbmeta.device_state=$currentVbmetaState"
    if ($currentFlashLocked -ne '0' -or $currentVbmetaState -ne 'unlocked') {
        throw $messages[$Language].unlockVerificationFailed
    }
}

function Resolve-RootBackend {
    if ($script:RootBackend) { return }

    $candidates = @(
        @{ Name = 'Magisk /debug_ramdisk/su'; Prefix = @('shell', '/debug_ramdisk/su', '-c'); Probe = @('shell', '/debug_ramdisk/su', '-c', 'id') },
        @{ Name = 'stock su 0'; Prefix = @('shell', 'su', '0', 'sh', '-c'); Probe = @('shell', 'su', '0', 'sh', '-c', 'id') },
        @{ Name = 'stock su -c'; Prefix = @('shell', 'su', '-c'); Probe = @('shell', 'su', '-c', 'id') },
        @{ Name = '/system/xbin/su 0'; Prefix = @('shell', '/system/xbin/su', '0', 'sh', '-c'); Probe = @('shell', '/system/xbin/su', '0', 'sh', '-c', 'id') },
        @{ Name = '/system/bin/su 0'; Prefix = @('shell', '/system/bin/su', '0', 'sh', '-c'); Probe = @('shell', '/system/bin/su', '0', 'sh', '-c', 'id') }
    )
    foreach ($candidate in $candidates) {
        $result = Invoke-AdbCapture (@('-s', $script:AdbSerial) + $candidate.Probe)
        $text = ((@($result.Output) -join "`n").Trim())
        if ($result.ExitCode -eq 0 -and $text -match 'uid=0(?:\D|$)') {
            $script:RootBackend = @($candidate.Prefix)
            Write-Host "Root backend: $($candidate.Name)" -ForegroundColor Green
            return
        }
    }
    throw 'No working root backend found after reboot. The active init_boot was flashed, but no root command is available.'
}

function Wait-RootBackend([int]$timeoutSeconds = 120) {
    Write-Host $messages[$Language].rootWait -ForegroundColor Yellow
    $deadline = (Get-Date).AddSeconds($timeoutSeconds)
    do {
        $script:RootBackend = $null
        try {
            Resolve-RootBackend
            return
        } catch {
            Start-Sleep -Seconds 3
        }
    } while ((Get-Date) -lt $deadline)

    throw 'Magisk is loaded, but the persistent root policy for local USB ADB is unavailable. Re-run the installer; init_boot will not be flashed again.'
}

function ConvertTo-ShellSingleQuoted([string]$command) {
    $quote = [string][char]39
    $doubleQuote = [string][char]34
    $escapedQuote = $quote + $doubleQuote + $quote + $doubleQuote + $quote
    return $quote + $command.Replace($quote, $escapedQuote) + $quote
}

function Root([string]$command, [string]$failure = 'Magisk root command failed') {
    Resolve-RootBackend
    $quotedCommand = ConvertTo-ShellSingleQuoted $command
    return Adb (@($script:RootBackend) + @($quotedCommand)) $failure
}

function Invoke-RootOnce([string]$command) {
    Resolve-RootBackend
    $quotedCommand = ConvertTo-ShellSingleQuoted $command
    return Invoke-AdbCapture (@('-s', $script:AdbSerial) + @($script:RootBackend) + @($quotedCommand)) -NoCommandRetry
}

function Complete-MagiskEnvironment {
    $status = '/data/local/tmp/magisk-env.result'
    $log = '/data/local/tmp/magisk-env.log'
    $command = 'sh /data/local/tmp/magisk-env/start_magisk_job.sh'
    $result = Invoke-RootOnce $command
    @($result.Output) | ForEach-Object { Write-Host $_ }
    if ($result.ExitCode -ne 0) {
        $failureText = (@($result.Output) -join "`n")
        if ($failureText -notmatch '(?i)daemon still not running|cannot connect to daemon|tcp:5037|127\.0\.0\.1:5037|10060') {
            throw "Unable to start Magisk environment setup (exit $($result.ExitCode))"
        }
    }

    $deadline = (Get-Date).AddSeconds(180)
    do {
        $probe = Invoke-AdbCapture @('-s', $script:AdbSerial, 'shell', 'cat', $status)
        if ($probe.ExitCode -eq 0) {
            $code = ((@($probe.Output) -join '').Trim())
            if ($code -eq '0') {
                $script:MagiskBinary = '/data/adb/magisk/magisk'
                Write-Host 'Magisk environment setup readback: OK' -ForegroundColor Green
                return
            }
            $logResult = Invoke-AdbCapture @('-s', $script:AdbSerial, 'shell', 'cat', $log)
            @($logResult.Output) | ForEach-Object { Write-Host $_ }
            throw "Magisk environment setup failed on the device (exit $code)"
        }
        Start-Sleep -Seconds 3
    } while ((Get-Date) -lt $deadline)

    throw 'Timed out waiting for Magisk environment setup result on the device.'
}

function Stage-MagiskEnvironment {
    Adb @('shell', 'rm', '-rf', '/data/local/tmp/magisk-env', '/data/local/tmp/complete_magisk_env.sh') | Out-Null
    Adb @('push', (Join-Path $kitRoot 'Environment'), '/data/local/tmp/magisk-env') 'Failed to push Magisk environment' | Out-Null
    Adb @('push', (Join-Path $kitRoot 'Environment\complete_magisk_env.sh'), '/data/local/tmp/complete_magisk_env.sh') | Out-Null
    Complete-MagiskEnvironment
}

function Read-Prop([string]$name) {
    $result = Invoke-AdbCapture @('-s', $script:AdbSerial, 'shell', 'getprop', $name)
    if ($result.ExitCode -ne 0) { throw "Cannot read Android property $name" }
    return ((@($result.Output) -join '').Trim())
}

function Assert-X11 {
    $device = Read-Prop 'ro.product.device'
    $incremental = Read-Prop 'ro.build.version.incremental'
    Write-Host "device=$device incremental=$incremental"
    if ($device -ne $expectedDevice -or $incremental -ne $expectedIncremental) {
        throw "Unsupported firmware. Expected device=$expectedDevice incremental=$expectedIncremental. Nothing was flashed."
    }
}

function Assert-FastbootProduct {
    $output = (Invoke-External $fastboot @('getvar', 'product') 'Cannot read fastboot product') -join "`n"
    if ($output -notmatch "product:\s*$expectedBootloader") {
        throw "Unexpected bootloader product. Expected $expectedBootloader. Nothing was flashed."
    }
}

function Get-AndroidBootSlot {
    $slot = Read-Prop 'ro.boot.slot_suffix'
    if ($slot -notin @('_a', '_b')) {
        throw "Unsupported or missing Android boot slot '$slot'."
    }
    return $slot
}

function Assert-AndroidBootSlot([string]$expectedSlot) {
    $slot = Get-AndroidBootSlot
    Write-Host "Android boot slot=$slot"
    if ($slot -ne $expectedSlot) {
        throw "Android changed boot slots unexpectedly: expected '$expectedSlot', reported '$slot'. No further changes were made."
    }
}

function Get-StockInitBootImage([string]$slot) {
    $relative = if ($slot -eq '_a') {
        'Image\stock_init_boot_a.img'
    } elseif ($slot -eq '_b') {
        'Image\stock_init_boot_b.img'
    } else {
        throw "Unsupported init_boot slot '$slot'."
    }
    $path = Join-Path $kitRoot $relative
    if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
        throw "Missing verified stock image: $path"
    }
    return $path
}

function Restore-ActiveStockForRootRecovery {
    $slot = Get-AndroidBootSlot
    $partition = "init_boot$slot"
    $stockImage = Get-StockInitBootImage $slot
    Write-Host $messages[$Language].rootRecovery -ForegroundColor Yellow
    Write-Host "Recovery target: $partition" -ForegroundColor Yellow

    Adb @('reboot', 'bootloader') | Out-Null
    Wait-Fastboot | Out-Null
    Assert-FastbootProduct
    Invoke-External $fastboot @('flash', $partition, $stockImage) "$partition stock recovery failed" | Out-Null
    Invoke-External $fastboot @('reboot') 'Fastboot recovery reboot failed' | Out-Null
    $script:AdbSerial = Wait-Adb $AdbTarget 300
    Wait-AndroidReady 300
    Assert-X11
    Assert-AndroidBootSlot $slot
    $script:RootBackend = $null
    $script:MagiskBinary = $null
}

function Test-MagiskBoot {
    foreach ($candidate in @(
        @{ Binary = '/debug_ramdisk/magisk'; Su = '/debug_ramdisk/su' },
        @{ Binary = '/sbin/magisk'; Su = '/sbin/su' }
    )) {
        $binary = $candidate.Binary
        $result = Invoke-AdbCapture @('-s', $script:AdbSerial, 'shell', $binary, '-v')
        if ($null -eq $result -or $result.ExitCode -ne 0) { continue }
        $text = ((@($result.Output) -join "`n").Trim())
        if ($text -notmatch '^30\.7:MAGISK(?::[A-Z]+)?$') { continue }

        $script:MagiskBinary = $binary
        return $true
    }
    return $false
}

function Resolve-MagiskBinary {
    if ($script:MagiskBinary) {
        return $script:MagiskBinary
    }
    Resolve-RootBackend
    foreach ($binary in @('/debug_ramdisk/magisk', '/sbin/magisk', '/data/adb/magisk/magisk', 'magisk')) {
        $probe = ConvertTo-ShellSingleQuoted "$binary -v"
        $result = Invoke-AdbCapture (@('-s', $script:AdbSerial) + @($script:RootBackend) + @($probe))
        if ($null -eq $result -or $result.ExitCode -ne 0) { continue }
        $text = ((@($result.Output) -join "`n").Trim())
        if ($text -match '^30\.7:MAGISK(?::[A-Z]+)?$') {
            $script:MagiskBinary = $binary
            Write-Host "Magisk binary: $binary" -ForegroundColor Green
            return $binary
        }
    }
    throw 'Magisk 30.7 binary was not found after environment setup.'
}

function Get-InstalledVersionCode([string]$packageName) {
    $result = Invoke-AdbCapture @('-s', $script:AdbSerial, 'shell', 'dumpsys', 'package', $packageName)
    if ($null -eq $result -or $result.ExitCode -ne 0) { return $null }
    $text = @($result.Output) -join "`n"
    if ($text -match '(?m)^\s*versionCode=(\d+)') { return [long]$matches[1] }
    return $null
}

function Install-Apk(
    [string]$relativePath,
    [string]$PackageName = '',
    [long]$ExpectedVersionCode = 0
) {
    $path = Join-Path $kitRoot $relativePath
    if ($PackageName -and $ExpectedVersionCode -gt 0) {
        $installedVersion = Get-InstalledVersionCode $PackageName
        if ($null -ne $installedVersion -and $installedVersion -ge $ExpectedVersionCode) {
            Write-Host "$PackageName versionCode=$installedVersion is already installed. Keeping it." -ForegroundColor Green
            return
        }
    }

    $lastResult = $null
    for ($attempt = 1; $attempt -le 2; $attempt++) {
        $lastResult = Invoke-AdbCapture @('-s', $script:AdbSerial, 'install', '-r', $path)
        @($lastResult.Output) | ForEach-Object { Write-Host $_ }
        if ($lastResult.ExitCode -eq 0) { break }
        if ($attempt -lt 2) {
            Write-Host 'APK installation did not complete. Reconnecting ADB and retrying once without removing the installed app.' -ForegroundColor Yellow
            Wait-Adb $script:AdbSerial 45 | Out-Null
            Start-Sleep -Seconds 2
        }
    }
    if ($null -eq $lastResult -or $lastResult.ExitCode -ne 0) {
        throw "Failed to install $relativePath. The existing application was not removed."
    }

    if ($PackageName -and $ExpectedVersionCode -gt 0) {
        $installedVersion = Get-InstalledVersionCode $PackageName
        if ($null -eq $installedVersion -or $installedVersion -lt $ExpectedVersionCode) {
            throw "$PackageName readback failed after APK installation."
        }
        Write-Host "$PackageName versionCode=$installedVersion readback: OK" -ForegroundColor Green
    }
}

function Install-MagiskModule([string]$relativePath) {
    $path = Join-Path $kitRoot $relativePath
    $name = Split-Path -Leaf $path
    $remote = "/data/local/tmp/$name"
    Adb @('push', $path, $remote) "Failed to push $name" | Out-Null
    if (-not $script:MagiskBinary) { Resolve-MagiskBinary | Out-Null }
    $installCommand = "$($script:MagiskBinary) --install-module $remote"
    Root $installCommand "Failed to install $name" | Out-Null
    Adb @('shell', 'rm', '-f', $remote) | Out-Null
}

function Initialize-TvSmbPairing {
    $local = Join-Path $kitRoot 'Scripts\provision_tvsmb_pairing.sh'
    $remote = '/data/local/tmp/provision_tvsmb_pairing.sh'
    Adb @('push', $local, $remote) 'TV SMB pairing script upload failed' | Out-Null
    $output = Root "sh $remote" 'TV SMB pairing provisioning failed'
    Adb @('shell', 'rm', '-f', $remote) | Out-Null
    if (($output -join "`n") -notmatch 'TVSMB_PAIRING_V1_OK') {
        throw 'TV SMB pairing provisioning did not pass readback verification.'
    }
}

function Test-TcpPort([string]$hostName, [int]$port, [int]$timeoutMs = 1800) {
    $client = New-Object Net.Sockets.TcpClient
    try {
        $async = $client.BeginConnect($hostName, $port, $null, $null)
        if (-not $async.AsyncWaitHandle.WaitOne($timeoutMs, $false)) { return $false }
        $client.EndConnect($async)
        return $true
    } catch {
        return $false
    } finally {
        $client.Close()
    }
}

$logDir = Join-Path $kitRoot 'InstallLogs'
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
$logPath = Join-Path $logDir ("install_{0}.log" -f (Get-Date -Format 'yyyy-MM-dd_HH-mm-ss'))
Start-Transcript -LiteralPath $logPath -Force | Out-Null

try {
    Write-Host $messages[$Language].title -ForegroundColor Cyan
    Write-Host $messages[$Language].warning -ForegroundColor Yellow

    if ($UnlockGuardSelfTest) {
        if (-not (Test-FastbootUnlockAccepted 0 "OKAY [  1.034s]`nFinished. Total time: 1.034s")) {
            throw 'UNLOCK GUARD SELF-TEST: valid success was rejected.'
        }
        if (Test-FastbootUnlockAccepted 0 "(bootloader) can't set unlock partitions`nOKAY [  1.034s]") {
            throw 'UNLOCK GUARD SELF-TEST: false OKAY was accepted.'
        }
        if (Test-FastbootUnlockAccepted 1 "FAILED (remote: flash command is denied)" ) {
            throw 'UNLOCK GUARD SELF-TEST: failed command was accepted.'
        }
        Write-Host 'UNLOCK GUARD SELF-TEST: PASS' -ForegroundColor Green
        return
    }

    if ($FastbootSelfTest) {
        Wait-Fastboot | Out-Null
        Assert-FastbootProduct
        Write-Host 'FASTBOOT SELF-TEST: product kylin OK' -ForegroundColor Green
        return
    }

    if ($AdbWaitSelfTest) {
        $serial = Wait-Adb $AdbTarget 30
        Write-Host "ADB WAIT SELF-TEST: device $serial OK" -ForegroundColor Green
        return
    }

    if ($MagiskStageSelfTest) {
        $script:AdbSerial = Wait-Adb $AdbTarget 60
        Assert-X11
        Install-Apk 'Components\Apps\Magisk-v30.7.apk'
        if (Test-MagiskBoot) { Wait-RootBackend 60 }
        Stage-MagiskEnvironment
        $script:RootBackend = $null
        Wait-RootBackend 60
        Write-Host 'MAGISK STAGE SELF-TEST: environment and shell policy OK' -ForegroundColor Green
        return
    }

    if ($RootRecoverySelfTest) {
        $script:AdbSerial = Wait-Adb $AdbTarget 60
        Assert-X11
        $slot = Get-AndroidBootSlot
        Restore-ActiveStockForRootRecovery
        Install-Apk 'Components\Apps\Magisk-v30.7.apk'
        Stage-MagiskEnvironment
        $partition = "init_boot$slot"
        $patchedImage = Join-Path $kitRoot 'Image\magisk_patched_init_boot_a.img'
        Adb @('reboot', 'bootloader') | Out-Null
        Wait-Fastboot | Out-Null
        Assert-FastbootProduct
        Invoke-External $fastboot @('flash', $partition, $patchedImage) "$partition Magisk recovery flash failed" | Out-Null
        Invoke-External $fastboot @('reboot') 'Fastboot recovery reboot failed' | Out-Null
        $script:AdbSerial = Wait-Adb $AdbTarget 300
        Wait-AndroidReady 300
        Assert-X11
        Assert-AndroidBootSlot $slot
        $script:RootBackend = $null
        $script:MagiskBinary = $null
        Wait-RootBackend 60
        $magiskBinary = Resolve-MagiskBinary
        Root "$magiskBinary -v" 'Magisk recovery verification failed' | Out-Null
        Write-Host 'ROOT RECOVERY SELF-TEST: stock preparation and Magisk reinstall OK' -ForegroundColor Green
        return
    }

    Show-Step verify
    Verify-Manifest
    Write-Host 'SHA256 OK' -ForegroundColor Green
    if ($VerifyOnly) {
        Write-Host 'VERIFY_ONLY: OK' -ForegroundColor Green
        return
    }

    Read-ExactToken $messages[$Language].confirmX11 'X11'

    Show-Step connect
    Install-FastbootDriverPackage
    Resume-AndroidIfAlreadyInFastboot
    if (-not $AdbTarget -and -not $script:AdbSerial) {
        $AdbTarget = Read-Host $messages[$Language].adbTarget
    }
    if ($AdbTarget) {
        $connectResult = Invoke-AdbCapture @('connect', $AdbTarget)
        $connectResult.Output | ForEach-Object { Write-Host $_ }
    }
    $script:AdbSerial = Wait-Adb $AdbTarget
    Assert-X11
    $flashLocked = Read-Prop 'ro.boot.flash.locked'
    $vbmetaState = Read-Prop 'ro.boot.vbmeta.device_state'
    Write-Host "flash.locked=$flashLocked vbmeta.device_state=$vbmetaState"

    Write-Host $messages[$Language].cable -ForegroundColor Yellow
    Read-Host $messages[$Language].cableEnter

    if ($flashLocked -eq '1' -or $vbmetaState -eq 'locked') {
        Write-Host $messages[$Language].unlock -ForegroundColor Red
        Read-ExactToken $messages[$Language].unlockPrompt 'UNLOCK'
        Assert-OemUnlockAllowed
        Adb @('reboot', 'bootloader') | Out-Null
        Wait-Fastboot | Out-Null
        Assert-FastbootProduct
        Invoke-FastbootUnlock @('flashing', 'unlock') 'flashing unlock'
        Invoke-FastbootUnlock @('flashing', 'unlock_critical') 'flashing unlock_critical'
        Invoke-External $fastboot @('reboot') 'Fastboot reboot failed' | Out-Null
        $script:AdbSerial = Wait-Adb '' 300
        Wait-AndroidReady 300
        Assert-X11
        Assert-AndroidBootloaderUnlocked
    }

    Show-Step magisk
    Install-Apk 'Components\Apps\Magisk-v30.7.apk'
    $magiskAlreadyActive = Test-MagiskBoot
    if ($magiskAlreadyActive) {
        $rootAvailable = $false
        try {
            Resolve-RootBackend
            $rootAvailable = $true
        } catch {
            $rootAvailable = $false
        }
        if (-not $rootAvailable) {
            Restore-ActiveStockForRootRecovery
            $magiskAlreadyActive = $false
        }
    }
    Stage-MagiskEnvironment

    Show-Step flash
    if ($magiskAlreadyActive) {
        Write-Host 'Magisk boot 30.7 is already active. Skipping repeated init_boot flash.' -ForegroundColor Green
    } else {
        $targetSlot = Get-AndroidBootSlot
        $targetPartition = "init_boot$targetSlot"
        $inactiveSlot = if ($targetSlot -eq '_a') { '_b' } else { '_a' }
        $inactivePartition = "init_boot$inactiveSlot"
        $inactiveStockRelative = if ($inactiveSlot -eq '_a') {
            'Image\stock_init_boot_a.img'
        } else {
            'Image\stock_init_boot_b.img'
        }
        $inactiveStockImage = Join-Path $kitRoot $inactiveStockRelative
        if (-not (Test-Path -LiteralPath $inactiveStockImage -PathType Leaf)) {
            throw "Missing verified inactive stock image: $inactiveStockImage"
        }
        Write-Host "Magisk boot is not active. Active=$targetPartition; restoring verified stock to inactive=$inactivePartition first." -ForegroundColor Yellow
        Adb @('reboot', 'bootloader') | Out-Null
        Wait-Fastboot | Out-Null
        Assert-FastbootProduct
        $patchedImage = Join-Path $kitRoot 'Image\magisk_patched_init_boot_a.img'
        Invoke-External $fastboot @('flash', $inactivePartition, $inactiveStockImage) "$inactivePartition stock restore failed" | Out-Null
        Invoke-External $fastboot @('flash', $targetPartition, $patchedImage) "$targetPartition flash failed" | Out-Null
        Invoke-External $fastboot @('reboot') 'Fastboot reboot failed' | Out-Null
        $script:AdbSerial = Wait-Adb $AdbTarget 300
        Wait-AndroidReady 300
        Assert-X11
        Assert-AndroidBootSlot $targetSlot
    }

    Show-Step env
    Wait-AndroidReady 300
    Install-Apk 'Components\Apps\Magisk-v30.7.apk'
    if ($magiskAlreadyActive) {
        Write-Host $messages[$Language].magiskReboot -ForegroundColor Yellow
        Adb @('reboot') | Out-Null
        $script:AdbSerial = Wait-Adb $AdbTarget 300
        Wait-AndroidReady 300
        Assert-X11
    }
    $script:RootBackend = $null
    $script:MagiskBinary = $null
    Wait-RootBackend 120
    $magiskBinary = Resolve-MagiskBinary
    Root "$magiskBinary -v" 'Magisk daemon verification failed after environment reboot' | Out-Null

    Show-Step apps
    Install-Apk 'Components\Apps\TECHNOZON_Modifier_X11_v0.7.35_RELEASE.apk' 'info.technozon.steshmodifier' 75
    Initialize-TvSmbPairing

    Show-Step profile
    Adb @('push', (Join-Path $kitRoot 'Scripts\apply_profile.sh'), '/data/local/tmp/apply_profile.sh') | Out-Null
    Root 'chmod 755 /data/local/tmp/apply_profile.sh' | Out-Null
    Root 'sh /data/local/tmp/apply_profile.sh' 'X11 profile failed' | Out-Null

    Show-Step modules
    Install-MagiskModule 'Components\MagiskModules\STESH_Apps_Bundle_v1.1.0.zip'
    Install-MagiskModule 'Components\MagiskModules\TECHNOZON_TV_SMB_Manager_X11_v1.3.6.zip'
    Install-MagiskModule 'Components\MagiskModules\TECHNOZON_X11_Performance_v1.2.7.zip'
    Install-MagiskModule 'Components\MagiskModules\STESH_ATV_Bootanimation_v1.0.zip'
    Install-MagiskModule 'Components\MagiskModules\STESH_Legacy_SU_Guard_v1.0.zip'
    Install-MagiskModule 'Components\MagiskModules\TECHNOZON_X11_Light_Sleep_v1.0.7.zip'
    Install-MagiskModule 'Components\MagiskModules\TECHNOZON_X11_Hardening_v1.0.0.zip'
    Install-MagiskModule 'Components\MagiskModules\TECHNOZON_X11_ATV_AdBlock_v1.0.1.zip'
    Install-MagiskModule 'Components\MagiskModules\TECHNOZON_X11_Home_Clean_v1.0.1.zip'

    Adb @('shell', 'pm', 'path', 'info.technozon.steshmodifier') | Out-Null
    Root 'test -f /data/adb/modules_update/technozon_x11_hardening/module.prop' | Out-Null
    Root 'setprop persist.technozon.adb.tcp 0; setprop persist.adb.tcp.port -1; setprop persist.sys.wifi.adb 0; setprop service.adb.tcp.port -1; settings put global adb_wifi_enabled 0' 'Failed to close maintenance TCP ADB before reboot' | Out-Null

    Show-Step final
    if ((Read-Host) -ne 'REBOOT') { throw 'Final reboot cancelled. Hardening is installed but not active until reboot.' }
    Adb @('reboot') | Out-Null

    Show-Step check
    Start-Sleep -Seconds 75
    if ($AdbTarget -match '^([^:]+):(\d+)$') {
        $hostName = $matches[1]
        $port = [int]$matches[2]
        if (Test-TcpPort $hostName $port) {
            Write-Warning "TCP $hostName`:$port still answers. Use USB and Disable_TCP_ADB_USB.cmd."
        } else {
            Write-Host "TCP $hostName`:$port is closed." -ForegroundColor Green
        }
    }
    Write-Host $messages[$Language].done -ForegroundColor Green
    Write-Host "Log: $logPath"
} catch {
    Write-Host ''
    Write-Host ('ERROR: ' + $_.Exception.Message) -ForegroundColor Red
    Write-Host "Log: $logPath"
    exit 1
} finally {
    Stop-Transcript | Out-Null
}
