├── .gitignore ├── RunAsDesktopUser-mod ├── ExecInExplorer-mod │ ├── .gitignore │ ├── ExecInExplorer-mod │ │ ├── ExecInExplorer_Implementation.h │ │ ├── ExecInExplorer_Util.h │ │ ├── ExecInExplorer-mod.vcxproj.filters │ │ ├── DefaultPlatformToolset.props │ │ ├── ExecInExplorer.cpp │ │ ├── ExecInExplorer-mod.vcxproj │ │ ├── ExecInExplorer_Util.cpp │ │ └── ExecInExplorer_Implementation.cpp │ ├── ExecInExplorer-mod.sln │ ├── LICENSE │ └── README.md ├── RunAsDesktopUser_Implementation.h ├── RunAsDesktopUser-mod.vcxproj.filters ├── RunAsDesktopUser_Utils.h ├── DefaultPlatformToolset.props ├── RunAsDesktopUser.rc ├── RunAsDesktopUser-mod.vcxproj ├── RunAsDesktopUser.cpp └── RunAsDesktopUser_Implementation.cpp ├── RunAsDesktopUser-mod.sln ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.aps 2 | *.ncb 3 | *.opensdf 4 | *.sdf 5 | *.suo 6 | *.user 7 | Debug/ 8 | Release/ 9 | ipch/ 10 | x64/ 11 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/.gitignore: -------------------------------------------------------------------------------- 1 | *.aps 2 | *.opensdf 3 | *.sdf 4 | *.suo 5 | *.user 6 | Debug/ 7 | Release/ 8 | ipch/ 9 | x64/ 10 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/ExecInExplorer-mod/ExecInExplorer_Implementation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | HRESULT ShellExecInExplorerProcess(PCWSTR pszFile, PCWSTR pszArgs = L"", PCWSTR pszDir = L""); 6 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/ExecInExplorer-mod/ExecInExplorer_Util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | BOOL ProgAndArgLineFromCmdLine(const TCHAR *cmdline, TCHAR **prog, 6 | TCHAR **argline); 7 | 8 | BOOL GetCurrentDir_ThreadUnsafe(TCHAR **currentdir); 9 | 10 | BOOL GetProgPath(TCHAR **progpath); 11 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/RunAsDesktopUser_Implementation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Declaration of the function this sample is all about. 6 | // The szApp, szCmdLine, szCurrDir, si, and pi parameters are passed directly to CreateProcessWithTokenW. 7 | // sErrorInfo returns text describing any error that occurs. 8 | // Returns "true" on success, "false" on any error. 9 | // It is up to the caller to close the HANDLEs returned in the PROCESS_INFORMATION structure. 10 | bool RunAsDesktopUser( 11 | __in const wchar_t * szApp, 12 | __in wchar_t * szCmdLine, 13 | __in const wchar_t * szCurrDir, 14 | __in STARTUPINFOW & si, 15 | __inout PROCESS_INFORMATION & pi, 16 | __inout std::wstringstream & sErrorInfo); 17 | 18 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.40629.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RunAsDesktopUser-mod", "RunAsDesktopUser-mod\RunAsDesktopUser-mod.vcxproj", "{F6D6B52E-1FF3-44E8-A581-EA1B681C5CF7}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Release|Win32 = Release|Win32 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {F6D6B52E-1FF3-44E8-A581-EA1B681C5CF7}.Debug|Win32.ActiveCfg = Debug|Win32 15 | {F6D6B52E-1FF3-44E8-A581-EA1B681C5CF7}.Debug|Win32.Build.0 = Debug|Win32 16 | {F6D6B52E-1FF3-44E8-A581-EA1B681C5CF7}.Release|Win32.ActiveCfg = Release|Win32 17 | {F6D6B52E-1FF3-44E8-A581-EA1B681C5CF7}.Release|Win32.Build.0 = Release|Win32 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/ExecInExplorer-mod.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.40629.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExecInExplorer-mod", "ExecInExplorer-mod\ExecInExplorer-mod.vcxproj", "{7DA1D1DE-588D-4095-B9AB-0B68663FAFD5}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Release|Win32 = Release|Win32 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {7DA1D1DE-588D-4095-B9AB-0B68663FAFD5}.Debug|Win32.ActiveCfg = Debug|Win32 15 | {7DA1D1DE-588D-4095-B9AB-0B68663FAFD5}.Debug|Win32.Build.0 = Debug|Win32 16 | {7DA1D1DE-588D-4095-B9AB-0B68663FAFD5}.Release|Win32.ActiveCfg = Release|Win32 17 | {7DA1D1DE-588D-4095-B9AB-0B68663FAFD5}.Release|Win32.Build.0 = Release|Win32 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Microsoft Corporation 4 | Copyright (c) 2009 Aaron Margosis 5 | Copyright (C) 2017 Jay Satiro 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Microsoft Corporation 4 | Copyright (C) 2017 Jay Satiro 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/ExecInExplorer-mod/ExecInExplorer-mod.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | 29 | 30 | Header Files 31 | 32 | 33 | Header Files 34 | 35 | 36 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/RunAsDesktopUser-mod.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | 29 | 30 | Header Files 31 | 32 | 33 | Header Files 34 | 35 | 36 | Header Files 37 | 38 | 39 | 40 | 41 | Resource Files 42 | 43 | 44 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/RunAsDesktopUser_Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // ------------------------------------------------------------------------------------------ 12 | // Structure and operators to insert a zero-filled hex-formatted number into a std::ostream. 13 | struct HEX 14 | { 15 | HEX(unsigned long num, unsigned long fieldwidth = 8, bool bUpcase = false) 16 | : m_num(num), m_width(fieldwidth), m_upcase(bUpcase) 17 | {} 18 | 19 | unsigned long m_num; 20 | unsigned long m_width; 21 | bool m_upcase; 22 | }; 23 | 24 | inline std::ostream& operator << ( std::ostream& os, const HEX & h ) 25 | { 26 | using namespace std; 27 | int fmt = os.flags(); 28 | char fillchar = os.fill('0'); 29 | os << "0x" << hex << (h.m_upcase ? uppercase : nouppercase) << setw(h.m_width) << h.m_num ; 30 | os.fill(fillchar); 31 | os.flags(fmt); 32 | return os; 33 | } 34 | 35 | inline std::wostream& operator << ( std::wostream& os, const HEX & h ) 36 | { 37 | using namespace std; 38 | int fmt = os.flags(); 39 | wchar_t fillchar = os.fill(L'0'); 40 | os << L"0x" << hex << (h.m_upcase ? uppercase : nouppercase) << setw(h.m_width) << h.m_num ; 41 | os.fill(fillchar); 42 | os.flags(fmt); 43 | return os; 44 | } 45 | 46 | 47 | // ------------------------------------------------------------------------------------------ 48 | 49 | // Convert an error code to corresponding text, returning it as a std::wstring. 50 | 51 | inline std::wstring SysErrorMessageWithCode(DWORD dwErrCode /*= GetLastError()*/) 52 | { 53 | using namespace std; 54 | LPWSTR pszErrMsg = NULL; 55 | wstringstream sRetval; 56 | DWORD flags = 57 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 58 | FORMAT_MESSAGE_IGNORE_INSERTS | 59 | FORMAT_MESSAGE_FROM_SYSTEM ; 60 | 61 | if ( FormatMessageW( 62 | flags, 63 | NULL, 64 | dwErrCode, 65 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language 66 | (LPWSTR) &pszErrMsg, 67 | 0, 68 | NULL ) ) 69 | { 70 | sRetval << pszErrMsg << L" (Error # " << dwErrCode << L" = " << HEX(dwErrCode) << L")"; 71 | LocalFree(pszErrMsg); 72 | } 73 | else 74 | { 75 | sRetval << L"Error # " << dwErrCode << L" (" << HEX(dwErrCode) << L")"; 76 | } 77 | return sRetval.str(); 78 | } 79 | 80 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/DefaultPlatformToolset.props: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 22 | v100 23 | 42 | $(DefaultPlatformToolset) 43 | 44 | 45 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/RunAsDesktopUser.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | //#include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #include "winres.h" 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | #undef APSTUDIO_READONLY_SYMBOLS 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | // English (United States) resources 17 | 18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 19 | LANGUAGE 9, 1 20 | 21 | #ifdef APSTUDIO_INVOKED 22 | ///////////////////////////////////////////////////////////////////////////// 23 | // 24 | // TEXTINCLUDE 25 | // 26 | 27 | 1 TEXTINCLUDE 28 | BEGIN 29 | "resource.h\0" 30 | END 31 | 32 | 2 TEXTINCLUDE 33 | BEGIN 34 | "#include ""winres.h""\r\n" 35 | "\0" 36 | END 37 | 38 | 3 TEXTINCLUDE 39 | BEGIN 40 | "\r\n" 41 | "\0" 42 | END 43 | 44 | #endif // APSTUDIO_INVOKED 45 | 46 | #endif // English (United States) resources 47 | ///////////////////////////////////////////////////////////////////////////// 48 | 49 | 50 | 51 | ///////////////////////////////////////////////////////////////////////////// 52 | // 53 | // Version 54 | // 55 | 56 | VS_VERSION_INFO VERSIONINFO 57 | FILEVERSION 1,2,0,1 58 | PRODUCTVERSION 1,2,0,1 59 | FILEFLAGSMASK 0x3fL 60 | #ifdef _DEBUG 61 | FILEFLAGS 0x1L 62 | #else 63 | FILEFLAGS 0x0L 64 | #endif 65 | FILEOS 0x4L 66 | FILETYPE 0x1L 67 | FILESUBTYPE 0x0L 68 | BEGIN 69 | BLOCK "StringFileInfo" 70 | BEGIN 71 | BLOCK "040904e4" 72 | BEGIN 73 | VALUE "Comments", "Unsupported sample code, as-is" 74 | VALUE "CompanyName", "https://github.com/jay/RunAsDesktopUser" 75 | VALUE "FileDescription", "RunAsDesktopUser - modified for command line usage" 76 | VALUE "FileVersion", "1.2.0.1" 77 | VALUE "InternalName", "RunAsDesktopUser.exe" 78 | VALUE "LegalCopyright", "(c) 2009 Aaron Margosis, (c) 2017 Jay Satiro" 79 | VALUE "OriginalFilename", "RunAsDesktopUser.exe" 80 | VALUE "ProductName", "RunAsDesktopUser - modified for command line usage" 81 | VALUE "ProductVersion", "1.2.0.1" 82 | END 83 | END 84 | BLOCK "VarFileInfo" 85 | BEGIN 86 | VALUE "Translation", 0x409, 1252 87 | END 88 | END 89 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/ExecInExplorer-mod/DefaultPlatformToolset.props: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 22 | v100 23 | 42 | $(DefaultPlatformToolset) 43 | 44 | 45 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/README.md: -------------------------------------------------------------------------------- 1 | ExecInExplorer 2 | ============== 3 | 4 | ExecInExplorer - Execute a command asynchronously in the context of explorer 5 | 6 | This is a fork of 7 | [Microsoft's ExecInExplorer sample](https://github.com/Microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/winui/shell/appplatform/ExecInExplorer). 8 | I modified it to execute the program arguments as a command line and optionally 9 | use the current directory. 10 | 11 | ### Usage 12 | 13 | Usage: `ExecInExplorer [--use-current-directory[=dir]] [[arg]...]` 14 | Execute a command asynchronously in the context of explorer. 15 | 16 | ExecInExplorer is useful if you are running as administrator and you want to 17 | execute a program in the context of the user's explorer (eg it will de-elevate 18 | the program if explorer isn't running as admin). 19 | 20 | Use option `--use-current-directory` to tell explorer to use this program's 21 | current directory or optionally 'dir'. Due to the command line being parsed as 22 | raw data in order to preserve it for explorer, this option is parsed raw 23 | (escapes are not acknowledged), must not contain quotes but can be quoted and 24 | if quoted ends immediately at the next quote no matter what. For example: 25 | 26 | ~~~ 27 | Yes: ExecInExplorer "--use-current-directory=C:\foo bar\baz" notepad foo 28 | No: ExecInExplorer --use-current-directory="C:\foo bar\baz" notepad foo 29 | No: ExecInExplorer "--use-current-directory=\"C:\foo bar\baz\"" notepad foo 30 | ~~~ 31 | 32 | If the specified directory is invalid or inaccessible the behavior is 33 | undocumented. Empirical testing shows if the directory does not exist then 34 | explorer will use the root directory of the drive if the drive exists or the 35 | system root (typically C:\Windows) if it doesn't. If you do not use option 36 | `--use-current-directory` then explorer will use its current directory 37 | (typically C:\Windows\System32). 38 | 39 | The exit code returned by ExecInExplorer is 0 if the command line to be 40 | executed was *dispatched* successfully or otherwise an HRESULT indicating why 41 | it wasn't. There is no other interaction. 42 | 43 | Other 44 | ----- 45 | 46 | ### Resources 47 | 48 | Raymond Chen's 49 | [blog post](https://blogs.msdn.microsoft.com/oldnewthing/20131118-00/?p=2643). 50 | 51 | ExecInExplorer works by calling function 52 | [IShellDispatch2.ShellExecute](https://msdn.microsoft.com/en-us/library/windows/desktop/bb774148.aspx). 53 | 54 | [Microsoft's RunAsDesktopUser sample](https://blogs.msdn.microsoft.com/aaron_margosis/2009/06/06/faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app/), 55 | which is similar to ExecInExplorer. In 56 | [my RunAsDesktopUser fork](https://github.com/jay/RunAsDesktopUser) 57 | I made changes similar to what I made in ExecInExplorer, and also a change 58 | allowing for synchronous execution of the command line (via 59 | --wait-for-exit-code). 60 | 61 | ### Incorrect exit code when run from Windows Command Prompt? 62 | 63 | ExecInExplorer uses the Windows subsystem not the console subsystem, which 64 | means that when you execute it from the Windows command interpreter the 65 | interpreter runs it asynchronously and therefore does not update %ERRORLEVEL% 66 | with the exit code. However if you execute it from a batch file the interpreter 67 | will run it synchronously and once it terminates its exit code will be put in 68 | %ERRORLEVEL%. None of this has any bearing on how ExecInExplorer executes the 69 | command line passed to it, which is always asynchronous. 70 | 71 | ### License 72 | 73 | [MIT license](https://github.com/jay/ExecInExplorer/blob/master/LICENSE) 74 | 75 | ### Source 76 | 77 | The source can be found on 78 | [GitHub](https://github.com/jay/ExecInExplorer). 79 | Since you're reading this maybe you're already there? 80 | 81 | ### Send me any questions you have 82 | 83 | Jay Satiro `` and put ExecInExplorer in the subject. 84 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/ExecInExplorer-mod/ExecInExplorer.cpp: -------------------------------------------------------------------------------- 1 | /* LICENSE: MIT License 2 | Copyright (C) 2017 Jay Satiro 3 | https://github.com/jay/ExecInExplorer/blob/master/LICENSE 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "ExecInExplorer_Implementation.h" 12 | #include "ExecInExplorer_Util.h" 13 | 14 | using namespace std; 15 | 16 | #define ISSPACE(c) ((c) == ' ' || (c) == '\t') 17 | 18 | #define OPTSTR_CURDIR L"--use-current-directory" 19 | 20 | void ShowUsageAndDie(void) 21 | { 22 | MessageBoxW(NULL, 23 | L"Usage: ExecInExplorer [" OPTSTR_CURDIR L"[=dir]] [[arg]...]\n" 24 | L"Execute a command asynchronously in the context of explorer.\n" 25 | L"\n" 26 | L"ExecInExplorer is useful if you are running as administrator and you want to execute a " 27 | L"program in the context of the user's explorer (eg it will de-elevate the program if " 28 | L"explorer isn't running as admin).\n" 29 | L"\n" 30 | L"Use option " OPTSTR_CURDIR L" to tell explorer to use this program's current directory or " 31 | L"optionally 'dir'. Due to the command line being parsed as raw data in order to preserve it " 32 | L"for explorer, this option is parsed raw (escapes are not acknowledged), must not contain " 33 | L"quotes but can be quoted and if quoted ends immediately at the next quote no matter what. " 34 | L"For example:\n" 35 | L"\n" 36 | L"Yes: ExecInExplorer \"" OPTSTR_CURDIR L"=C:\\foo bar\\baz\" notepad foo\n" 37 | L"No: ExecInExplorer " OPTSTR_CURDIR L"=\"C:\\foo bar\\baz\" notepad foo\n" 38 | L"No: ExecInExplorer \"" OPTSTR_CURDIR L"=\\\"C:\\foo bar\\baz\\\"\" notepad foo\n" 39 | L"\n" 40 | L"If the specified directory is invalid or inaccessible the behavior is undocumented. " 41 | L"Empirical testing shows if the directory does not exist then explorer will use the root " 42 | L"directory of the drive if the drive exists or the system root (typically C:\\Windows) if it " 43 | L"doesn't. If you do not use option " OPTSTR_CURDIR L" then explorer will use its current " 44 | L"directory (typically C:\\Windows\\System32).\n" 45 | L"\n" 46 | L"The exit code returned by ExecInExplorer is 0 if the command line to be executed was " 47 | L"*dispatched* successfully or otherwise an HRESULT indicating why it wasn't. There is no " 48 | L"other interaction.\n" 49 | L"\n" 50 | L"https://github.com/jay/ExecInExplorer\n", L"ExecInExplorer", 0); 51 | exit(-1); 52 | } 53 | 54 | int WINAPI wWinMain(HINSTANCE, HINSTANCE, const PWSTR cmdline, int) 55 | { 56 | HRESULT hr; 57 | WCHAR *dir, *prog, *argline; 58 | 59 | if(!*cmdline) 60 | ShowUsageAndDie(); 61 | 62 | if(!ProgAndArgLineFromCmdLine(cmdline, &prog, &argline)) 63 | return -1; 64 | 65 | if(!wcscmp(prog, L"--help") && !*argline) 66 | ShowUsageAndDie(); 67 | 68 | dir = NULL; 69 | 70 | if(!wcsncmp(prog, OPTSTR_CURDIR, wcslen(OPTSTR_CURDIR))) { 71 | WCHAR *p = prog + wcslen(OPTSTR_CURDIR); 72 | 73 | if(!*p || *p == '=') { 74 | WCHAR *newcmdline; 75 | 76 | if(*p == '=' && *++p) { 77 | if(wcschr(p, '"')) 78 | return -1; 79 | 80 | dir = _wcsdup(p); 81 | if(!dir) 82 | return -1; 83 | } 84 | else { 85 | if(!GetCurrentDir_ThreadUnsafe(&dir)) 86 | return -1; 87 | } 88 | 89 | p = NULL; 90 | free(prog); 91 | newcmdline = argline; 92 | 93 | if(!ProgAndArgLineFromCmdLine(newcmdline, &prog, &argline)) 94 | return -1; 95 | 96 | free(newcmdline); 97 | } 98 | } 99 | 100 | hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); 101 | if(SUCCEEDED(hr)) { 102 | hr = ShellExecInExplorerProcess(prog, argline, dir); 103 | CoUninitialize(); 104 | } 105 | 106 | free(dir); 107 | free(prog); 108 | free(argline); 109 | return SUCCEEDED(hr) ? 0 : (int)hr; 110 | } 111 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/ExecInExplorer-mod/ExecInExplorer-mod.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {7DA1D1DE-588D-4095-B9AB-0B68663FAFD5} 15 | Win32Proj 16 | ExecInExplorermod 17 | 18 | 19 | 20 | 21 | Application 22 | true 23 | Unicode 24 | 25 | 26 | Application 27 | false 28 | true 29 | Unicode 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | true 43 | ExecInExplorer 44 | 45 | 46 | false 47 | ExecInExplorer 48 | 49 | 50 | 51 | 52 | 53 | Level4 54 | Disabled 55 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 56 | MultiThreadedDebug 57 | 58 | 59 | Windows 60 | true 61 | 62 | 63 | 64 | 65 | Level4 66 | 67 | 68 | MaxSpeed 69 | true 70 | true 71 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 72 | MultiThreaded 73 | 74 | 75 | Windows 76 | true 77 | true 78 | true 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/ExecInExplorer-mod/ExecInExplorer_Util.cpp: -------------------------------------------------------------------------------- 1 | /* LICENSE: MIT License 2 | Copyright (C) 2017 Jay Satiro 3 | https://github.com/jay/ExecInExplorer/blob/master/LICENSE 4 | */ 5 | 6 | #ifndef _CRT_SECURE_NO_WARNINGS 7 | #define _CRT_SECURE_NO_WARNINGS 8 | #endif 9 | #ifndef PSAPI_VERSION 10 | #define PSAPI_VERSION 1 11 | #endif 12 | 13 | #include "ExecInExplorer_Util.h" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #pragma comment(lib, "psapi.lib") 20 | 21 | /* MAX_EXTENDED_PATH includes the NUL the same way MAX_PATH does. 22 | https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#maxpath */ 23 | #define MAX_EXTENDED_PATH (32767 + 1) 24 | 25 | #define ISSPACE(c) ((c) == ' ' || (c) == '\t') 26 | 27 | /* 28 | Separate the first argument from remaining arguments in a command line. 29 | 30 | The first argument is parsed as the program argument not as a normal argument, 31 | which means if the argument starts with a quote then it ends at the next quote, 32 | if any, no matter what (ie escaped quotes aren't acknowledged) and those quotes 33 | and outer whitespace are removed. If it doesn't start with a quote then it ends 34 | at the following whitespace, if any, no matter what (ie escaped whitespace 35 | isn't acknowledged). For example: 36 | 37 | cmdline: foo bar baz 38 | prog: foo 39 | argline: bar baz 40 | 41 | cmdline: "C:\foo bar\baz" " wibble wobble" wubble 42 | prog: C:\foo bar\baz 43 | argline: " wibble wobble" wubble 44 | 45 | On success nonzero is returned and: 46 | *prog points to a copy of the first argument. 47 | *argline points to a copy of the remaining arguments. 48 | Caller free()s memory. 49 | */ 50 | BOOL ProgAndArgLineFromCmdLine(const TCHAR *cmdline, TCHAR **prog, 51 | TCHAR **argline) 52 | { 53 | const TCHAR *p; 54 | size_t prog_len; 55 | 56 | *prog = NULL; 57 | *argline = NULL; 58 | 59 | for(p = cmdline; *p && ISSPACE(*p); ++p); 60 | 61 | if(*p == '"') { 62 | cmdline = ++p; 63 | for(; *p && *p != '"'; ++p); 64 | } 65 | else { 66 | cmdline = p; 67 | for(; *p && !ISSPACE(*p); ++p); 68 | } 69 | 70 | prog_len = p - cmdline; 71 | 72 | *prog = (TCHAR *)malloc((prog_len + 1) * sizeof(TCHAR)); 73 | if(!*prog) { 74 | SetLastError(ERROR_OUTOFMEMORY); 75 | return FALSE; 76 | } 77 | 78 | _tcsncpy(*prog, cmdline, prog_len); 79 | (*prog)[prog_len] = 0; 80 | 81 | if(*p == '"') 82 | ++p; 83 | 84 | for(; *p && ISSPACE(*p); ++p); 85 | 86 | *argline = _tcsdup(p); 87 | if(!*argline) { 88 | free(*prog); 89 | *prog = NULL; 90 | SetLastError(ERROR_OUTOFMEMORY); 91 | return FALSE; 92 | } 93 | 94 | return TRUE; 95 | } 96 | 97 | /* 98 | Get the current directory. 99 | Note this calls GetCurrentDirectory API which is not thread-safe. 100 | 101 | On success nonzero is returned and: 102 | *currentdir points to a copy of the current directory. 103 | Caller free()s memory. 104 | */ 105 | BOOL GetCurrentDir_ThreadUnsafe(TCHAR **currentdir) 106 | { 107 | DWORD c, res; 108 | 109 | *currentdir = NULL; 110 | 111 | c = GetCurrentDirectory(0, NULL); 112 | 113 | if(!c || c >= MAX_EXTENDED_PATH) { 114 | DWORD gle = c ? ERROR_BAD_LENGTH : GetLastError(); 115 | /**/ 116 | SetLastError(gle); 117 | return FALSE; 118 | } 119 | 120 | *currentdir = (TCHAR *)malloc((size_t)c * sizeof(TCHAR)); 121 | if(!*currentdir) { 122 | SetLastError(ERROR_OUTOFMEMORY); 123 | return FALSE; 124 | } 125 | 126 | res = GetCurrentDirectory(c, *currentdir); 127 | 128 | if(!res || res >= c) { 129 | DWORD gle = res ? ERROR_BAD_LENGTH : GetLastError(); 130 | free(*currentdir); 131 | *currentdir = NULL; 132 | SetLastError(gle); 133 | return FALSE; 134 | } 135 | 136 | return TRUE; 137 | } 138 | 139 | /* 140 | Get the full path of this program. 141 | 142 | This is more reliable than the global _tpgmptr which may not be the full path. 143 | 144 | On success nonzero is returned and: 145 | *progpath points to a copy of the full path of this program. 146 | Caller free()s memory. 147 | */ 148 | BOOL GetProgPath(TCHAR **progpath) 149 | { 150 | TCHAR *buf; 151 | DWORD res; 152 | 153 | *progpath = NULL; 154 | 155 | buf = (TCHAR *)malloc(MAX_EXTENDED_PATH * sizeof(TCHAR)); 156 | 157 | res = GetModuleFileNameEx(GetCurrentProcess(), NULL, buf, MAX_EXTENDED_PATH); 158 | 159 | /* If the character length written is MAX_EXTENDED_PATH - 1 the path may be 160 | truncated, so error. In practice this shouldn't happen. */ 161 | if(!res || res >= MAX_EXTENDED_PATH - 1) { 162 | DWORD gle = res ? ERROR_BAD_LENGTH : GetLastError(); 163 | free(buf); 164 | SetLastError(gle); 165 | return FALSE; 166 | } 167 | 168 | *progpath = _tcsdup(buf); 169 | 170 | if(!*progpath) { 171 | free(buf); 172 | SetLastError(ERROR_OUTOFMEMORY); 173 | return FALSE; 174 | } 175 | 176 | free(buf); 177 | return TRUE; 178 | } 179 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/RunAsDesktopUser-mod.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {F6D6B52E-1FF3-44E8-A581-EA1B681C5CF7} 15 | Win32Proj 16 | RunAsDesktopUsermod 17 | 18 | 19 | 20 | 21 | Application 22 | true 23 | Unicode 24 | 25 | 26 | Application 27 | false 28 | true 29 | Unicode 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | true 43 | RunAsDesktopUser 44 | 45 | 46 | false 47 | RunAsDesktopUser 48 | 49 | 50 | 51 | 52 | 53 | Level4 54 | Disabled 55 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 56 | MultiThreadedDebug 57 | ExecInExplorer-mod\ExecInExplorer-mod; 58 | 59 | 60 | Windows 61 | true 62 | 63 | 64 | 65 | 66 | Level4 67 | 68 | 69 | MaxSpeed 70 | true 71 | true 72 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 73 | MultiThreaded 74 | ExecInExplorer-mod\ExecInExplorer-mod; 75 | 76 | 77 | Windows 78 | true 79 | true 80 | true 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RunAsDesktopUser 2 | ================ 3 | 4 | RunAsDesktopUser - Execute a command in the context of the desktop user 5 | 6 | This is a fork of 7 | [Microsoft's RunAsDesktopUser sample](https://blogs.msdn.microsoft.com/aaron_margosis/2009/06/06/faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app/). 8 | I modified it to execute the program arguments as a command line and optionally 9 | use the current directory, wait for process termination and fall back to the 10 | current user if not an administrator. It works on Windows Vista or greater. 11 | 12 | ### Usage 13 | Usage: `RunAsDesktopUser [--use-current-directory[=dir]] 14 | [--fallback-current-user] [--wait-for-exit-code] [[arg]...]` 15 | Execute a command in the context of the desktop user. 16 | 17 | RunAsDesktopUser is useful if you are running as administrator and you want to 18 | execute a program in the context of the desktop user (eg it will de-elevate the 19 | program if the desktop user's shell isn't running as admin). 20 | 21 | Use option `--fallback-current-user` to fall back to executing in the context 22 | of the current user running this program if that user is not running the 23 | program with administrator privileges. If you do not use this option and are 24 | not running this program as an administrator then this program will have no 25 | effect. 26 | 27 | Use option `--use-current-directory` to use this program's current directory or 28 | optionally 'dir'. Due to the command line being parsed as raw data in order to 29 | preserve it for CreateProcess, this option is parsed raw (escapes are not 30 | acknowledged), must not contain quotes but can be quoted and if quoted ends 31 | immediately at the next quote no matter what. For example: 32 | 33 | ~~~ 34 | Yes: RunAsDesktopUser "--use-current-directory=C:\foo bar\baz" notepad foo 35 | No: RunAsDesktopUser --use-current-directory="C:\foo bar\baz" notepad foo 36 | No: RunAsDesktopUser "--use-current-directory=\"C:\foo bar\baz\"" notepad foo 37 | ~~~ 38 | 39 | If the specified directory is invalid or inaccessible the behavior is 40 | undocumented. Empirical testing shows process creation will fail. If you do not 41 | use option `--use-current-directory` then the behavior varies; it is documented 42 | that both CreateProcessWithTokenW (admin) and CreateProcessW (nonadmin) will 43 | use RunAsDesktopUser's current directory, however empirical testing shows the 44 | former will use the system directory (typically C:\Windows\System32). 45 | 46 | Use option `--wait-for-exit-code` to wait for the exit code of the 'prog' 47 | process, and have this program return that exit code. If you do not use this 48 | option then the exit code is 0 if prog was started. Whether this option is used 49 | or not if an error occurs in RunAsDesktopUser the exit code is -1. There is no 50 | other interaction. 51 | 52 | Other 53 | ----- 54 | 55 | ### Resources 56 | 57 | RunAsDesktopUser works by calling function 58 | [CreateProcessWithTokenW](https://msdn.microsoft.com/en-us/library/ms682434.aspx) 59 | if the program has admin privileges or 60 | [CreateProcessW](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425.aspx) 61 | if the program doesn't have admin privileges and option --fallback-current-user 62 | was used. 63 | 64 | [Microsoft's ExecInExplorer sample](https://github.com/Microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/winui/shell/appplatform/ExecInExplorer), 65 | which is similar to RunAsDesktopUser. In 66 | [my ExecInExplorer fork](https://github.com/jay/ExecInExplorer) 67 | I made changes similar to what I made in RunAsDesktopUser, however due to 68 | limitations of the method there is no synchronous execution of the command 69 | line. 70 | 71 | ### Using explorer.exe for the same purpose 72 | 73 | If explorer.exe is running it's possible to execute a program in that context 74 | by using the full path to the program as the first argument, for example 75 | `explorer.exe "C:\foo bar\baz"`. Program arguments are not accepted. 76 | Unfortunately this feature is undocumented, has been called a bug and may 77 | [disappear one day](http://mdb-blog.blogspot.com/2013/01/nsis-lunch-program-as-user-from-uac.html?showComment=1388694317801#c939517856791332836). 78 | 79 | ### Incorrect exit code when run from Windows Command Prompt? 80 | 81 | RunAsDesktopUser uses the Windows subsystem not the console subsystem, which 82 | means that when you execute it from the Windows command interpreter the 83 | interpreter runs it asynchronously and therefore does not update %ERRORLEVEL% 84 | with the exit code. However if you execute it from a batch file the interpreter 85 | will run it synchronously and once it terminates its exit code will be put in 86 | %ERRORLEVEL%. None of this has any bearing on how RunAsDesktopUser executes the 87 | command line passed to it. 88 | 89 | ### License 90 | 91 | [MIT license](https://github.com/jay/RunAsDesktopUser/blob/master/LICENSE) 92 | 93 | ### Source 94 | 95 | The source can be found on 96 | [GitHub](https://github.com/jay/RunAsDesktopUser). 97 | Since you're reading this maybe you're already there? 98 | 99 | ### Send me any questions you have 100 | 101 | Jay Satiro `` and put RunAsDesktopUser in the subject. 102 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/RunAsDesktopUser.cpp: -------------------------------------------------------------------------------- 1 | /* LICENSE: MIT License 2 | Copyright (C) 2017 Jay Satiro 3 | https://github.com/jay/RunAsDesktopUser/blob/master/LICENSE 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "ExecInExplorer_Util.h" 17 | 18 | #include "RunAsDesktopUser_Implementation.h" 19 | #include "RunAsDesktopUser_Utils.h" 20 | 21 | using namespace std; 22 | 23 | #define OPTSTR_CURDIR L"--use-current-directory" 24 | #define OPTSTR_FALLBACK L"--fallback-current-user" 25 | #define OPTSTR_WAITEXIT L"--wait-for-exit-code" 26 | 27 | void ShowUsageAndDie(void) 28 | { 29 | MessageBoxW(NULL, 30 | L"Usage: RunAsDesktopUser " 31 | L"[" OPTSTR_CURDIR L"[=dir]] [" OPTSTR_FALLBACK L"] [" OPTSTR_WAITEXIT L"] [[arg]...]\n" 32 | L"Execute a command in the context of the desktop user.\n" 33 | L"\n" 34 | L"RunAsDesktopUser is useful if you are running as administrator and you want to execute a " 35 | L"program in the context of the desktop user (eg it will de-elevate the program if the desktop " 36 | L"user's shell isn't running as admin).\n" 37 | L"\n" 38 | L"Use option " OPTSTR_FALLBACK L" to fall back to executing in the context of the current user " 39 | L"running this program if that user is not running the program with administrator privileges. " 40 | L"If you do not use this option and are not running this program as an administrator then it's " 41 | L"likely this program will be unable to obtain the required permissions and will have no " 42 | L"effect.\n" 43 | L"\n" 44 | L"Use option " OPTSTR_CURDIR L" to use this program's current directory or optionally 'dir'. " 45 | L"Due to the command line being parsed as raw data in order to preserve it for CreateProcess, " 46 | L"this option is parsed raw (escapes are not acknowledged), must not contain quotes but can be " 47 | L"quoted and if quoted ends immediately at the next quote no matter what. For example:\n" 48 | L"\n" 49 | L"Yes: RunAsDesktopUser \"" OPTSTR_CURDIR L"=C:\\foo bar\\baz\" notepad foo\n" 50 | L"No: RunAsDesktopUser " OPTSTR_CURDIR L"=\"C:\\foo bar\\baz\" notepad foo\n" 51 | L"No: RunAsDesktopUser \"" OPTSTR_CURDIR L"=\\\"C:\\foo bar\\baz\\\"\" notepad foo\n" 52 | L"\n" 53 | L"If the specified directory is invalid or inaccessible the behavior is undocumented. " 54 | L"Empirical testing shows process creation will fail. If you do not use option " OPTSTR_CURDIR 55 | L" then the behavior varies; it is documented that both CreateProcessWithTokenW (admin) and " 56 | L"CreateProcessW (nonadmin) will use RunAsDesktopUser's current directory, however empirical " 57 | L"testing shows the former may use the system directory (typically C:\\Windows\\System32).\n" 58 | L"\n" 59 | L"Use option " OPTSTR_WAITEXIT L" to wait for the exit code of the 'prog' process, and have " 60 | L"this program return that exit code. If you do not use this option then the exit code is 0 if " 61 | L"prog was started. Whether this option is used or not if an error occurs in RunAsDesktopUser " 62 | L"the exit code is -1. There is no other interaction.\n" 63 | L"\n" 64 | L"https://github.com/jay/RunAsDesktopUser\n", L"RunAsDesktopUser", 0); 65 | exit(-1); 66 | } 67 | 68 | int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR cmdline, int) 69 | { 70 | WCHAR *dir, *prog, *argline; 71 | bool fallback_current_user, wait_for_exit_code; 72 | 73 | cmdline = _wcsdup(cmdline); 74 | if(!*cmdline) 75 | ShowUsageAndDie(); 76 | 77 | if(!ProgAndArgLineFromCmdLine(cmdline, &prog, &argline)) 78 | return -1; 79 | 80 | if(!wcscmp(prog, L"--help") && !*argline) 81 | ShowUsageAndDie(); 82 | 83 | dir = NULL; 84 | fallback_current_user = false; 85 | wait_for_exit_code = false; 86 | 87 | for(;;) { 88 | if(!wcscmp(prog, OPTSTR_FALLBACK)) 89 | fallback_current_user = true; 90 | else if(!wcscmp(prog, OPTSTR_WAITEXIT)) 91 | wait_for_exit_code = true; 92 | else if(!wcscmp(prog, OPTSTR_CURDIR) || 93 | !wcsncmp(prog, OPTSTR_CURDIR L"=", wcslen(OPTSTR_CURDIR) + 1)) { 94 | WCHAR *p = prog + wcslen(OPTSTR_CURDIR); 95 | if(*p == '=' && *++p) { 96 | if(wcschr(p, '"')) 97 | return -1; 98 | 99 | dir = _wcsdup(p); 100 | if(!dir) 101 | return -1; 102 | } 103 | else { 104 | if(!GetCurrentDir_ThreadUnsafe(&dir)) 105 | return -1; 106 | } 107 | } 108 | else 109 | break; 110 | 111 | free(prog); 112 | free(cmdline); 113 | cmdline = argline; 114 | 115 | if(!ProgAndArgLineFromCmdLine(cmdline, &prog, &argline)) 116 | return -1; 117 | } 118 | 119 | STARTUPINFOW si = {}; 120 | PROCESS_INFORMATION pi = {}; 121 | wstringstream errinfo; 122 | 123 | if(IsUserAnAdmin()) { 124 | /* This uses CreateProcessWithTokenW which is not available in XP */ 125 | if(!RunAsDesktopUser(NULL, cmdline, dir, si, pi, errinfo)) 126 | return -1; 127 | } 128 | else if(fallback_current_user) { 129 | if(!CreateProcessW(NULL, cmdline, NULL, NULL, FALSE, 0, 130 | NULL, dir, &si, &pi)) 131 | return -1; 132 | } 133 | else 134 | return -1; 135 | 136 | DWORD exitcode = 0; 137 | 138 | if(wait_for_exit_code) { 139 | if(WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0) 140 | return -1; 141 | 142 | if(!GetExitCodeProcess(pi.hProcess, &exitcode)) 143 | return -1; 144 | } 145 | 146 | free(dir); 147 | free(prog); 148 | free(argline); 149 | free(cmdline); 150 | CloseHandle(pi.hProcess); 151 | CloseHandle(pi.hThread); 152 | 153 | return (int)exitcode; 154 | } 155 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/RunAsDesktopUser_Implementation.cpp: -------------------------------------------------------------------------------- 1 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 2 | // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 3 | // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 4 | // PARTICULAR PURPOSE. 5 | // 6 | // Copyright (c) Microsoft Corporation. All rights reserved 7 | // Copyright (c) 2009 Aaron Margosis 8 | // Copyright (C) 2017 Jay Satiro 9 | // 10 | // RunAsDesktopUser() has been modified to confirm the right process was opened, and retry if not. 11 | // 12 | // Unmodified project at: 13 | // https://blogs.msdn.microsoft.com/aaron_margosis/2009/06/06/faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app/ 14 | // 15 | // MIT License: 16 | // https://github.com/jay/RunAsDesktopUser/blob/master/LICENSE 17 | 18 | #include 19 | 20 | #include "RunAsDesktopUser_Utils.h" 21 | 22 | using namespace std; 23 | 24 | // Definition of the function this sample is all about. 25 | // The szApp, szCmdLine, szCurrDir, si, and pi parameters are passed directly to CreateProcessWithTokenW. 26 | // sErrorInfo returns text describing any error that occurs. 27 | // Returns "true" on success, "false" on any error. 28 | // It is up to the caller to close the HANDLEs returned in the PROCESS_INFORMATION structure. 29 | bool RunAsDesktopUser( 30 | __in const wchar_t * szApp, 31 | __in wchar_t * szCmdLine, 32 | __in const wchar_t * szCurrDir, 33 | __in STARTUPINFOW & si, 34 | __inout PROCESS_INFORMATION & pi, 35 | __inout wstringstream & sErrorInfo) 36 | { 37 | HANDLE hShellProcess = NULL, hShellProcessToken = NULL, hPrimaryToken = NULL; 38 | HWND hwnd = NULL; 39 | DWORD dwPID = 0; 40 | BOOL ret; 41 | DWORD dwLastErr; 42 | 43 | // Enable SeIncreaseQuotaPrivilege in this process. (This won't work if current process is not elevated.) 44 | HANDLE hProcessToken = NULL; 45 | if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken)) 46 | { 47 | dwLastErr = GetLastError(); 48 | sErrorInfo << L"OpenProcessToken failed: " << SysErrorMessageWithCode(dwLastErr); 49 | return false; 50 | } 51 | else 52 | { 53 | TOKEN_PRIVILEGES tkp; 54 | tkp.PrivilegeCount = 1; 55 | LookupPrivilegeValueW(NULL, SE_INCREASE_QUOTA_NAME, &tkp.Privileges[0].Luid); 56 | tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 57 | AdjustTokenPrivileges(hProcessToken, FALSE, &tkp, 0, NULL, NULL); 58 | dwLastErr = GetLastError(); 59 | CloseHandle(hProcessToken); 60 | if (ERROR_SUCCESS != dwLastErr) 61 | { 62 | sErrorInfo << L"AdjustTokenPrivileges failed: " << SysErrorMessageWithCode(dwLastErr); 63 | return false; 64 | } 65 | } 66 | 67 | retry: 68 | // Get an HWND representing the desktop shell. 69 | // CAVEATS: This will fail if the shell is not running (crashed or terminated), or the default shell has been 70 | // replaced with a custom shell. This also won't return what you probably want if Explorer has been terminated and 71 | // restarted elevated. 72 | hwnd = GetShellWindow(); 73 | if (NULL == hwnd) 74 | { 75 | sErrorInfo << L"No desktop shell is present"; 76 | return false; 77 | } 78 | 79 | // Get the PID of the desktop shell process. 80 | GetWindowThreadProcessId(hwnd, &dwPID); 81 | if (0 == dwPID) 82 | { 83 | sErrorInfo << L"Unable to get PID of desktop shell."; 84 | return false; 85 | } 86 | 87 | // Open the desktop shell process in order to query it (get the token) 88 | hShellProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPID); 89 | if (!hShellProcess) 90 | { 91 | dwLastErr = GetLastError(); 92 | sErrorInfo << L"Can't open desktop shell process: " << SysErrorMessageWithCode(dwLastErr); 93 | return false; 94 | } 95 | 96 | // Make sure window and process id are still the same (ie we've opened the right process) 97 | if(hwnd != GetShellWindow()) { 98 | CloseHandle(hShellProcess); 99 | goto retry; 100 | } 101 | GetWindowThreadProcessId(hwnd, &dwPID); 102 | if(dwPID != GetProcessId(hShellProcess)) { 103 | CloseHandle(hShellProcess); 104 | goto retry; 105 | } 106 | 107 | // From this point down, we have handles to close, so make sure to clean up. 108 | 109 | bool retval = false; 110 | // Get the process token of the desktop shell. 111 | ret = OpenProcessToken(hShellProcess, TOKEN_DUPLICATE, &hShellProcessToken); 112 | if (!ret) 113 | { 114 | dwLastErr = GetLastError(); 115 | sErrorInfo << L"Can't get process token of desktop shell: " << SysErrorMessageWithCode(dwLastErr); 116 | goto cleanup; 117 | } 118 | 119 | // Duplicate the shell's process token to get a primary token. 120 | // Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation). 121 | const DWORD dwTokenRights = TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID; 122 | ret = DuplicateTokenEx(hShellProcessToken, dwTokenRights, NULL, SecurityImpersonation, TokenPrimary, &hPrimaryToken); 123 | if (!ret) 124 | { 125 | dwLastErr = GetLastError(); 126 | sErrorInfo << L"Can't get primary token: " << SysErrorMessageWithCode(dwLastErr); 127 | goto cleanup; 128 | } 129 | 130 | // Start the target process with the new token. 131 | ret = CreateProcessWithTokenW( 132 | hPrimaryToken, 133 | 0, 134 | szApp, 135 | szCmdLine, 136 | 0, 137 | NULL, 138 | szCurrDir, 139 | &si, 140 | &pi); 141 | if (!ret) 142 | { 143 | dwLastErr = GetLastError(); 144 | sErrorInfo << L"CreateProcessWithTokenW failed: " << SysErrorMessageWithCode(dwLastErr); 145 | goto cleanup; 146 | } 147 | 148 | retval = true; 149 | 150 | cleanup: 151 | // Clean up resources 152 | CloseHandle(hShellProcessToken); 153 | CloseHandle(hPrimaryToken); 154 | CloseHandle(hShellProcess); 155 | return retval; 156 | } 157 | 158 | 159 | -------------------------------------------------------------------------------- /RunAsDesktopUser-mod/ExecInExplorer-mod/ExecInExplorer-mod/ExecInExplorer_Implementation.cpp: -------------------------------------------------------------------------------- 1 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 2 | // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 3 | // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 4 | // PARTICULAR PURPOSE. 5 | // 6 | // Copyright (c) Microsoft Corporation. All rights reserved 7 | // Copyright (C) 2017 Jay Satiro 8 | // 9 | // ShellExecInExplorerProcess() has been modified to take optional args and dir. 10 | // 11 | // Unmodified project at: 12 | // https://github.com/Microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/winui/shell/appplatform/ExecInExplorer 13 | // 14 | // MIT License: 15 | // https://github.com/jay/ExecInExplorer/blob/master/LICENSE 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #pragma comment(lib, "shlwapi.lib") 23 | 24 | // use the shell view for the desktop using the shell windows automation to find the 25 | // desktop web browser and then grabs its view 26 | // 27 | // returns: 28 | // IShellView, IFolderView and related interfaces 29 | 30 | HRESULT GetShellViewForDesktop(REFIID riid, void **ppv) 31 | { 32 | *ppv = NULL; 33 | 34 | IShellWindows *psw; 35 | HRESULT hr = CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psw)); 36 | if (SUCCEEDED(hr)) 37 | { 38 | HWND hwnd; 39 | IDispatch* pdisp; 40 | VARIANT vEmpty = {}; // VT_EMPTY 41 | if (S_OK == psw->FindWindowSW(&vEmpty, &vEmpty, SWC_DESKTOP, (long*)&hwnd, SWFO_NEEDDISPATCH, &pdisp)) 42 | { 43 | IShellBrowser *psb; 44 | hr = IUnknown_QueryService(pdisp, SID_STopLevelBrowser, IID_PPV_ARGS(&psb)); 45 | if (SUCCEEDED(hr)) 46 | { 47 | IShellView *psv; 48 | hr = psb->QueryActiveShellView(&psv); 49 | if (SUCCEEDED(hr)) 50 | { 51 | hr = psv->QueryInterface(riid, ppv); 52 | psv->Release(); 53 | } 54 | psb->Release(); 55 | } 56 | pdisp->Release(); 57 | } 58 | else 59 | { 60 | hr = E_FAIL; 61 | } 62 | psw->Release(); 63 | } 64 | return hr; 65 | } 66 | 67 | // From a shell view object gets its automation interface and from that gets the shell 68 | // application object that implements IShellDispatch2 and related interfaces. 69 | 70 | HRESULT GetShellDispatchFromView(IShellView *psv, REFIID riid, void **ppv) 71 | { 72 | *ppv = NULL; 73 | 74 | IDispatch *pdispBackground; 75 | HRESULT hr = psv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&pdispBackground)); 76 | if (SUCCEEDED(hr)) 77 | { 78 | IShellFolderViewDual *psfvd; 79 | hr = pdispBackground->QueryInterface(IID_PPV_ARGS(&psfvd)); 80 | if (SUCCEEDED(hr)) 81 | { 82 | IDispatch *pdisp; 83 | hr = psfvd->get_Application(&pdisp); 84 | if (SUCCEEDED(hr)) 85 | { 86 | hr = pdisp->QueryInterface(riid, ppv); 87 | pdisp->Release(); 88 | } 89 | psfvd->Release(); 90 | } 91 | pdispBackground->Release(); 92 | } 93 | return hr; 94 | } 95 | 96 | HRESULT ShellExecInExplorerProcess(PCWSTR pszFile, PCWSTR pszArgs, PCWSTR pszDir) 97 | { 98 | IShellView *psv; 99 | HRESULT hr = GetShellViewForDesktop(IID_PPV_ARGS(&psv)); 100 | if (SUCCEEDED(hr)) 101 | { 102 | IShellDispatch2 *psd; 103 | hr = GetShellDispatchFromView(psv, IID_PPV_ARGS(&psd)); 104 | if (SUCCEEDED(hr)) 105 | { 106 | BSTR bstrFile = SysAllocString(pszFile); 107 | hr = bstrFile ? S_OK : E_OUTOFMEMORY; 108 | if (SUCCEEDED(hr)) 109 | { 110 | VARIANT vtArgs = {}; 111 | if (pszArgs && *pszArgs) 112 | { 113 | vtArgs.vt = VT_BSTR; 114 | vtArgs.bstrVal = SysAllocString(pszArgs); 115 | hr = vtArgs.bstrVal ? S_OK : E_OUTOFMEMORY; 116 | } 117 | if (SUCCEEDED(hr)) 118 | { 119 | VARIANT vtDir = {}; 120 | if (pszDir && *pszDir) 121 | { 122 | vtDir.vt = VT_BSTR; 123 | vtDir.bstrVal = SysAllocString(pszDir); 124 | hr = vtDir.bstrVal ? S_OK : E_OUTOFMEMORY; 125 | } 126 | if (SUCCEEDED(hr)) 127 | { 128 | /* Note using an app path directly here like L"notepad++.exe" may appear to 129 | work but it's misleading. That first param requires a BSTR not wchar_t *. 130 | The underlying type is the same but the data is stored differently. 131 | Due to a quirk in the way explorer receives the arguments it may appear 132 | to work arbitrarily (or execute something different entirely). 133 | Wrong: 134 | hr = psd->ShellExecuteW(L"notepad++.exe", vtArgs, vtDir, vtEmpty, vtEmpty); 135 | 136 | To see all the app paths refer to: 137 | HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths 138 | AppPaths are used by ShellExecute if the command isn't found in the path. 139 | 140 | Note also for first param that bstrFile it can be a full path to the exe. 141 | 142 | Note the third parameter can be the directory of bstrFile if it's just a 143 | filename. However it can also be the working directory. If the directory 144 | is not found or inaccessible then it will crop it to the root. 145 | eg curdir of C:\Windows\doesnotexist explorer changes it to curdir C:\ 146 | */ 147 | VARIANT vtEmpty = {}; // VT_EMPTY 148 | hr = psd->ShellExecuteW(bstrFile, vtArgs, vtDir, vtEmpty, vtEmpty); 149 | VariantClear(&vtDir); 150 | } 151 | VariantClear(&vtArgs); 152 | } 153 | SysFreeString(bstrFile); 154 | } 155 | psd->Release(); 156 | } 157 | psv->Release(); 158 | } 159 | return hr; 160 | } 161 | --------------------------------------------------------------------------------