diff --git a/Installer/Install.cpp b/Installer/Install.cpp index 816a8ef..befed64 100644 --- a/Installer/Install.cpp +++ b/Installer/Install.cpp @@ -1,6 +1,7 @@ #include "Installer.h" #include "RegKey.h" #include +#include // Returns the exit code. Throws if failed to launch. @@ -190,4 +191,230 @@ void CheckPhonemeConverters() { ReportError(ex.code().value()); } +} + +// Parse command line parameter value +static std::wstring GetParameterValue(int argc, LPWSTR* argv, LPCWSTR paramName) +{ + for (int i = 1; i < argc - 1; i++) + { + if (_wcsicmp(argv[i], paramName) == 0) + { + return argv[i + 1]; + } + } + return L""; +} + +// Check if parameter exists +static bool HasParameter(int argc, LPWSTR* argv, LPCWSTR paramName) +{ + for (int i = 1; i < argc; i++) + { + if (_wcsicmp(argv[i], paramName) == 0) + { + return true; + } + } + return false; +} + +// Apply configuration settings from command line +static void ApplyConfigurationSettings(int argc, LPWSTR* argv) +{ + RegKey configKey, enumKey; + + // Create registry keys + configKey.Create(HKEY_CURRENT_USER, L"Software\\NaturalVoiceSAPIAdapter", KEY_SET_VALUE); + enumKey.Create(HKEY_CURRENT_USER, L"Software\\NaturalVoiceSAPIAdapter\\Enumerator", KEY_SET_VALUE); + + // Log level (0-6, default 2) + std::wstring logLevel = GetParameterValue(argc, argv, L"-loglevel"); + if (!logLevel.empty()) + { + DWORD level = _wtoi(logLevel.c_str()); + if (level <= 6) + { + configKey.SetDword(L"LogLevel", level); + } + } + + // Narrator voices + if (HasParameter(argc, argv, L"-no-narrator")) + { + enumKey.SetDword(L"NoNarratorVoices", 1); + } + else if (HasParameter(argc, argv, L"-enable-narrator")) + { + enumKey.SetDword(L"NoNarratorVoices", 0); + } + + // Edge voices + if (HasParameter(argc, argv, L"-no-edge")) + { + enumKey.SetDword(L"NoEdgeVoices", 1); + } + else if (HasParameter(argc, argv, L"-enable-edge")) + { + enumKey.SetDword(L"NoEdgeVoices", 0); + } + + // Azure voices + if (HasParameter(argc, argv, L"-no-azure")) + { + enumKey.SetDword(L"NoAzureVoices", 1); + } + else if (HasParameter(argc, argv, L"-enable-azure")) + { + enumKey.SetDword(L"NoAzureVoices", 0); + } + + // Narrator voice path + std::wstring narratorPath = GetParameterValue(argc, argv, L"-narrator-path"); + if (!narratorPath.empty()) + { + enumKey.SetString(L"NarratorVoicePath", narratorPath.c_str()); + } + + // Azure key and region + std::wstring azureKey = GetParameterValue(argc, argv, L"-azure-key"); + std::wstring azureRegion = GetParameterValue(argc, argv, L"-azure-region"); + if (!azureKey.empty()) + { + enumKey.SetString(L"AzureVoiceKey", azureKey.c_str()); + } + if (!azureRegion.empty()) + { + enumKey.SetString(L"AzureVoiceRegion", azureRegion.c_str()); + } + + // Language settings + if (HasParameter(argc, argv, L"-all-languages")) + { + enumKey.SetDword(L"EdgeVoiceAllLanguages", 1); + } + else + { + std::wstring languages = GetParameterValue(argc, argv, L"-languages"); + if (!languages.empty()) + { + enumKey.SetDword(L"EdgeVoiceAllLanguages", 0); + // Convert comma-separated to null-separated + std::vector langList; + std::wstring temp; + for (wchar_t c : languages) + { + if (c == L',') + { + if (!temp.empty()) + { + langList.push_back(temp); + temp.clear(); + } + } + else + { + temp.push_back(c); + } + } + if (!temp.empty()) + { + langList.push_back(temp); + } + enumKey.SetMultiStringList(L"EdgeVoiceLanguages", langList); + } + } +} + +void SilentInstall(int argc, LPWSTR* argv) +{ + // Apply configuration settings first + ApplyConfigurationSettings(argc, argv); + + // Determine which architectures to install + bool install32 = true; + bool install64 = Is64BitSystem(); + + // Check for architecture-specific parameters + if (HasParameter(argc, argv, L"-32bit-only")) + { + install32 = true; + install64 = false; + } + else if (HasParameter(argc, argv, L"-64bit-only")) + { + install32 = false; + install64 = Is64BitSystem(); + } + + // Install 32-bit version + if (install32) + { + try + { + Register(false); + } + catch (const std::system_error&) + { + // In silent mode, we still throw to let the caller handle it + throw; + } + } + + // Install 64-bit version + if (install64) + { + try + { + Register(true); + } + catch (const std::system_error&) + { + // In silent mode, we still throw to let the caller handle it + throw; + } + } + + // Check and install phoneme converters if needed + if (!HasParameter(argc, argv, L"-no-phoneme-converters")) + { + HKEY hKey; + bool hasConverters = true; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Speech\\PhoneConverters\\Tokens\\Universal", + 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hKey) == ERROR_SUCCESS) + { + RegCloseKey(hKey); + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Speech\\PhoneConverters\\Tokens\\Universal", + 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &hKey) == ERROR_SUCCESS) + { + RegCloseKey(hKey); + } + else + hasConverters = false; + } + else + hasConverters = false; + + if (!hasConverters) + { + try + { + if (Is64BitSystem()) + AddToRegistry(L"x64\\PhoneConverters.reg"); + else + AddToRegistry(L"x86\\PhoneConverters.reg"); + } + catch (const std::system_error&) + { + // Ignore errors in silent mode for phoneme converters + } + } + } +} + +void SilentUninstall() +{ + Unregister(false); + if (Is64BitSystem()) + Unregister(true); } \ No newline at end of file diff --git a/Installer/Installer.vcxproj b/Installer/Installer.vcxproj index ecab19f..4ecf7e5 100644 --- a/Installer/Installer.vcxproj +++ b/Installer/Installer.vcxproj @@ -29,14 +29,14 @@ Application true - v143 + $(DefaultPlatformToolset) Unicode YY_Thunks_for_WinXP.obj Application false - v143 + $(DefaultPlatformToolset) true Unicode YY_Thunks_for_WinXP.obj @@ -44,14 +44,14 @@ Application true - v143 + $(DefaultPlatformToolset) Unicode YY_Thunks_for_WinXP.obj Application false - v143 + $(DefaultPlatformToolset) true Unicode YY_Thunks_for_WinXP.obj diff --git a/Installer/WinMain.cpp b/Installer/WinMain.cpp index 9ab0241..5680679 100644 --- a/Installer/WinMain.cpp +++ b/Installer/WinMain.cpp @@ -1,8 +1,48 @@ #include "Installer.h" +#include +#include void Register(bool is64Bit); void Unregister(bool is64Bit); INT_PTR CALLBACK MainDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +void SilentInstall(int argc, LPWSTR* argv); +void SilentUninstall(); + +// Show help message +void ShowHelp() +{ + MessageBoxW(nullptr, + L"NaturalVoiceSAPIAdapter Installer - Command Line Options\n\n" + L"Basic Usage:\n" + L" Installer.exe - Open GUI installer\n" + L" Installer.exe -silent - Silent installation\n" + L" Installer.exe -silent -uninstall - Silent uninstallation\n\n" + L"Parameters:\n" + L" -?, -h, -help, /?, /h, /help - Show this help\n" + L" -silent, -s, /silent, /s - Enable silent mode\n" + L" -uninstall, /uninstall - Uninstall mode\n\n" + L"Architecture:\n" + L" -32bit-only - Install 32-bit version only\n" + L" -64bit-only - Install 64-bit version only\n\n" + L"Voice Engines:\n" + L" -enable-narrator, -no-narrator - Enable/disable Narrator voices\n" + L" -enable-edge, -no-edge - Enable/disable Edge voices\n" + L" -enable-azure, -no-azure - Enable/disable Azure voices\n\n" + L"Configuration:\n" + L" -narrator-path - Set Narrator voice path\n" + L" -azure-key - Set Azure Speech Service key\n" + L" -azure-region - Set Azure Speech Service region\n" + L" -languages - Language list (comma-separated)\n" + L" -all-languages - Include all languages\n" + L" -loglevel <0-6> - Set log level (0=Off, 6=Verbose)\n" + L" -no-phoneme-converters - Skip phoneme converters\n\n" + L"Examples:\n" + L" Installer.exe -silent -enable-edge -languages \"en-US,zh-CN\"\n" + L" Installer.exe -silent -64bit-only -no-narrator -enable-edge\n\n" + L"For detailed documentation, see SILENT_INSTALL.md", + L"NaturalVoiceSAPIAdapter Installer Help", + MB_ICONINFORMATION | MB_OK); +} int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, @@ -11,28 +51,89 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(nCmdShow); + UNREFERENCED_PARAMETER(lpCmdLine); + + // Parse command line arguments + int argc; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (!argv) + { + return GetLastError(); + } - if (_wcsicmp(lpCmdLine, L"-uninstall") == 0) + // Check for help request first + for (int i = 1; i < argc; i++) { - try + if (_wcsicmp(argv[i], L"-?") == 0 || _wcsicmp(argv[i], L"/?") == 0 || + _wcsicmp(argv[i], L"-h") == 0 || _wcsicmp(argv[i], L"/h") == 0 || + _wcsicmp(argv[i], L"-help") == 0 || _wcsicmp(argv[i], L"/help") == 0 || + _wcsicmp(argv[i], L"--help") == 0) { - Unregister(false); - if (Is64BitSystem()) - Unregister(true); + ShowHelp(); + LocalFree(argv); + return 0; + } + } - ReportError(ERROR_SUCCESS); + // Check for silent install/uninstall + bool silentMode = false; + bool uninstallMode = false; + + for (int i = 1; i < argc; i++) + { + if (_wcsicmp(argv[i], L"-silent") == 0 || _wcsicmp(argv[i], L"/silent") == 0 || + _wcsicmp(argv[i], L"-s") == 0 || _wcsicmp(argv[i], L"/s") == 0) + { + silentMode = true; } - catch (const std::system_error& ex) + else if (_wcsicmp(argv[i], L"-uninstall") == 0 || _wcsicmp(argv[i], L"/uninstall") == 0) { - DWORD err = ex.code().value(); - ReportError(err); - return err; + uninstallMode = true; } } - else + + DWORD exitCode = 0; + + try { - DialogBoxParamW(hInstance, MAKEINTRESOURCEW(IDD_MAIN), nullptr, MainDlg, 0); + if (uninstallMode) + { + if (silentMode) + { + SilentUninstall(); + } + else + { + Unregister(false); + if (Is64BitSystem()) + Unregister(true); + ReportError(ERROR_SUCCESS); + } + } + else if (silentMode) + { + SilentInstall(argc, argv); + } + else + { + DialogBoxParamW(hInstance, MAKEINTRESOURCEW(IDD_MAIN), nullptr, MainDlg, 0); + } + } + catch (const std::system_error& ex) + { + exitCode = ex.code().value(); + if (!silentMode) + ReportError(exitCode); + } + catch (const std::exception& ex) + { + if (!silentMode) + { + MessageBoxA(nullptr, ex.what(), "Error", MB_ICONERROR); + } + exitCode = E_FAIL; } - return 0; + LocalFree(argv); + return exitCode; } \ No newline at end of file diff --git a/SILENT_INSTALL.md b/SILENT_INSTALL.md new file mode 100644 index 0000000..53cc84f --- /dev/null +++ b/SILENT_INSTALL.md @@ -0,0 +1,229 @@ +# 静默安装指南 / Silent Installation Guide + +## 概述 / Overview + +NaturalVoiceSAPIAdapter 安装程序现在支持静默安装和卸载功能,允许在没有用户界面的情况下自动完成安装过程。 + +The NaturalVoiceSAPIAdapter installer now supports silent installation and uninstallation, allowing automated installation without user interface. + +# 快速参考 / Quick Reference + +## Installer.exe 构建指令 + +提前安装好 Visual Studio Community。 + +```powershell +"C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe" Installer\Installer.vcxproj /p:Configuration=Release /p:Platform=Win32 /v:minimal /t:Clean,Build +``` + +## 命令速查 / Command Cheat Sheet + +### 帮助 / Help +```cmd +Installer.exe -? +``` + +### 基本安装 / Basic Installation +```cmd +REM GUI 安装 / GUI Installation +Installer.exe + +REM 静默安装 / Silent Installation +Installer.exe -silent +Installer.exe -s +``` + +### 卸载 / Uninstallation +```cmd +REM GUI 卸载 / GUI Uninstallation +Installer.exe -uninstall + +REM 静默卸载 / Silent Uninstallation +Installer.exe -silent -uninstall +``` + +### 常用配置 / Common Configurations + +#### 仅讲述人自然语音 / Narrator Voices Only +```cmd +Installer.exe -silent -64bit-only -enable-narrator -no-edge -no-azure +``` + +#### 仅 Edge 语音 / Edge Voices Only +```cmd +Installer.exe -silent -no-narrator -enable-edge -no-azure +``` + +#### 企业标准配置 / Enterprise Standard +```cmd +Installer.exe -silent -64bit-only -enable-edge -languages "en-US,zh-CN" -loglevel 1 +``` + +#### 完整配置 / Full Configuration +```cmd +Installer.exe -silent ^ + -enable-narrator ^ + -enable-edge ^ + -enable-azure ^ + -azure-key "your-key" ^ + -azure-region "eastus" ^ + -languages "en-US,zh-CN,ja-JP" ^ + -loglevel 2 +``` + +## 参数速查表 / Parameter Quick Reference + +| 参数 / Parameter | 说明 / Description | 示例 / Example | +|------------------|-------------------|----------------| +| `-?`, `-h`, `-help` | 显示帮助 / Show help | `Installer.exe -?` | +| `-silent`, `-s` | 静默模式 / Silent mode | `Installer.exe -s` | +| `-uninstall` | 卸载 / Uninstall | `Installer.exe -uninstall` | +| `-32bit-only` | 仅32位 / 32-bit only | `Installer.exe -s -32bit-only` | +| `-64bit-only` | 仅64位 / 64-bit only | `Installer.exe -s -64bit-only` | +| `-enable-narrator` | 启用讲述人 / Enable Narrator | `Installer.exe -s -enable-narrator` | +| `-no-narrator` | 禁用讲述人 / Disable Narrator | `Installer.exe -s -no-narrator` | +| `-enable-edge` | 启用Edge / Enable Edge | `Installer.exe -s -enable-edge` | +| `-no-edge` | 禁用Edge / Disable Edge | `Installer.exe -s -no-edge` | +| `-enable-azure` | 启用Azure / Enable Azure | `Installer.exe -s -enable-azure` | +| `-no-azure` | 禁用Azure / Disable Azure | `Installer.exe -s -no-azure` | +| `-narrator-path ` | 讲述人路径 / Narrator path | `Installer.exe -s -narrator-path "C:\Voices"` | +| `-azure-key ` | Azure密钥 / Azure key | `Installer.exe -s -azure-key "abc123"` | +| `-azure-region ` | Azure区域 / Azure region | `Installer.exe -s -azure-region "eastus"` | +| `-languages ` | 语言列表 / Language list | `Installer.exe -s -languages "en-US,zh-CN"` | +| `-all-languages` | 所有语言 / All languages | `Installer.exe -s -all-languages` | +| `-loglevel <0-6>` | 日志级别 / Log level | `Installer.exe -s -loglevel 2` | +| `-no-phoneme-converters` | 跳过音素转换器 / Skip phoneme converters | `Installer.exe -s -no-phoneme-converters` | + +## 退出代码 / Exit Codes + +| 代码 / Code | 含义 / Meaning | +|-------------|----------------| +| 0 | 成功 / Success | +| 2 | 文件未找到 / File not found | +| 5 | 访问被拒绝(需要管理员)/ Access denied (need admin) | +| 87 | 参数错误 / Invalid parameter | +| 1223 | 用户取消UAC / User cancelled UAC | + +## 日志级别 / Log Levels + +| 级别 / Level | 说明 / Description | +|--------------|-------------------| +| 0 | 关闭 / Off | +| 1 | 错误 / Error | +| 2 | 警告 / Warning (默认 / default) | +| 3 | 信息 / Info | +| 4 | 调试 / Debug | +| 5 | 详细 / Verbose | +| 6 | 全部 / All | + +## 常见语言代码 / Common Language Codes + +| 代码 / Code | 语言 / Language | +|-------------|----------------| +| en-US | 英语(美国)/ English (US) | +| en-GB | 英语(英国)/ English (UK) | +| zh-CN | 中文(简体)/ Chinese (Simplified) | +| zh-TW | 中文(繁体)/ Chinese (Traditional) | +| ja-JP | 日语 / Japanese | +| ko-KR | 韩语 / Korean | +| fr-FR | 法语 / French | +| de-DE | 德语 / German | +| es-ES | 西班牙语 / Spanish | +| it-IT | 意大利语 / Italian | +| pt-BR | 葡萄牙语(巴西)/ Portuguese (Brazil) | +| ru-RU | 俄语 / Russian | + +## 故障排除 / Troubleshooting + +### 问题:退出代码 5 +**解决方案:** 以管理员身份运行 +```cmd +REM 右键点击命令提示符 > 以管理员身份运行 +REM Right-click Command Prompt > Run as Administrator +``` + +### 问题:退出代码 2 +**解决方案:** 检查文件路径 +```cmd +REM 确保 x86 和 x64 文件夹存在 +dir x86\NaturalVoiceSAPIAdapter.dll +dir x64\NaturalVoiceSAPIAdapter.dll +``` + +### 问题:帮助不显示 +**解决方案:** 这是正常的,帮助是GUI对话框 +```cmd +REM 直接运行,会显示对话框 +Installer.exe -? +``` + +## 验证安装 / Verify Installation + +### 检查注册表 / Check Registry +```cmd +REM 32位版本 +reg query "HKLM\SOFTWARE\Classes\CLSID\{013ab33b-ad1a-401c-8bee-f6e2b046a94e}\InprocServer32" /reg:32 + +REM 64位版本 +reg query "HKLM\SOFTWARE\Classes\CLSID\{013ab33b-ad1a-401c-8bee-f6e2b046a94e}\InprocServer32" /reg:64 +``` + +### 检查配置 / Check Configuration +```cmd +REM 查看配置 +reg query "HKCU\Software\NaturalVoiceSAPIAdapter" +reg query "HKCU\Software\NaturalVoiceSAPIAdapter\Enumerator" +``` + +## 批量部署脚本模板 / Batch Deployment Script Template + +```batch +@echo off +REM 企业部署脚本 +REM Enterprise Deployment Script + +REM 检查管理员权限 +net session >nul 2>&1 +if %errorLevel% neq 0 ( + echo ERROR: Administrator privileges required + exit /b 1 +) + +REM 执行静默安装 +Installer.exe -silent ^ + -64bit-only ^ + -enable-edge ^ + -languages "en-US,zh-CN" ^ + -loglevel 1 + +REM 检查结果 +if %errorLevel% equ 0 ( + echo Installation successful +) else ( + echo Installation failed: %errorLevel% + exit /b %errorLevel% +) +``` + +## PowerShell 部署脚本模板 / PowerShell Deployment Script Template + +```powershell +# 检查管理员权限 +if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { + Write-Error "Administrator privileges required" + exit 1 +} + +# 执行静默安装 +$process = Start-Process -FilePath "Installer.exe" ` + -ArgumentList "-silent", "-64bit-only", "-enable-edge", "-languages", "en-US,zh-CN", "-loglevel", "1" ` + -Wait -PassThru -NoNewWindow + +# 检查结果 +if ($process.ExitCode -eq 0) { + Write-Host "Installation successful" -ForegroundColor Green +} else { + Write-Error "Installation failed: $($process.ExitCode)" + exit $process.ExitCode +} +```