From: Jason Edmeades Subject: [PATCH 3/4] cmd: Support for launching programs based on file association Message-Id: <20180925074002.16485-3-us@edmeades.me.uk> Date: Tue, 25 Sep 2018 08:40:01 +0100 In-Reply-To: <20180925074002.16485-1-us@edmeades.me.uk> References: <20180925074002.16485-1-us@edmeades.me.uk> cmd already handles exe, cmd, bat etc but if you run a file with another extension, then use the associations set in the registry (for example via ftype / assoc) to launch a program. This enables you to run test.txt and notepad to pop up, or fred.msi for msiexec to be launched. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=18154 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=36646 --- Test for this added in the next patch, as ftype is broken at the moment, and needs fixing first Signed-off-by: Jason Edmeades --- programs/cmd/wcmdmain.c | 140 +++++++++++++++++++++++++++++----------- 1 file changed, 104 insertions(+), 36 deletions(-) diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 5ef4a2bf34..348eec8ad9 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1143,8 +1143,10 @@ void WCMD_run_program (WCHAR *command, BOOL called) /* 1. If extension supplied, see if that file exists */ if (extensionsupplied) { - if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) { + DWORD attribs = GetFileAttributesW(thisDir); + if (attribs != INVALID_FILE_ATTRIBUTES && !(attribs&FILE_ATTRIBUTE_DIRECTORY)) { found = TRUE; + WINE_TRACE("Found as file with extension as '%s'\n", wine_dbgstr_w(thisDir)); } } @@ -1175,6 +1177,7 @@ void WCMD_run_program (WCHAR *command, BOOL called) } if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) { + WINE_TRACE("Found via search and pathext as '%s'\n", wine_dbgstr_w(thisDir)); found = TRUE; thisExt = NULL; } @@ -1192,58 +1195,123 @@ void WCMD_run_program (WCHAR *command, BOOL called) WCHAR *ext = strrchrW( thisDir, '.' ); static const WCHAR batExt[] = {'.','b','a','t','\0'}; static const WCHAR cmdExt[] = {'.','c','m','d','\0'}; + static const WCHAR exeExt[] = {'.','e','x','e','\0'}; + static const WCHAR comExt[] = {'.','c','o','m','\0'}; WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir)); /* Special case BAT and CMD */ if (ext && (!strcmpiW(ext, batExt) || !strcmpiW(ext, cmdExt))) { BOOL oldinteractive = interactive; + WINE_TRACE("Calling batch program\n"); interactive = FALSE; WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE); interactive = oldinteractive; + WINE_TRACE("Back from call to batch program\n"); return; - } else { + } - /* thisDir contains the file to be launched, but with what? - eg. a.exe will require a.exe to be launched, a.html may be iexplore */ - hinst = FindExecutableW (thisDir, NULL, temp); - if ((INT_PTR)hinst < 32) - console = 0; - else - console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE); - - ZeroMemory (&st, sizeof(STARTUPINFOW)); - st.cb = sizeof(STARTUPINFOW); - init_msvcrt_io_block(&st); - - /* Launch the process and if a CUI wait on it to complete - Note: Launching internal wine processes cannot specify a full path to exe */ - status = CreateProcessW(thisDir, - command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe); - heap_free(st.lpReserved2); - if ((opt_c || opt_k) && !opt_s && !status - && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') { - /* strip first and last quote WCHARacters and try again */ - WCMD_strip_quotes(command); - opt_s = TRUE; - WCMD_run_program(command, called); + /* Calculate what program will be launched, and whether it is a + console application or not. Note the program may be different + from the parameter (eg running a .txt file will launch notepad.exe) */ + hinst = FindExecutableW (thisDir, NULL, temp); + if ((INT_PTR)hinst < 32) + console = 0; /* Assume not console app by default */ + else + console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE); + + + /* If it is not a .com or .exe, try to launch through ShellExecuteExW + which takes into account the association for the extension. */ + if (ext && (strcmpiW(ext, exeExt) && strcmpiW(ext, comExt))) { + + SHELLEXECUTEINFOW shexw; + BOOL rc; + WCHAR *rawarg; + + WCMD_parameter(command, 1, &rawarg, FALSE, TRUE); + WINE_TRACE("Launching via ShellExecuteEx\n"); + memset(&shexw, 0x00, sizeof(shexw)); + shexw.cbSize = sizeof(SHELLEXECUTEINFOW); + shexw.fMask = SEE_MASK_NO_CONSOLE | /* Run in same console as currently using */ + SEE_MASK_NOCLOSEPROCESS; /* We need a process handle to possibly wait on */ + shexw.lpFile = thisDir; + shexw.lpParameters = rawarg; + shexw.nShow = SW_SHOWNORMAL; + + /* Try to launch the binary or its associated program */ + rc = ShellExecuteExW(&shexw); + + if (rc && (INT_PTR)shexw.hInstApp >= 32) { + + WINE_TRACE("Successfully launched\n"); + + /* It worked... Always wait when non-interactive (cmd /c or in + batch program), or for console applications */ + if (!interactive || (console && !HIWORD(console))) { + WINE_TRACE("Waiting for process to end\n"); + WaitForSingleObject (shexw.hProcess, INFINITE); + } + + GetExitCodeProcess (shexw.hProcess, &errorlevel); + if (errorlevel == STILL_ACTIVE) { + WINE_TRACE("Process still running, but returning anyway\n"); + errorlevel = 0; + } else { + WINE_TRACE("Process ended, errorlevel %d\n", errorlevel); + } + + CloseHandle(pe.hProcess); return; + } + } - if (!status) - break; + /* If its a .exe or .com or the shellexecute failed due to no association, + CreateProcess directly */ + ZeroMemory (&st, sizeof(STARTUPINFOW)); + st.cb = sizeof(STARTUPINFOW); + init_msvcrt_io_block(&st); + + /* Launch the process and if a CUI wait on it to complete + Note: Launching internal wine processes cannot specify a full path to exe */ + WINE_TRACE("Launching via CreateProcess\n"); + status = CreateProcessW(thisDir, + command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe); + heap_free(st.lpReserved2); + if ((opt_c || opt_k) && !opt_s && !status + && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') { + /* strip first and last quote WCHARacters and try again */ + WCMD_strip_quotes(command); + opt_s = TRUE; + WCMD_run_program(command, called); + return; + } - /* Always wait when non-interactive (cmd /c or in batch program), - or for console applications */ - if (!interactive || (console && !HIWORD(console))) - WaitForSingleObject (pe.hProcess, INFINITE); - GetExitCodeProcess (pe.hProcess, &errorlevel); - if (errorlevel == STILL_ACTIVE) errorlevel = 0; + if (!status) { + WINE_TRACE("Failed to launch via CreateProcess, rc %d (%d)\n", + status, GetLastError()); + break; + } - CloseHandle(pe.hProcess); - CloseHandle(pe.hThread); - return; + /* Always wait when non-interactive (cmd /c or in batch program), + or for console applications */ + if (!interactive || (console && !HIWORD(console))) { + WINE_TRACE("Waiting for process to end\n"); + WaitForSingleObject (pe.hProcess, INFINITE); } + + GetExitCodeProcess (pe.hProcess, &errorlevel); + if (errorlevel == STILL_ACTIVE) { + WINE_TRACE("Process still running, but returning anyway\n"); + errorlevel = 0; + } else { + WINE_TRACE("Process ended, errorlevel %d\n", errorlevel); + } + + CloseHandle(pe.hProcess); + CloseHandle(pe.hThread); + return; } } -- 2.17.1