From 8cb33b8388c0a17ca46671063c680f3a21b815be Mon Sep 17 00:00:00 2001 From: Federico Michel <104396772+aimagist@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:04:50 -0300 Subject: [PATCH 1/7] Update Open in Admin Terminal to v1.17.1 --- mods/open-in-admin-terminal.wh.cpp | 558 ++++++++++++++++++++++++++--- 1 file changed, 507 insertions(+), 51 deletions(-) diff --git a/mods/open-in-admin-terminal.wh.cpp b/mods/open-in-admin-terminal.wh.cpp index 83d63e923e..fb4f0dd6cb 100644 --- a/mods/open-in-admin-terminal.wh.cpp +++ b/mods/open-in-admin-terminal.wh.cpp @@ -2,7 +2,7 @@ // @id open-in-admin-terminal // @name Open in Admin Terminal // @description Adds an Explorer classic context menu entry to open an elevated terminal in the current or selected folder. -// @version 1.16 +// @version 1.17.1 // @author aimagist // @github https://github.com/aimagist // @include explorer.exe @@ -21,10 +21,13 @@ - Right-click a folder background and open an admin terminal in that location - Right-click a folder item and open an admin terminal inside it - Right-click a drive and open an admin terminal at its root +- Optionally add a second terminal entry that opens without administrator privileges +- Optionally run `.ps1`, `.bat`, `.cmd`, `.vbs`, and `.js` script files normally, as administrator, or both - Optionally show the entry when right-clicking filesystem folders and drives in the navigation pane and Quick access - Choose your preferred terminal: Auto, Windows Terminal, PowerShell 7, Windows PowerShell, Command Prompt, WSL, Git Bash, WezTerm, Alacritty, ConEmu, or a custom command - Customize the context menu label, or let the mod use a smart default based on your terminal choice - Optionally append the terminal name to a custom label (e.g. "Open elevated (Windows Terminal)") +- Choose whether script entries include the terminal name or use shorter labels ## Preview @@ -46,9 +49,12 @@ Screenshots may show earlier builds, but current releases use runtime classic-me - The entry is injected only while Explorer's classic menu is open; disabling the mod leaves no registry cleanup behind. - The mod intentionally targets filesystem folders and drive roots only, including optional navigation pane and Quick access support. - Auto chooses Windows Terminal, PowerShell 7, Windows PowerShell, then Command Prompt. If another built-in preset is unavailable, the mod falls back to Auto instead of hiding the entry. +- Script actions use the selected terminal when it can host the required Windows interpreter. WSL, Git Bash, and custom commands fall back to PowerShell, Command Prompt, or Windows Script Host and use that actual program name in the menu. - Diagnostics use Windhawk's built-in logging controls. ## Version log +- 1.17.1: Fix navigation pane menu not appearing: the entry was missing when right-clicking folders or drives in File Explorer’s left sidebar and Quick access. Now it works :) +- 1.17: Added optional non-elevated terminal entries, configurable normal/elevated script actions (.ps1/.bat/.cmd/.vbs/.js), and shorter script labels. The two extra entry features are opt-in and disabled by default; terminal names remain shown in script entries by default for compatibility. - 1.16: Added native UAC shield overlay composition on the terminal menu icon using `SHGetStockIconInfo(SIID_SHIELD)`. - 1.15: Added optional navigation pane and Quick access support for filesystem folders and drives. - 1.14: Added support for Desktop context menu targets. @@ -106,6 +112,34 @@ Screenshots may show earlier builds, but current releases use runtime classic-me - appendTerminalName: false $name: Append terminal name $description: When Menu text is set, append the selected terminal name in parentheses. +- showNonElevatedEntry: false + $name: Show non-elevated entry + $description: Add a second context menu entry to open a terminal without admin privileges. +- nonElevatedMenuText: "" + $name: Non-elevated menu text + $description: Leave empty for auto label based on terminal name. +- showOnScriptFiles: false + $name: Show on script files + $description: Add context menu entries when right-clicking supported script files. +- scriptMenuEntries: admin + $name: Script menu entries + $description: Choose which actions to show for script files. + $options: + - admin: Run as administrator + - normal: Run normally + - both: Show both +- appendScriptTerminalName: true + $name: Append terminal name to script entries + $description: Add the selected terminal name to script actions. Disable for shorter labels. +- scriptExtensions: ".ps1;.bat;.cmd;.vbs;.js" + $name: Script extensions + $description: Semicolon-separated extensions treated as scripts. +- keepOpenAfterScript: true + $name: Keep terminal open after script + $description: Terminal stays open after script finishes. +- scriptExecutionPolicyBypass: true + $name: Bypass execution policy for .ps1 + $description: Adds -ExecutionPolicy Bypass for PowerShell scripts. */ // ==/WindhawkModSettings== @@ -134,6 +168,14 @@ struct Settings { bool showOnDriveItem; bool showOnNavigationPane; std::wstring position; + bool showNonElevatedEntry; + std::wstring nonElevatedMenuText; + bool showOnScriptFiles; + std::wstring scriptMenuEntries; + bool appendScriptTerminalName; + std::wstring scriptExtensions; + bool keepOpenAfterScript; + bool scriptExecutionPolicyBypass; }; enum class TargetKind { @@ -141,6 +183,7 @@ enum class TargetKind { FolderBackground, FolderItem, DriveItem, + ScriptItem, }; struct MenuTarget { @@ -154,10 +197,17 @@ struct LaunchSpec { std::wstring workingDirectory; }; +static Settings GetSettingsSnapshot(); +static void LaunchTerminalNonElevated(const MenuTarget&); +static LaunchSpec BuildScriptLaunchSpec(const Settings&, const std::wstring&); +static bool IsScriptExtension(const std::wstring&, const Settings&); +static bool IsShellViewWindow(HWND hwnd); + static Settings g_settings; static SRWLOCK g_settingsLock = SRWLOCK_INIT; static const UINT kMenuCommandId = 0xBF31; +static const UINT kMenuCommandIdNonElevated = 0xBF32; #ifndef IO_REPARSE_TAG_APPEXECLINK #define IO_REPARSE_TAG_APPEXECLINK (0x8000001BL) @@ -486,6 +536,14 @@ static Settings LoadSettings() { s.showOnDriveItem = GetSettingBool(L"showOnDriveItem"); s.showOnNavigationPane = GetSettingBool(L"showOnNavigationPane"); s.position = GetSettingString(L"position", L"Top"); + s.showNonElevatedEntry = GetSettingBool(L"showNonElevatedEntry"); + s.nonElevatedMenuText = GetSettingString(L"nonElevatedMenuText", L""); + s.showOnScriptFiles = GetSettingBool(L"showOnScriptFiles"); + s.scriptMenuEntries = GetSettingString(L"scriptMenuEntries", L"admin"); + s.appendScriptTerminalName = GetSettingBool(L"appendScriptTerminalName"); + s.scriptExtensions = GetSettingString(L"scriptExtensions", L".ps1;.bat;.cmd;.vbs;.js"); + s.keepOpenAfterScript = GetSettingBool(L"keepOpenAfterScript"); + s.scriptExecutionPolicyBypass = GetSettingBool(L"scriptExecutionPolicyBypass"); ResolveSettingsTerminal(s); @@ -508,6 +566,9 @@ static bool IsTargetEnabled(const Settings& s, TargetKind kind) { if (kind == TargetKind::DriveItem) { return s.showOnDriveItem; } + if (kind == TargetKind::ScriptItem) { + return s.showOnScriptFiles; + } return false; } @@ -521,6 +582,9 @@ static PCWSTR TargetKindName(TargetKind kind) { if (kind == TargetKind::DriveItem) { return L"drive-item"; } + if (kind == TargetKind::ScriptItem) { + return L"script-item"; + } return L"none"; } @@ -529,6 +593,33 @@ static bool IsDirectoryPath(const std::wstring& path) { return attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY); } +static bool IsScriptExtension(const std::wstring& path, const Settings& s) { + PCWSTR ext = PathFindExtensionW(path.c_str()); + if (!ext || !*ext) { + return false; + } + + if (_wcsicmp(ext, L".ps1") != 0 && _wcsicmp(ext, L".bat") != 0 && + _wcsicmp(ext, L".cmd") != 0 && _wcsicmp(ext, L".vbs") != 0 && + _wcsicmp(ext, L".js") != 0) { + return false; + } + + size_t start = 0; + while (start <= s.scriptExtensions.size()) { + size_t end = s.scriptExtensions.find(L';', start); + std::wstring token = TrimString(s.scriptExtensions.substr(start, end - start)); + if (_wcsicmp(token.c_str(), ext) == 0) { + return true; + } + if (end == std::wstring::npos) { + break; + } + start = end + 1; + } + return false; +} + static bool IsDriveRootPath(const std::wstring& path) { return PathIsRootW(path.c_str()) != FALSE; } @@ -716,6 +807,93 @@ static LaunchSpec BuildLaunchSpec(const Settings& s, const std::wstring& target) return spec; } +static LaunchSpec BuildScriptInterpreterSpec(const Settings& s, + const std::wstring& scriptPath) { + LaunchSpec spec; + PCWSTR ext = PathFindExtensionW(scriptPath.c_str()); + size_t separator = scriptPath.find_last_of(L"\\/"); + spec.workingDirectory = scriptPath.substr( + 0, separator == 2 && scriptPath.size() > 2 && scriptPath[1] == L':' + ? separator + 1 + : separator); + + if (_wcsicmp(ext, L".ps1") == 0) { + if (s.terminalEffectiveChoice == L"pwsh" || + s.terminalEffectiveChoice == L"powershell") { + spec.executable = s.terminalDisplayCommand; + } else if (!ResolveTerminalChoiceExecutable(L"pwsh", spec.executable)) { + spec.executable = L"powershell.exe"; + } + std::vector args; + if (s.keepOpenAfterScript) { + args.push_back(L"-NoExit"); + } + if (s.scriptExecutionPolicyBypass) { + args.push_back(L"-ExecutionPolicy"); + args.push_back(L"Bypass"); + } + args.push_back(L"-File"); + args.push_back(scriptPath); + spec.parameters = JoinCommandLineArguments(args); + } else if (_wcsicmp(ext, L".bat") == 0 || + _wcsicmp(ext, L".cmd") == 0) { + spec.executable = L"cmd.exe"; + spec.parameters = + std::wstring(s.keepOpenAfterScript ? L"/k " : L"/c ") + + QuoteCommandLineArgument(scriptPath); + } else if (_wcsicmp(ext, L".vbs") == 0 || + _wcsicmp(ext, L".js") == 0) { + std::wstring scriptCommand = + L"cscript.exe //nologo " + QuoteCommandLineArgument(scriptPath); + if (s.keepOpenAfterScript) { + spec.executable = L"cmd.exe"; + spec.parameters = L"/k " + scriptCommand; + } else { + spec.executable = L"cscript.exe"; + spec.parameters = L"//nologo " + QuoteCommandLineArgument(scriptPath); + } + } + return spec; +} + +static bool IsScriptHostChoice(const std::wstring& choice) { + return choice == L"wt" || choice == L"wezterm" || + choice == L"alacritty" || choice == L"conemu"; +} + +static LaunchSpec BuildScriptLaunchSpec(const Settings& s, + const std::wstring& scriptPath) { + LaunchSpec interpreter = BuildScriptInterpreterSpec(s, scriptPath); + if (!IsScriptHostChoice(s.terminalEffectiveChoice)) { + return interpreter; + } + + LaunchSpec spec; + spec.executable = s.terminalDisplayCommand; + spec.workingDirectory = interpreter.workingDirectory; + std::vector args; + + if (s.terminalEffectiveChoice == L"wt") { + args = {L"new-tab", L"-d", interpreter.workingDirectory, + interpreter.executable}; + } else if (s.terminalEffectiveChoice == L"wezterm") { + args = {L"start", L"--cwd", interpreter.workingDirectory, L"--", + interpreter.executable}; + } else if (s.terminalEffectiveChoice == L"alacritty") { + args = {L"--working-directory", interpreter.workingDirectory, L"-e", + interpreter.executable}; + } else { + args = {L"-Dir", interpreter.workingDirectory, L"-run", + interpreter.executable}; + } + + spec.parameters = JoinCommandLineArguments(args); + if (!interpreter.parameters.empty()) { + spec.parameters += L" " + interpreter.parameters; + } + return spec; +} + static IServiceProvider* GetExplorerServiceProviderForHwnd(HWND topLevel) { IShellWindows* shellWindows = nullptr; HRESULT hr = CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, @@ -972,6 +1150,25 @@ static UINT GetSelectedPaths(IShellView* shellView, return selectedCount; } +static bool GetFilesystemPathFromShellItem(IShellItem* item, + std::wstring& pathOut) { + pathOut.clear(); + if (!item) { + return false; + } + + PWSTR path = nullptr; + bool ok = SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &path)) && + path && path[0]; + if (ok) { + pathOut = path; + } + if (path) { + CoTaskMemFree(path); + } + return ok; +} + static bool GetSingleFilesystemPathFromShellItemArray(IShellItemArray* items, std::wstring& pathOut) { pathOut.clear(); @@ -989,16 +1186,7 @@ static bool GetSingleFilesystemPathFromShellItemArray(IShellItemArray* items, return false; } - PWSTR path = nullptr; - bool ok = SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &path)) && path && - path[0]; - if (ok) { - pathOut = path; - } - if (path) { - CoTaskMemFree(path); - } - + bool ok = GetFilesystemPathFromShellItem(item, pathOut); item->Release(); return ok; } @@ -1028,6 +1216,27 @@ static bool IsNavigationPaneWindow(HWND hwnd) { return false; } +static bool IsNavigationPaneContextWindow(HWND hwnd) { + if (IsNavigationPaneWindow(hwnd)) { + return true; + } + + HWND focus = GetFocus(); + if (focus && IsNavigationPaneWindow(focus)) { + return true; + } + + POINT cursorPos; + if (GetCursorPos(&cursorPos)) { + HWND underCursor = WindowFromPoint(cursorPos); + if (underCursor && IsNavigationPaneWindow(underCursor)) { + return true; + } + } + + return false; +} + static bool ResolveNavigationPaneMenuTarget(HWND hwnd, MenuTarget& targetOut) { targetOut = {}; @@ -1047,19 +1256,51 @@ static bool ResolveNavigationPaneMenuTarget(HWND hwnd, MenuTarget& targetOut) { SID_SNavigationPane, IID_INameSpaceTreeControl, reinterpret_cast(&navigationPane))) && navigationPane) { - IShellItemArray* selectedItems = nullptr; - if (SUCCEEDED(navigationPane->GetSelectedItems(&selectedItems)) && - selectedItems) { - std::wstring path; - if (GetSingleFilesystemPathFromShellItemArray(selectedItems, path) && - IsDirectoryPath(path)) { - targetOut.path = std::move(path); - targetOut.kind = IsDriveRootPath(targetOut.path) - ? TargetKind::DriveItem - : TargetKind::FolderItem; - ok = true; + IShellBrowser* shellBrowser = nullptr; + HWND treeWindow = nullptr; + if (SUCCEEDED(serviceProvider->QueryService( + SID_STopLevelBrowser, IID_IShellBrowser, + reinterpret_cast(&shellBrowser))) && + shellBrowser) { + shellBrowser->GetControlWindow(FCW_TREE, &treeWindow); + shellBrowser->Release(); + } + + POINT cursorPos; + if (treeWindow && GetCursorPos(&cursorPos) && + ScreenToClient(treeWindow, &cursorPos)) { + IShellItem* hitItem = nullptr; + if (SUCCEEDED(navigationPane->HitTest(&cursorPos, &hitItem)) && + hitItem) { + std::wstring path; + if (GetFilesystemPathFromShellItem(hitItem, path) && + IsDirectoryPath(path)) { + targetOut.path = std::move(path); + targetOut.kind = IsDriveRootPath(targetOut.path) + ? TargetKind::DriveItem + : TargetKind::FolderItem; + ok = true; + } + hitItem->Release(); + } + } + + if (!ok && IsNavigationPaneContextWindow(hwnd)) { + IShellItemArray* selectedItems = nullptr; + if (SUCCEEDED(navigationPane->GetSelectedItems(&selectedItems)) && + selectedItems) { + std::wstring path; + if (GetSingleFilesystemPathFromShellItemArray(selectedItems, + path) && + IsDirectoryPath(path)) { + targetOut.path = std::move(path); + targetOut.kind = IsDriveRootPath(targetOut.path) + ? TargetKind::DriveItem + : TargetKind::FolderItem; + ok = true; + } + selectedItems->Release(); } - selectedItems->Release(); } navigationPane->Release(); } @@ -1073,7 +1314,7 @@ static bool ResolveMenuTarget(HWND hwnd, MenuTarget& targetOut) { targetOut = {}; - bool isNavigationPane = IsNavigationPaneWindow(hwnd); + bool isNavigationPane = IsNavigationPaneContextWindow(hwnd); if (isNavigationPane) { if (!allowNavigationPane) { return false; @@ -1081,6 +1322,15 @@ static bool ResolveMenuTarget(HWND hwnd, return ResolveNavigationPaneMenuTarget(hwnd, targetOut); } + if (allowNavigationPane && + ResolveNavigationPaneMenuTarget(hwnd, targetOut)) { + return true; + } + + if (!IsShellViewWindow(hwnd)) { + return false; + } + HWND root = GetAncestor(hwnd, GA_ROOT); if (!root) { root = hwnd; @@ -1136,6 +1386,15 @@ static bool ResolveMenuTarget(HWND hwnd, targetOut.kind = IsDriveRootPath(targetOut.path) ? TargetKind::DriveItem : TargetKind::FolderItem; ok = true; + } else if (selectedPaths.size() == 1 && + IsFilePath(selectedPaths[0])) { + Settings snap = GetSettingsSnapshot(); + if (snap.showOnScriptFiles && + IsScriptExtension(selectedPaths[0], snap)) { + targetOut.path = selectedPaths[0]; + targetOut.kind = TargetKind::ScriptItem; + ok = true; + } } } @@ -1408,6 +1667,51 @@ static HBITMAP GetCachedMenuBitmapForTerminal(const Settings& settings) { return bitmap; } +static HBITMAP GetCachedMenuBitmapNoShield(const Settings& settings) { + std::wstring key = GetMenuBitmapCacheKey(settings) + L"\nno-shield"; + + AcquireSRWLockShared(&g_menuBitmapLock); + for (const auto& entry : g_menuBitmapCache) { + if (entry.key == key) { + HBITMAP bitmap = entry.bitmap; + ReleaseSRWLockShared(&g_menuBitmapLock); + return bitmap; + } + } + ReleaseSRWLockShared(&g_menuBitmapLock); + + std::wstring exePath; + HBITMAP bitmap = nullptr; + if (ResolveExecutablePathForIcon(settings, exePath)) { + SHFILEINFOW shfi = {}; + if (SHGetFileInfoW(exePath.c_str(), FILE_ATTRIBUTE_NORMAL, &shfi, + sizeof(shfi), + SHGFI_ICON | SHGFI_SMALLICON | + SHGFI_USEFILEATTRIBUTES)) { + bitmap = CreateMenuBitmapFromIcon(shfi.hIcon, nullptr); + if (shfi.hIcon) { + DestroyIcon(shfi.hIcon); + } + } + } + + AcquireSRWLockExclusive(&g_menuBitmapLock); + for (const auto& entry : g_menuBitmapCache) { + if (entry.key == key) { + if (bitmap) { + DeleteObject(bitmap); + } + bitmap = entry.bitmap; + ReleaseSRWLockExclusive(&g_menuBitmapLock); + return bitmap; + } + } + g_menuBitmapCache.push_back({std::move(key), bitmap}); + ReleaseSRWLockExclusive(&g_menuBitmapLock); + + return bitmap; +} + static void ClearMenuBitmapCache() { AcquireSRWLockExclusive(&g_menuBitmapLock); for (const auto& entry : g_menuBitmapCache) { @@ -1425,7 +1729,40 @@ static void ClearCurrentMenuState() { g_currentMenuHwnd = nullptr; } -static void InsertAdminTerminalMenuItem(HMENU menu, const Settings& settings) { +static std::wstring GetScriptTerminalDisplayName(const Settings& settings, + const std::wstring& scriptPath) { + if (IsScriptHostChoice(settings.terminalEffectiveChoice)) { + return GetTerminalDisplayName(settings); + } + + LaunchSpec interpreter = BuildScriptInterpreterSpec(settings, scriptPath); + if (StrStrIW(interpreter.executable.c_str(), L"pwsh")) { + return L"PowerShell 7"; + } + if (StrStrIW(interpreter.executable.c_str(), L"powershell")) { + return L"Windows PowerShell"; + } + if (StrStrIW(interpreter.executable.c_str(), L"cmd")) { + return L"Command Prompt"; + } + return L"Windows Script Host"; +} + +static Settings GetScriptIconSettings(const Settings& settings, + const std::wstring& scriptPath) { + if (IsScriptHostChoice(settings.terminalEffectiveChoice)) { + return settings; + } + + Settings iconSettings = settings; + LaunchSpec interpreter = BuildScriptInterpreterSpec(settings, scriptPath); + iconSettings.terminalEffectiveChoice.clear(); + iconSettings.terminalDisplayCommand = interpreter.executable; + return iconSettings; +} + +static void InsertAdminTerminalMenuItem(HMENU menu, const Settings& settings, + const MenuTarget& target) { int itemCount = GetMenuItemCount(menu); int insertPos = 0; bool separatorAbove = false; @@ -1457,35 +1794,100 @@ static void InsertAdminTerminalMenuItem(HMENU menu, const Settings& settings) { insertPos = 0; } + bool isScript = target.kind == TargetKind::ScriptItem; + bool showAdmin = !isScript || settings.scriptMenuEntries != L"normal"; + bool showNormal = isScript + ? settings.scriptMenuEntries == L"normal" || + settings.scriptMenuEntries == L"both" + : settings.showNonElevatedEntry; + Settings iconSettings = isScript + ? GetScriptIconSettings(settings, target.path) + : settings; + std::wstring displayName = isScript + ? GetScriptTerminalDisplayName(settings, target.path) + : GetTerminalDisplayName(settings); + std::wstring adminLabel; + if (isScript) { + adminLabel = L"Run script"; + if (settings.appendScriptTerminalName) { + adminLabel += L" in " + displayName; + } + adminLabel += L" as administrator"; + } else { + adminLabel = settings.menuText; + } + std::wstring normalLabel; + if (isScript) { + normalLabel = L"Run script"; + if (settings.appendScriptTerminalName) { + normalLabel += L" in " + displayName; + } + } else { + normalLabel = settings.nonElevatedMenuText; + if (normalLabel.empty()) { + normalLabel = L"Open " + displayName + L" here"; + } + } + + int actionPos = insertPos; if (separatorAbove) { InsertMenuW(menu, insertPos, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); - InsertMenuW(menu, insertPos + 1, MF_BYPOSITION | MF_STRING, kMenuCommandId, - settings.menuText.c_str()); - } else { - InsertMenuW(menu, insertPos, MF_BYPOSITION | MF_STRING, kMenuCommandId, - settings.menuText.c_str()); - InsertMenuW(menu, insertPos + 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); - } - - HBITMAP menuBitmap = GetCachedMenuBitmapForTerminal(settings); - if (menuBitmap) { - MENUITEMINFOW itemInfo = {}; - itemInfo.cbSize = sizeof(itemInfo); - itemInfo.fMask = MIIM_BITMAP; - itemInfo.hbmpItem = menuBitmap; - if (!SetMenuItemInfoW(menu, kMenuCommandId, FALSE, &itemInfo)) { - Wh_Log(L"Menu icon assignment failed"); + actionPos++; + } + + if (showAdmin) { + InsertMenuW(menu, actionPos++, MF_BYPOSITION | MF_STRING, kMenuCommandId, + adminLabel.c_str()); + } + if (showNormal) { + InsertMenuW(menu, actionPos++, MF_BYPOSITION | MF_STRING, + kMenuCommandIdNonElevated, normalLabel.c_str()); + } + if (!separatorAbove) { + InsertMenuW(menu, actionPos, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); + } + + if (showAdmin) { + HBITMAP menuBitmap = GetCachedMenuBitmapForTerminal(iconSettings); + if (menuBitmap) { + MENUITEMINFOW itemInfo = {}; + itemInfo.cbSize = sizeof(itemInfo); + itemInfo.fMask = MIIM_BITMAP; + itemInfo.hbmpItem = menuBitmap; + if (!SetMenuItemInfoW(menu, kMenuCommandId, FALSE, &itemInfo)) { + Wh_Log(L"Menu icon assignment failed"); + } else { + Wh_Log(L"Menu icon assigned"); + } } else { - Wh_Log(L"Menu icon assigned"); + Wh_Log(L"Menu icon unavailable"); + } + } + + if (showNormal) { + HBITMAP menuBitmapNoShield = GetCachedMenuBitmapNoShield(iconSettings); + if (menuBitmapNoShield) { + MENUITEMINFOW itemInfo = {}; + itemInfo.cbSize = sizeof(itemInfo); + itemInfo.fMask = MIIM_BITMAP; + itemInfo.hbmpItem = menuBitmapNoShield; + if (!SetMenuItemInfoW(menu, kMenuCommandIdNonElevated, FALSE, + &itemInfo)) { + Wh_Log(L"Non-elevated menu icon assignment failed"); + } else { + Wh_Log(L"Non-elevated menu icon assigned"); + } + } else { + Wh_Log(L"Non-elevated menu icon unavailable"); } - } else { - Wh_Log(L"Menu icon unavailable"); } } static void LaunchAdminTerminal(const MenuTarget& target) { Settings settings = GetSettingsSnapshot(); - LaunchSpec launch = BuildLaunchSpec(settings, target.path); + LaunchSpec launch = (target.kind == TargetKind::ScriptItem) + ? BuildScriptLaunchSpec(settings, target.path) + : BuildLaunchSpec(settings, target.path); SHELLEXECUTEINFOW executeInfo = {}; executeInfo.cbSize = sizeof(executeInfo); @@ -1518,6 +1920,43 @@ static void LaunchAdminTerminal(const MenuTarget& target) { } } +static void LaunchTerminalNonElevated(const MenuTarget& target) { + Settings settings = GetSettingsSnapshot(); + LaunchSpec launch = (target.kind == TargetKind::ScriptItem) + ? BuildScriptLaunchSpec(settings, target.path) + : BuildLaunchSpec(settings, target.path); + + SHELLEXECUTEINFOW executeInfo = {}; + executeInfo.cbSize = sizeof(executeInfo); + executeInfo.fMask = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI; + executeInfo.lpVerb = L"open"; + executeInfo.lpFile = launch.executable.c_str(); + executeInfo.lpParameters = + launch.parameters.empty() ? nullptr : launch.parameters.c_str(); + executeInfo.lpDirectory = + launch.workingDirectory.empty() ? nullptr : launch.workingDirectory.c_str(); + executeInfo.nShow = SW_SHOWNORMAL; + + POINT cursorPos; + if (GetCursorPos(&cursorPos)) { + executeInfo.fMask |= SEE_MASK_HMONITOR; + executeInfo.hMonitor = MonitorFromPoint(cursorPos, MONITOR_DEFAULTTONEAREST); + } + + if (ShellExecuteExW(&executeInfo)) { + Wh_Log(L"Launch succeeded: target=%ls executable=%ls parameters=%ls", + target.path.c_str(), launch.executable.c_str(), + launch.parameters.c_str()); + } else { + DWORD error = GetLastError(); + if (error != ERROR_CANCELLED) { + Wh_Log(L"Launch failed: error=%lu target=%ls executable=%ls parameters=%ls", + error, target.path.c_str(), launch.executable.c_str(), + launch.parameters.c_str()); + } + } +} + BOOL WINAPI TrackPopupMenuEx_Hook(HMENU menu, UINT flags, int x, @@ -1527,8 +1966,7 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU menu, bool injected = false; ClearCurrentMenuState(); - if (menu && hwnd && - (IsShellViewWindow(hwnd) || IsNavigationPaneWindow(hwnd))) { + if (menu && hwnd) { MenuTarget target; Settings settings = GetSettingsSnapshot(); if (!ResolveMenuTarget(hwnd, settings.showOnNavigationPane, target)) { @@ -1545,9 +1983,9 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU menu, } Wh_Log(L"Injection target: kind=%ls path=%ls", TargetKindName(target.kind), target.path.c_str()); - InsertAdminTerminalMenuItem(menu, settings); - Wh_Log(L"Injection inserted: position=%ls text=%ls", - settings.position.c_str(), settings.menuText.c_str()); + InsertAdminTerminalMenuItem(menu, settings, target); + Wh_Log(L"Injection inserted: position=%ls kind=%ls", + settings.position.c_str(), TargetKindName(target.kind)); injected = true; g_currentMenuHwnd = hwnd; g_currentMenuEligible = true; @@ -1565,6 +2003,14 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU menu, return 0; } + if (injected && (flags & TPM_RETURNCMD) && + static_cast(result) == kMenuCommandIdNonElevated) { + MenuTarget target = g_currentTarget; + ClearCurrentMenuState(); + LaunchTerminalNonElevated(target); + return 0; + } + return result; } @@ -1579,11 +2025,21 @@ BOOL WINAPI PostMessageW_Hook(HWND hwnd, UINT message, WPARAM wParam, LPARAM lPa return TRUE; } + if (message == WM_COMMAND && + LOWORD(wParam) == kMenuCommandIdNonElevated && + g_currentMenuEligible && + hwnd == g_currentMenuHwnd) { + MenuTarget target = g_currentTarget; + ClearCurrentMenuState(); + LaunchTerminalNonElevated(target); + return TRUE; + } + return PostMessageW_Orig(hwnd, message, wParam, lParam); } BOOL Wh_ModInit() { - Wh_Log(L"Init v1.16-classic"); + Wh_Log(L"Init"); g_shellIdListClipboardFormat = RegisterClipboardFormatW(L"Shell IDList Array"); From 3221a1f08109d3c154ef74812c69348c1a5b5d20 Mon Sep 17 00:00:00 2001 From: Federico Michel <104396772+aimagist@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:01:09 -0300 Subject: [PATCH 2/7] Address v1.17.1 review feedback --- mods/open-in-admin-terminal.wh.cpp | 183 ++++++++++++++--------------- 1 file changed, 87 insertions(+), 96 deletions(-) diff --git a/mods/open-in-admin-terminal.wh.cpp b/mods/open-in-admin-terminal.wh.cpp index fb4f0dd6cb..32875285ae 100644 --- a/mods/open-in-admin-terminal.wh.cpp +++ b/mods/open-in-admin-terminal.wh.cpp @@ -133,13 +133,13 @@ Screenshots may show earlier builds, but current releases use runtime classic-me $description: Add the selected terminal name to script actions. Disable for shorter labels. - scriptExtensions: ".ps1;.bat;.cmd;.vbs;.js" $name: Script extensions - $description: Semicolon-separated extensions treated as scripts. + $description: Semicolon-separated filter for supported script extensions: .ps1, .bat, .cmd, .vbs, and .js. - keepOpenAfterScript: true $name: Keep terminal open after script $description: Terminal stays open after script finishes. -- scriptExecutionPolicyBypass: true +- scriptExecutionPolicyBypass: false $name: Bypass execution policy for .ps1 - $description: Adds -ExecutionPolicy Bypass for PowerShell scripts. + $description: Adds -ExecutionPolicy Bypass for PowerShell scripts. Disabled by default. */ // ==/WindhawkModSettings== @@ -572,6 +572,26 @@ static bool IsTargetEnabled(const Settings& s, TargetKind kind) { return false; } +static bool ResolveSystemExecutablePath(PCWSTR exe, std::wstring& pathOut) { + pathOut.clear(); + + WCHAR systemDirectory[MAX_PATH] = {}; + UINT length = GetSystemDirectoryW(systemDirectory, ARRAYSIZE(systemDirectory)); + if (!length || length >= ARRAYSIZE(systemDirectory)) { + return false; + } + + pathOut.assign(systemDirectory, length); + pathOut += L'\\'; + pathOut += exe; + if (!IsFilePath(pathOut)) { + pathOut.clear(); + return false; + } + + return true; +} + static PCWSTR TargetKindName(TargetKind kind) { if (kind == TargetKind::FolderBackground) { return L"folder-background"; @@ -822,7 +842,7 @@ static LaunchSpec BuildScriptInterpreterSpec(const Settings& s, s.terminalEffectiveChoice == L"powershell") { spec.executable = s.terminalDisplayCommand; } else if (!ResolveTerminalChoiceExecutable(L"pwsh", spec.executable)) { - spec.executable = L"powershell.exe"; + ResolveTerminalChoiceExecutable(L"powershell", spec.executable); } std::vector args; if (s.keepOpenAfterScript) { @@ -837,19 +857,24 @@ static LaunchSpec BuildScriptInterpreterSpec(const Settings& s, spec.parameters = JoinCommandLineArguments(args); } else if (_wcsicmp(ext, L".bat") == 0 || _wcsicmp(ext, L".cmd") == 0) { - spec.executable = L"cmd.exe"; + ResolveSystemExecutablePath(L"cmd.exe", spec.executable); spec.parameters = std::wstring(s.keepOpenAfterScript ? L"/k " : L"/c ") + QuoteCommandLineArgument(scriptPath); } else if (_wcsicmp(ext, L".vbs") == 0 || _wcsicmp(ext, L".js") == 0) { - std::wstring scriptCommand = - L"cscript.exe //nologo " + QuoteCommandLineArgument(scriptPath); + std::wstring cscriptPath; + if (!ResolveSystemExecutablePath(L"cscript.exe", cscriptPath)) { + return spec; + } if (s.keepOpenAfterScript) { - spec.executable = L"cmd.exe"; - spec.parameters = L"/k " + scriptCommand; + if (!ResolveSystemExecutablePath(L"cmd.exe", spec.executable)) { + return {}; + } + spec.parameters = L"/k " + QuoteCommandLineArgument(cscriptPath) + + L" //nologo " + QuoteCommandLineArgument(scriptPath); } else { - spec.executable = L"cscript.exe"; + spec.executable = std::move(cscriptPath); spec.parameters = L"//nologo " + QuoteCommandLineArgument(scriptPath); } } @@ -1169,28 +1194,6 @@ static bool GetFilesystemPathFromShellItem(IShellItem* item, return ok; } -static bool GetSingleFilesystemPathFromShellItemArray(IShellItemArray* items, - std::wstring& pathOut) { - pathOut.clear(); - if (!items) { - return false; - } - - DWORD count = 0; - if (FAILED(items->GetCount(&count)) || count != 1) { - return false; - } - - IShellItem* item = nullptr; - if (FAILED(items->GetItemAt(0, &item)) || !item) { - return false; - } - - bool ok = GetFilesystemPathFromShellItem(item, pathOut); - item->Release(); - return ok; -} - static bool IsNavigationPaneWindow(HWND hwnd) { bool sawNavigationTreeClass = false; HWND w = hwnd; @@ -1216,28 +1219,14 @@ static bool IsNavigationPaneWindow(HWND hwnd) { return false; } -static bool IsNavigationPaneContextWindow(HWND hwnd) { - if (IsNavigationPaneWindow(hwnd)) { - return true; - } - - HWND focus = GetFocus(); - if (focus && IsNavigationPaneWindow(focus)) { - return true; - } - - POINT cursorPos; - if (GetCursorPos(&cursorPos)) { - HWND underCursor = WindowFromPoint(cursorPos); - if (underCursor && IsNavigationPaneWindow(underCursor)) { - return true; - } - } - - return false; +static bool IsNavigationPaneContextWindow(const POINT& invocationPoint) { + HWND invocationWindow = WindowFromPoint(invocationPoint); + return invocationWindow && IsNavigationPaneWindow(invocationWindow); } -static bool ResolveNavigationPaneMenuTarget(HWND hwnd, MenuTarget& targetOut) { +static bool ResolveNavigationPaneMenuTarget(HWND hwnd, + const POINT& invocationPoint, + MenuTarget& targetOut) { targetOut = {}; HWND root = GetAncestor(hwnd, GA_ROOT); @@ -1266,11 +1255,10 @@ static bool ResolveNavigationPaneMenuTarget(HWND hwnd, MenuTarget& targetOut) { shellBrowser->Release(); } - POINT cursorPos; - if (treeWindow && GetCursorPos(&cursorPos) && - ScreenToClient(treeWindow, &cursorPos)) { + POINT clientPoint = invocationPoint; + if (treeWindow && ScreenToClient(treeWindow, &clientPoint)) { IShellItem* hitItem = nullptr; - if (SUCCEEDED(navigationPane->HitTest(&cursorPos, &hitItem)) && + if (SUCCEEDED(navigationPane->HitTest(&clientPoint, &hitItem)) && hitItem) { std::wstring path; if (GetFilesystemPathFromShellItem(hitItem, path) && @@ -1285,23 +1273,6 @@ static bool ResolveNavigationPaneMenuTarget(HWND hwnd, MenuTarget& targetOut) { } } - if (!ok && IsNavigationPaneContextWindow(hwnd)) { - IShellItemArray* selectedItems = nullptr; - if (SUCCEEDED(navigationPane->GetSelectedItems(&selectedItems)) && - selectedItems) { - std::wstring path; - if (GetSingleFilesystemPathFromShellItemArray(selectedItems, - path) && - IsDirectoryPath(path)) { - targetOut.path = std::move(path); - targetOut.kind = IsDriveRootPath(targetOut.path) - ? TargetKind::DriveItem - : TargetKind::FolderItem; - ok = true; - } - selectedItems->Release(); - } - } navigationPane->Release(); } @@ -1310,21 +1281,17 @@ static bool ResolveNavigationPaneMenuTarget(HWND hwnd, MenuTarget& targetOut) { } static bool ResolveMenuTarget(HWND hwnd, - bool allowNavigationPane, + const Settings& settings, + const POINT& invocationPoint, MenuTarget& targetOut) { targetOut = {}; - bool isNavigationPane = IsNavigationPaneContextWindow(hwnd); + bool isNavigationPane = IsNavigationPaneContextWindow(invocationPoint); if (isNavigationPane) { - if (!allowNavigationPane) { + if (!settings.showOnNavigationPane) { return false; } - return ResolveNavigationPaneMenuTarget(hwnd, targetOut); - } - - if (allowNavigationPane && - ResolveNavigationPaneMenuTarget(hwnd, targetOut)) { - return true; + return ResolveNavigationPaneMenuTarget(hwnd, invocationPoint, targetOut); } if (!IsShellViewWindow(hwnd)) { @@ -1388,9 +1355,8 @@ static bool ResolveMenuTarget(HWND hwnd, ok = true; } else if (selectedPaths.size() == 1 && IsFilePath(selectedPaths[0])) { - Settings snap = GetSettingsSnapshot(); - if (snap.showOnScriptFiles && - IsScriptExtension(selectedPaths[0], snap)) { + if (settings.showOnScriptFiles && + IsScriptExtension(selectedPaths[0], settings)) { targetOut.path = selectedPaths[0]; targetOut.kind = TargetKind::ScriptItem; ok = true; @@ -1735,17 +1701,20 @@ static std::wstring GetScriptTerminalDisplayName(const Settings& settings, return GetTerminalDisplayName(settings); } - LaunchSpec interpreter = BuildScriptInterpreterSpec(settings, scriptPath); - if (StrStrIW(interpreter.executable.c_str(), L"pwsh")) { - return L"PowerShell 7"; - } - if (StrStrIW(interpreter.executable.c_str(), L"powershell")) { - return L"Windows PowerShell"; - } - if (StrStrIW(interpreter.executable.c_str(), L"cmd")) { + PCWSTR extension = PathFindExtensionW(scriptPath.c_str()); + if (_wcsicmp(extension, L".bat") == 0 || + _wcsicmp(extension, L".cmd") == 0) { return L"Command Prompt"; } - return L"Windows Script Host"; + if (_wcsicmp(extension, L".vbs") == 0 || + _wcsicmp(extension, L".js") == 0) { + return L"Windows Script Host"; + } + + LaunchSpec interpreter = BuildScriptInterpreterSpec(settings, scriptPath); + PCWSTR executableName = PathFindFileNameW(interpreter.executable.c_str()); + return _wcsicmp(executableName, L"pwsh.exe") == 0 ? L"PowerShell 7" + : L"Windows PowerShell"; } static Settings GetScriptIconSettings(const Settings& settings, @@ -1888,6 +1857,11 @@ static void LaunchAdminTerminal(const MenuTarget& target) { LaunchSpec launch = (target.kind == TargetKind::ScriptItem) ? BuildScriptLaunchSpec(settings, target.path) : BuildLaunchSpec(settings, target.path); + if (launch.executable.empty()) { + Wh_Log(L"Launch failed: no executable resolved target=%ls", + target.path.c_str()); + return; + } SHELLEXECUTEINFOW executeInfo = {}; executeInfo.cbSize = sizeof(executeInfo); @@ -1925,6 +1899,11 @@ static void LaunchTerminalNonElevated(const MenuTarget& target) { LaunchSpec launch = (target.kind == TargetKind::ScriptItem) ? BuildScriptLaunchSpec(settings, target.path) : BuildLaunchSpec(settings, target.path); + if (launch.executable.empty()) { + Wh_Log(L"Launch failed: no executable resolved target=%ls", + target.path.c_str()); + return; + } SHELLEXECUTEINFOW executeInfo = {}; executeInfo.cbSize = sizeof(executeInfo); @@ -1966,10 +1945,21 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU menu, bool injected = false; ClearCurrentMenuState(); - if (menu && hwnd) { + HWND root = hwnd ? GetAncestor(hwnd, GA_ROOT) : nullptr; + WCHAR rootClass[64] = {}; + if (root) { + GetClassNameW(root, rootClass, ARRAYSIZE(rootClass)); + } + bool eligibleWindow = hwnd && + (IsShellViewWindow(hwnd) || + _wcsicmp(rootClass, L"CabinetWClass") == 0 || + _wcsicmp(rootClass, L"ExploreWClass") == 0); + + if (menu && eligibleWindow) { MenuTarget target; Settings settings = GetSettingsSnapshot(); - if (!ResolveMenuTarget(hwnd, settings.showOnNavigationPane, target)) { + POINT invocationPoint = {x, y}; + if (!ResolveMenuTarget(hwnd, settings, invocationPoint, target)) { Wh_Log(L"Injection skipped: no eligible filesystem directory target"); } else if (!IsTargetEnabled(settings, target.kind)) { Wh_Log(L"Injection skipped: target kind disabled kind=%ls path=%ls", @@ -2011,6 +2001,7 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU menu, return 0; } + ClearCurrentMenuState(); return result; } From 11496294cfc2fab7f5c8340bdf8d61ff55b4639d Mon Sep 17 00:00:00 2001 From: Federico Michel <104396772+aimagist@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:59:22 -0300 Subject: [PATCH 3/7] Address second v1.17.1 review --- mods/open-in-admin-terminal.wh.cpp | 55 ++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/mods/open-in-admin-terminal.wh.cpp b/mods/open-in-admin-terminal.wh.cpp index 32875285ae..51ee8c59bd 100644 --- a/mods/open-in-admin-terminal.wh.cpp +++ b/mods/open-in-admin-terminal.wh.cpp @@ -685,6 +685,10 @@ static std::wstring QuoteCommandLineArgument(const std::wstring& arg) { return result; } +static std::wstring QuoteCmdPath(const std::wstring& path) { + return L'"' + path + L'"'; +} + static std::wstring JoinCommandLineArguments( const std::vector& args) { std::wstring result; @@ -800,7 +804,7 @@ static LaunchSpec BuildLaunchSpec(const Settings& s, const std::wstring& target) return spec; } if (choice == L"cmd") { - spec.parameters = L"/k cd /d " + QuoteCommandLineArgument(target); + spec.parameters = L"/k cd /d " + QuoteCmdPath(target); return spec; } if (choice == L"wsl") { @@ -860,7 +864,7 @@ static LaunchSpec BuildScriptInterpreterSpec(const Settings& s, ResolveSystemExecutablePath(L"cmd.exe", spec.executable); spec.parameters = std::wstring(s.keepOpenAfterScript ? L"/k " : L"/c ") + - QuoteCommandLineArgument(scriptPath); + QuoteCmdPath(scriptPath); } else if (_wcsicmp(ext, L".vbs") == 0 || _wcsicmp(ext, L".js") == 0) { std::wstring cscriptPath; @@ -871,8 +875,8 @@ static LaunchSpec BuildScriptInterpreterSpec(const Settings& s, if (!ResolveSystemExecutablePath(L"cmd.exe", spec.executable)) { return {}; } - spec.parameters = L"/k " + QuoteCommandLineArgument(cscriptPath) + - L" //nologo " + QuoteCommandLineArgument(scriptPath); + spec.parameters = L"/k " + QuoteCmdPath(cscriptPath) + + L" //nologo " + QuoteCmdPath(scriptPath); } else { spec.executable = std::move(cscriptPath); spec.parameters = L"//nologo " + QuoteCommandLineArgument(scriptPath); @@ -1219,7 +1223,16 @@ static bool IsNavigationPaneWindow(HWND hwnd) { return false; } +static bool IsKeyboardContextMenuPoint(const POINT& invocationPoint) { + return invocationPoint.x == -1 && invocationPoint.y == -1; +} + static bool IsNavigationPaneContextWindow(const POINT& invocationPoint) { + if (IsKeyboardContextMenuPoint(invocationPoint)) { + HWND focusedWindow = GetFocus(); + return focusedWindow && IsNavigationPaneWindow(focusedWindow); + } + HWND invocationWindow = WindowFromPoint(invocationPoint); return invocationWindow && IsNavigationPaneWindow(invocationWindow); } @@ -1256,7 +1269,8 @@ static bool ResolveNavigationPaneMenuTarget(HWND hwnd, } POINT clientPoint = invocationPoint; - if (treeWindow && ScreenToClient(treeWindow, &clientPoint)) { + if (!IsKeyboardContextMenuPoint(invocationPoint) && treeWindow && + ScreenToClient(treeWindow, &clientPoint)) { IShellItem* hitItem = nullptr; if (SUCCEEDED(navigationPane->HitTest(&clientPoint, &hitItem)) && hitItem) { @@ -1273,6 +1287,29 @@ static bool ResolveNavigationPaneMenuTarget(HWND hwnd, } } + if (!ok && IsKeyboardContextMenuPoint(invocationPoint)) { + IShellItemArray* selectedItems = nullptr; + if (SUCCEEDED(navigationPane->GetSelectedItems(&selectedItems)) && + selectedItems) { + DWORD count = 0; + IShellItem* item = nullptr; + if (SUCCEEDED(selectedItems->GetCount(&count)) && count == 1 && + SUCCEEDED(selectedItems->GetItemAt(0, &item)) && item) { + std::wstring path; + if (GetFilesystemPathFromShellItem(item, path) && + IsDirectoryPath(path)) { + targetOut.path = std::move(path); + targetOut.kind = IsDriveRootPath(targetOut.path) + ? TargetKind::DriveItem + : TargetKind::FolderItem; + ok = true; + } + item->Release(); + } + selectedItems->Release(); + } + } + navigationPane->Release(); } @@ -1695,6 +1732,10 @@ static void ClearCurrentMenuState() { g_currentMenuHwnd = nullptr; } +static bool ShouldClearMenuStateAfterTracking(UINT flags, BOOL result) { + return (flags & TPM_RETURNCMD) || !result; +} + static std::wstring GetScriptTerminalDisplayName(const Settings& settings, const std::wstring& scriptPath) { if (IsScriptHostChoice(settings.terminalEffectiveChoice)) { @@ -2001,7 +2042,9 @@ BOOL WINAPI TrackPopupMenuEx_Hook(HMENU menu, return 0; } - ClearCurrentMenuState(); + if (ShouldClearMenuStateAfterTracking(flags, result)) { + ClearCurrentMenuState(); + } return result; } From c6cc016532f8a7646bee46fb8f3e6e80313d81f2 Mon Sep 17 00:00:00 2001 From: Federico Michel <104396772+aimagist@users.noreply.github.com> Date: Mon, 3 Aug 2026 21:00:19 -0300 Subject: [PATCH 4/7] Fix cmd command-line wrapping --- mods/open-in-admin-terminal.wh.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/mods/open-in-admin-terminal.wh.cpp b/mods/open-in-admin-terminal.wh.cpp index 51ee8c59bd..5121b18941 100644 --- a/mods/open-in-admin-terminal.wh.cpp +++ b/mods/open-in-admin-terminal.wh.cpp @@ -689,6 +689,13 @@ static std::wstring QuoteCmdPath(const std::wstring& path) { return L'"' + path + L'"'; } +static std::wstring BuildCmdCommandLine(bool keepOpen, + const std::wstring& command) { + // /s makes cmd strip the outer pair, preserving quotes inside the command. + return std::wstring(keepOpen ? L"/s /k \"" : L"/s /c \"") + command + + L'"'; +} + static std::wstring JoinCommandLineArguments( const std::vector& args) { std::wstring result; @@ -804,7 +811,8 @@ static LaunchSpec BuildLaunchSpec(const Settings& s, const std::wstring& target) return spec; } if (choice == L"cmd") { - spec.parameters = L"/k cd /d " + QuoteCmdPath(target); + spec.parameters = + BuildCmdCommandLine(true, L"cd /d " + QuoteCmdPath(target)); return spec; } if (choice == L"wsl") { @@ -862,9 +870,8 @@ static LaunchSpec BuildScriptInterpreterSpec(const Settings& s, } else if (_wcsicmp(ext, L".bat") == 0 || _wcsicmp(ext, L".cmd") == 0) { ResolveSystemExecutablePath(L"cmd.exe", spec.executable); - spec.parameters = - std::wstring(s.keepOpenAfterScript ? L"/k " : L"/c ") + - QuoteCmdPath(scriptPath); + spec.parameters = BuildCmdCommandLine(s.keepOpenAfterScript, + QuoteCmdPath(scriptPath)); } else if (_wcsicmp(ext, L".vbs") == 0 || _wcsicmp(ext, L".js") == 0) { std::wstring cscriptPath; @@ -875,8 +882,9 @@ static LaunchSpec BuildScriptInterpreterSpec(const Settings& s, if (!ResolveSystemExecutablePath(L"cmd.exe", spec.executable)) { return {}; } - spec.parameters = L"/k " + QuoteCmdPath(cscriptPath) + - L" //nologo " + QuoteCmdPath(scriptPath); + spec.parameters = BuildCmdCommandLine( + true, QuoteCmdPath(cscriptPath) + L" //nologo " + + QuoteCmdPath(scriptPath)); } else { spec.executable = std::move(cscriptPath); spec.parameters = L"//nologo " + QuoteCommandLineArgument(scriptPath); From 6b442f77211d9ae722c72c4cf9ba26986c1b7061 Mon Sep 17 00:00:00 2001 From: Federico Michel <104396772+aimagist@users.noreply.github.com> Date: Tue, 4 Aug 2026 07:29:10 -0300 Subject: [PATCH 5/7] Address third v1.17.1 review --- mods/open-in-admin-terminal.wh.cpp | 50 ++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/mods/open-in-admin-terminal.wh.cpp b/mods/open-in-admin-terminal.wh.cpp index 5121b18941..253944e3f7 100644 --- a/mods/open-in-admin-terminal.wh.cpp +++ b/mods/open-in-admin-terminal.wh.cpp @@ -901,7 +901,13 @@ static bool IsScriptHostChoice(const std::wstring& choice) { static LaunchSpec BuildScriptLaunchSpec(const Settings& s, const std::wstring& scriptPath) { LaunchSpec interpreter = BuildScriptInterpreterSpec(s, scriptPath); - if (!IsScriptHostChoice(s.terminalEffectiveChoice)) { + PCWSTR ext = PathFindExtensionW(scriptPath.c_str()); + bool usesCmdWrapper = _wcsicmp(ext, L".bat") == 0 || + _wcsicmp(ext, L".cmd") == 0 || + (s.keepOpenAfterScript && + (_wcsicmp(ext, L".vbs") == 0 || + _wcsicmp(ext, L".js") == 0)); + if (!IsScriptHostChoice(s.terminalEffectiveChoice) || usesCmdWrapper) { return interpreter; } @@ -1235,18 +1241,39 @@ static bool IsKeyboardContextMenuPoint(const POINT& invocationPoint) { return invocationPoint.x == -1 && invocationPoint.y == -1; } -static bool IsNavigationPaneContextWindow(const POINT& invocationPoint) { - if (IsKeyboardContextMenuPoint(invocationPoint)) { - HWND focusedWindow = GetFocus(); - return focusedWindow && IsNavigationPaneWindow(focusedWindow); +static bool ShouldUseFocusedNavigationPaneFallback( + bool invocationPointIsNavigationPane, + bool focusedWindowIsNavigationPane, + bool ownerIsShellView) { + return !invocationPointIsNavigationPane && focusedWindowIsNavigationPane && + !ownerIsShellView; +} + +static bool IsNavigationPaneContextWindow(HWND hwnd, + const POINT& invocationPoint, + bool& useSelectionFallback) { + useSelectionFallback = false; + bool invocationPointIsNavigationPane = false; + if (!IsKeyboardContextMenuPoint(invocationPoint)) { + HWND invocationWindow = WindowFromPoint(invocationPoint); + invocationPointIsNavigationPane = + invocationWindow && IsNavigationPaneWindow(invocationWindow); + if (invocationPointIsNavigationPane) { + return true; + } } - HWND invocationWindow = WindowFromPoint(invocationPoint); - return invocationWindow && IsNavigationPaneWindow(invocationWindow); + HWND focusedWindow = GetFocus(); + useSelectionFallback = ShouldUseFocusedNavigationPaneFallback( + invocationPointIsNavigationPane, + focusedWindow && IsNavigationPaneWindow(focusedWindow), + IsShellViewWindow(hwnd)); + return useSelectionFallback; } static bool ResolveNavigationPaneMenuTarget(HWND hwnd, const POINT& invocationPoint, + bool useSelectionFallback, MenuTarget& targetOut) { targetOut = {}; @@ -1295,7 +1322,7 @@ static bool ResolveNavigationPaneMenuTarget(HWND hwnd, } } - if (!ok && IsKeyboardContextMenuPoint(invocationPoint)) { + if (!ok && useSelectionFallback) { IShellItemArray* selectedItems = nullptr; if (SUCCEEDED(navigationPane->GetSelectedItems(&selectedItems)) && selectedItems) { @@ -1331,12 +1358,15 @@ static bool ResolveMenuTarget(HWND hwnd, MenuTarget& targetOut) { targetOut = {}; - bool isNavigationPane = IsNavigationPaneContextWindow(invocationPoint); + bool useNavigationPaneSelectionFallback = false; + bool isNavigationPane = IsNavigationPaneContextWindow( + hwnd, invocationPoint, useNavigationPaneSelectionFallback); if (isNavigationPane) { if (!settings.showOnNavigationPane) { return false; } - return ResolveNavigationPaneMenuTarget(hwnd, invocationPoint, targetOut); + return ResolveNavigationPaneMenuTarget( + hwnd, invocationPoint, useNavigationPaneSelectionFallback, targetOut); } if (!IsShellViewWindow(hwnd)) { From e8b8cb28eebeb192f165c1bc8ed1b140e3bfef19 Mon Sep 17 00:00:00 2001 From: Federico Michel <104396772+aimagist@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:49:05 -0300 Subject: [PATCH 6/7] Align script presentation with direct launches --- mods/open-in-admin-terminal.wh.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/mods/open-in-admin-terminal.wh.cpp b/mods/open-in-admin-terminal.wh.cpp index 253944e3f7..b93c8536ba 100644 --- a/mods/open-in-admin-terminal.wh.cpp +++ b/mods/open-in-admin-terminal.wh.cpp @@ -49,7 +49,7 @@ Screenshots may show earlier builds, but current releases use runtime classic-me - The entry is injected only while Explorer's classic menu is open; disabling the mod leaves no registry cleanup behind. - The mod intentionally targets filesystem folders and drive roots only, including optional navigation pane and Quick access support. - Auto chooses Windows Terminal, PowerShell 7, Windows PowerShell, then Command Prompt. If another built-in preset is unavailable, the mod falls back to Auto instead of hiding the entry. -- Script actions use the selected terminal when it can host the required Windows interpreter. WSL, Git Bash, and custom commands fall back to PowerShell, Command Prompt, or Windows Script Host and use that actual program name in the menu. +- Script actions use the selected terminal only when it can safely host the required interpreter. Batch scripts and keep-open Windows Script Host scripts launch through Command Prompt; menu labels show Command Prompt or Windows Script Host. WSL, Git Bash, and custom commands fall back to a compatible Windows interpreter. - Diagnostics use Windhawk's built-in logging controls. ## Version log @@ -898,16 +898,24 @@ static bool IsScriptHostChoice(const std::wstring& choice) { choice == L"alacritty" || choice == L"conemu"; } +static bool UsesCmdWrapper(const Settings& s, PCWSTR extension) { + return _wcsicmp(extension, L".bat") == 0 || + _wcsicmp(extension, L".cmd") == 0 || + (s.keepOpenAfterScript && + (_wcsicmp(extension, L".vbs") == 0 || + _wcsicmp(extension, L".js") == 0)); +} + +static bool UsesSelectedTerminalHost(const Settings& s, + const std::wstring& scriptPath) { + return IsScriptHostChoice(s.terminalEffectiveChoice) && + !UsesCmdWrapper(s, PathFindExtensionW(scriptPath.c_str())); +} + static LaunchSpec BuildScriptLaunchSpec(const Settings& s, const std::wstring& scriptPath) { LaunchSpec interpreter = BuildScriptInterpreterSpec(s, scriptPath); - PCWSTR ext = PathFindExtensionW(scriptPath.c_str()); - bool usesCmdWrapper = _wcsicmp(ext, L".bat") == 0 || - _wcsicmp(ext, L".cmd") == 0 || - (s.keepOpenAfterScript && - (_wcsicmp(ext, L".vbs") == 0 || - _wcsicmp(ext, L".js") == 0)); - if (!IsScriptHostChoice(s.terminalEffectiveChoice) || usesCmdWrapper) { + if (!UsesSelectedTerminalHost(s, scriptPath)) { return interpreter; } @@ -1776,7 +1784,7 @@ static bool ShouldClearMenuStateAfterTracking(UINT flags, BOOL result) { static std::wstring GetScriptTerminalDisplayName(const Settings& settings, const std::wstring& scriptPath) { - if (IsScriptHostChoice(settings.terminalEffectiveChoice)) { + if (UsesSelectedTerminalHost(settings, scriptPath)) { return GetTerminalDisplayName(settings); } @@ -1798,7 +1806,7 @@ static std::wstring GetScriptTerminalDisplayName(const Settings& settings, static Settings GetScriptIconSettings(const Settings& settings, const std::wstring& scriptPath) { - if (IsScriptHostChoice(settings.terminalEffectiveChoice)) { + if (UsesSelectedTerminalHost(settings, scriptPath)) { return settings; } From f532b3dbadf322a01c69b41c4e27391a333208ee Mon Sep 17 00:00:00 2001 From: Federico Michel <104396772+aimagist@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:05:05 -0300 Subject: [PATCH 7/7] Fix desktop fallback for unsupported selections --- mods/open-in-admin-terminal.wh.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/mods/open-in-admin-terminal.wh.cpp b/mods/open-in-admin-terminal.wh.cpp index b93c8536ba..3a41c0a3cb 100644 --- a/mods/open-in-admin-terminal.wh.cpp +++ b/mods/open-in-admin-terminal.wh.cpp @@ -1360,6 +1360,12 @@ static bool ResolveNavigationPaneMenuTarget(HWND hwnd, return ok; } +static bool ShouldUseDesktopFolderFallback(bool targetResolved, + bool sawSelection, + bool isDesktopShellView) { + return !targetResolved && !sawSelection && isDesktopShellView; +} + static bool ResolveMenuTarget(HWND hwnd, const Settings& settings, const POINT& invocationPoint, @@ -1396,6 +1402,7 @@ static bool ResolveMenuTarget(HWND hwnd, } bool ok = false; + bool sawSelection = false; IFolderView* folderView = nullptr; if (SUCCEEDED(shellView->QueryInterface(IID_IFolderView, reinterpret_cast(&folderView))) && @@ -1403,6 +1410,9 @@ static bool ResolveMenuTarget(HWND hwnd, int selectedCount = 0; bool hasSelectedCount = SUCCEEDED(folderView->ItemCount(SVGIO_SELECTION, &selectedCount)); + if (hasSelectedCount && selectedCount > 0) { + sawSelection = true; + } if (hasSelectedCount && selectedCount > 1) { ok = false; @@ -1417,6 +1427,12 @@ static bool ResolveMenuTarget(HWND hwnd, } else { std::vector selectedPaths; UINT shellSelectedCount = GetSelectedPaths(shellView, selectedPaths, 2); + if (shellSelectedCount > 0) { + sawSelection = true; + } + if (!selectedPaths.empty()) { + sawSelection = true; + } if (selectedPaths.empty()) { if (!hasSelectedCount && shellSelectedCount == 0) { @@ -1450,7 +1466,8 @@ static bool ResolveMenuTarget(HWND hwnd, folderView->Release(); } - if (!ok && isDesktopShellView) { + if (ShouldUseDesktopFolderFallback(ok, sawSelection, + isDesktopShellView)) { std::wstring folderPath; if (GetDesktopFolderPath(folderPath) && IsDirectoryPath(folderPath)) { targetOut.kind = TargetKind::FolderBackground;