import {memoize, runElevated} from './utils'; import {fs, tempDir} from 'appium/support'; import path from 'node:path'; import {exec} from 'teen_process'; import {log} from './logger'; import {queryRegistry, type RegEntry} from './registry'; const POSSIBLE_WAD_INSTALL_ROOTS = [ process.env['ProgramFiles(x86)'], process.env.ProgramFiles, `${process.env.SystemDrive || 'C:'}\\\\Program Files`, ]; const WAD_EXE_NAME = 'WinAppDriver.exe'; const UNINSTALL_REG_ROOT = 'HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall'; const REG_ENTRY_VALUE = 'Windows Application Driver'; const REG_ENTRY_KEY = 'DisplayName'; const REG_ENTRY_TYPE = 'REG_SZ'; const INST_LOCATION_SCRIPT_BY_GUID = (guid: string): string => ` Set installer = CreateObject("WindowsInstaller.Installer") Set session = installer.OpenProduct("${guid}") session.DoAction("CostInitialize") session.DoAction("CostFinalize") WScript.Echo session.Property("INSTALLFOLDER") `.replace(/\n/g, '\r\n'); class WADNotFoundError extends Error {} /** * Fetches the MSI installation location for a given installer GUID * * @param installerGuid - The MSI installer GUID * @returns The installation location path */ async function fetchMsiInstallLocation(installerGuid: string): Promise { const tmpRoot = await tempDir.openDir(); const scriptPath = path.join(tmpRoot, 'get_wad_inst_location.vbs'); try { await fs.writeFile(scriptPath, INST_LOCATION_SCRIPT_BY_GUID(installerGuid), 'latin1'); const {stdout} = await runElevated('cscript.exe', ['/Nologo', scriptPath]); return stdout.trim(); } finally { await fs.rimraf(tmpRoot); } } export const getWADExecutablePath = memoize(async function getWADInstallPath(): Promise { const wadPath = process.env.APPIUM_WAD_PATH ?? ''; if (await fs.exists(wadPath)) { log.debug(`Loaded WinAppDriver path from the APPIUM_WAD_PATH environment variable: ${wadPath}`); return wadPath; } // TODO: WAD installer should write the full path to it into the system registry const pathCandidates = POSSIBLE_WAD_INSTALL_ROOTS // remove unset env variables .filter((root): root is string => Boolean(root)) // construct full path .map((root) => path.resolve(root, REG_ENTRY_VALUE, WAD_EXE_NAME)); for (const result of pathCandidates) { if (await fs.exists(result)) { return result; } } log.debug('Did not detect WAD executable at any of the default install locations'); log.debug('Checking the system registry for the corresponding MSI entry'); try { const uninstallEntries = await queryRegistry(UNINSTALL_REG_ROOT); const wadEntry = uninstallEntries.find( (entry: RegEntry) => entry.key === REG_ENTRY_KEY && entry.value === REG_ENTRY_VALUE && entry.type === REG_ENTRY_TYPE, ); if (wadEntry) { log.debug(`Found MSI entry: ${JSON.stringify(wadEntry)}`); const parts = wadEntry.root.split('\\'); const installerGuid = parts[parts.length - 1] as string; // WAD MSI installer leaves InstallLocation registry value empty, // so we need to be hacky here const result = path.join(await fetchMsiInstallLocation(installerGuid), WAD_EXE_NAME); log.debug(`Checking if WAD exists at '${result}'`); if (await fs.exists(result)) { return result; } log.debug(result); } else { log.debug('No WAD MSI entries have been found'); } } catch (e: any) { if (e.stderr) { log.debug(e.stderr); } log.debug(e.stack); } throw new WADNotFoundError( `${WAD_EXE_NAME} has not been found in any of these ` + `locations: ${pathCandidates}. Use the following driver script to install it: ` + `'appium driver run windows install-wad '. ` + `Check https://github.com/microsoft/WinAppDriver/releases to list ` + `available server versions or drop the '' argument to ` + `install the latest stable one.`, ); }); /** * Checks if the current process is running with administrator privileges * * @returns Promise that resolves to true if running as admin, false otherwise */ export async function isAdmin(): Promise { try { await exec('fsutil.exe', ['dirty', 'query', process.env.SystemDrive || 'C:']); return true; } catch { return false; } }