
Run as standard (limited) user
7plus is a very generic program that is meant to do all sorts of things you can do with AutoHotkey. It uses an event-based system with triggers, conditions and actions that allow the user to create a lot of different functions. Many are centered around hotkeys, explorer improvements, window management,...
It stores the settings in the user directory by default, so it supports multi-user environments. This is up to the user of course.
The task scheduler solution I showed in the last post has another disadvantage: It doesn't allow to retrieve the process id which is needed to wait for the end of execution.


Any success here yet? I even tried implementing it in C++ but I still get ERROR_INVALID_NAME when trying to run a simple command like "cmd". I also read lots of discussions everywhere but I couldn't find one that came to a properly working conclusion. The only completely working suggestion seems to involve running a second, non-elevated process along with the elevated process that performs the execution.
At moment I'm dedicating to something else. You tried writing it in C++ and it still throws that error?
You are coding a unicode version right? You are running the program on XP? If yes, have you tried it on Vista/7?

// Some source code from AHK // Locale independent ctype (applied to the ASCII characters only) // isctype/iswctype affects the some non-ASCII characters. inline int cisctype(TBYTE c, int type) { return (c & (~0x7F)) ? 0 : _isctype(c, type); } #define cisupper(c) cisctype(c, _UPPER) #define cislower(c) cisctype(c, _LOWER) inline TCHAR ctoupper(TBYTE c) { return cislower(c) ? (c & ~0x20) : c; } inline TCHAR ctolower(TBYTE c) { return cisupper(c) ? (c | 0x20) : c; } #include "stdafx.h" #include <windows.h> #ifndef SECURITY_MANDATORY_HIGH_RID #define SECURITY_MANDATORY_UNTRUSTED_RID (0x00000000L) #define SECURITY_MANDATORY_LOW_RID (0x00001000L) #define SECURITY_MANDATORY_MEDIUM_RID (0x00002000L) #define SECURITY_MANDATORY_HIGH_RID (0x00003000L) #define SECURITY_MANDATORY_SYSTEM_RID (0x00004000L) #define SECURITY_MANDATORY_PROTECTED_PROCESS_RID (0x00005000L) #endif #ifndef TokenIntegrityLevel #define TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25) #endif /* #ifndef TOKEN_MANDATORY_LABEL typedef struct { SID_AND_ATTRIBUTES Label; } TOKEN_MANDATORY_LABEL; #endif */ typedef BOOL (WINAPI *defCreateProcessWithTokenW) (HANDLE,DWORD,LPCWSTR,LPWSTR,DWORD,LPVOID,LPCWSTR,LPSTARTUPINFOW,LPPROCESS_INFORMATION); // Writes Integration Level of the process with the given ID into pu32_ProcessIL // returns Win32 API error or 0 if succeeded DWORD GetProcessIL(DWORD u32_PID, DWORD* pu32_ProcessIL) { *pu32_ProcessIL = 0; HANDLE h_Process = 0; HANDLE h_Token = 0; DWORD u32_Size = 0; BYTE* pu8_Count = 0; DWORD* pu32_ProcIL = 0; TOKEN_MANDATORY_LABEL* pk_Label = 0; h_Process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, u32_PID); if (!h_Process) goto _CleanUp; if (!OpenProcessToken(h_Process, TOKEN_QUERY, &h_Token)) goto _CleanUp; if (!GetTokenInformation(h_Token, TokenIntegrityLevel, NULL, 0, &u32_Size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) goto _CleanUp; pk_Label = (TOKEN_MANDATORY_LABEL*) HeapAlloc(GetProcessHeap(), 0, u32_Size); if (!pk_Label) goto _CleanUp; if (!GetTokenInformation(h_Token, TokenIntegrityLevel, pk_Label, u32_Size, &u32_Size)) goto _CleanUp; pu8_Count = GetSidSubAuthorityCount(pk_Label->Label.Sid); if (!pu8_Count) goto _CleanUp; pu32_ProcIL = GetSidSubAuthority(pk_Label->Label.Sid, *pu8_Count-1); if (!pu32_ProcIL) goto _CleanUp; *pu32_ProcessIL = *pu32_ProcIL; SetLastError(ERROR_SUCCESS); _CleanUp: DWORD u32_Error = GetLastError(); if (pk_Label) HeapFree(GetProcessHeap(), 0, pk_Label); if (h_Token) CloseHandle(h_Token); if (h_Process) CloseHandle(h_Process); return u32_Error; } LPTSTR tcscasestr(LPCTSTR phaystack, LPCTSTR pneedle) // To make this work with MS Visual C++, this version uses tolower/toupper() in place of // _tolower/_toupper(), since apparently in GNU C, the underscore macros are identical // to the non-underscore versions; but in MS the underscore ones do an unconditional // conversion (mangling non-alphabetic characters such as the zero terminator). MSDN: // tolower: Converts c to lowercase if appropriate // _tolower: Converts c to lowercase // Return the offset of one string within another. // Copyright (C) 1994,1996,1997,1998,1999,2000 Free Software Foundation, Inc. // This file is part of the GNU C Library. // The GNU C Library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // The GNU C Library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // You should have received a copy of the GNU Lesser General Public // License along with the GNU C Library; if not, write to the Free // Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA // 02111-1307 USA. // My personal strstr() implementation that beats most other algorithms. // Until someone tells me otherwise, I assume that this is the // fastest implementation of strstr() in C. // I deliberately chose not to comment it. You should have at least // as much fun trying to understand it, as I had to write it :-). // Stephen R. van den Berg, [email protected] // Faster looping by precalculating bl, bu, cl, cu before looping. // 2004 Apr 08 Jose Da Silva, [email protected]@com { register const TBYTE *haystack, *needle; register unsigned bl, bu, cl, cu; haystack = (const TBYTE *) phaystack; needle = (const TBYTE *) pneedle; // Since ctolower returns TCHAR (which is signed in ANSI builds), typecast to // TBYTE first to promote characters \x80-\xFF to unsigned 32-bit correctly: bl = (TBYTE)ctolower(*needle); if (bl != '\0') { // Scan haystack until the first character of needle is found: bu = (TBYTE)ctoupper(bl); haystack--; /* possible ANSI violation */ do { cl = *++haystack; if (cl == '\0') goto ret0; } while ((cl != bl) && (cl != bu)); // See if the rest of needle is a one-for-one match with this part of haystack: cl = (TBYTE)ctolower(*++needle); if (cl == '\0') // Since needle consists of only one character, it is already a match as found above. goto foundneedle; cu = (TBYTE)ctoupper(cl); ++needle; goto jin; for (;;) { register unsigned a; register const TBYTE *rhaystack, *rneedle; do { a = *++haystack; if (a == '\0') goto ret0; if ((a == bl) || (a == bu)) break; a = *++haystack; if (a == '\0') goto ret0; shloop: ; } while ((a != bl) && (a != bu)); jin: a = *++haystack; if (a == '\0') // Remaining part of haystack is shorter than needle. No match. goto ret0; if ((a != cl) && (a != cu)) // This promising candidate is not a complete match. goto shloop; // Start looking for another match on the first char of needle. rhaystack = haystack-- + 1; rneedle = needle; a = (TBYTE)ctolower(*rneedle); if ((TBYTE)ctolower(*rhaystack) == (int) a) do { if (a == '\0') goto foundneedle; ++rhaystack; a = (TBYTE)ctolower(*++needle); if ((TBYTE)ctolower(*rhaystack) != (int) a) break; if (a == '\0') goto foundneedle; ++rhaystack; a = (TBYTE)ctolower(*++needle); } while ((TBYTE)ctolower(*rhaystack) == (int) a); needle = rneedle; /* took the register-poor approach */ if (a == '\0') break; } // for(;;) } // if (bl != '\0') foundneedle: return (LPTSTR) haystack; ret0: return 0; } static int ConvertRunMode(LPTSTR aBuf) // Returns the matching WinShow mode, or SW_SHOWNORMAL if none. // These are also the modes that AutoIt3 uses. { // For v1.0.19, this was made more permissive (the use of strcasestr vs. stricmp) to support // the optional word UseErrorLevel inside this parameter: if (!aBuf || !*aBuf) return SW_SHOWNORMAL; if (tcscasestr(aBuf, _T("MIN"))) return SW_MINIMIZE; if (tcscasestr(aBuf, _T("MAX"))) return SW_MAXIMIZE; if (tcscasestr(aBuf, _T("HIDE"))) return SW_HIDE; return SW_SHOWNORMAL; } // Creates a new process u16_Path with the integration level of the Explorer process (MEDIUM IL) // If you need this function in a service you must replace FindWindow() with another API to find Explorer process // The parent process of the new process will be svchost.exe if this EXE was run "As Administrator" // returns Win32 API error or 0 if succeeded DWORD _stdcall CreateProcessMediumIL(WCHAR* u16_CmdLine, WCHAR* u16_WorkingDir, WCHAR* aRunShowMode) { HANDLE h_Process = 0; HANDLE h_Token = 0; HANDLE h_Token2 = 0; PROCESS_INFORMATION k_ProcInfo = {0}; STARTUPINFOW k_StartupInfo = {0}; k_StartupInfo.dwFlags = STARTF_USESHOWWINDOW; k_StartupInfo.wShowWindow = (aRunShowMode && *aRunShowMode) ? ConvertRunMode(aRunShowMode) : SW_SHOWNORMAL; BOOL b_UseToken = FALSE; // Detect Windows Vista, 2008, Windows 7 and higher if (GetProcAddress(GetModuleHandleA("Kernel32"), "GetProductInfo")) { DWORD u32_CurIL; DWORD u32_Err = GetProcessIL(GetCurrentProcessId(), &u32_CurIL); if (u32_Err) return u32_Err; if (u32_CurIL > SECURITY_MANDATORY_MEDIUM_RID) b_UseToken = TRUE; } // Create the process normally (before Windows Vista or if current process runs with a medium IL) if (!b_UseToken) { if (!CreateProcessW(0, u16_CmdLine, 0, 0, FALSE, 0, 0, 0, &k_StartupInfo, &k_ProcInfo)) return GetLastError(); CloseHandle(k_ProcInfo.hThread); CloseHandle(k_ProcInfo.hProcess); return ERROR_SUCCESS; } defCreateProcessWithTokenW f_CreateProcessWithTokenW = (defCreateProcessWithTokenW) GetProcAddress(GetModuleHandleA("Advapi32"), "CreateProcessWithTokenW"); if (!f_CreateProcessWithTokenW) // This will never happen on Vista! return ERROR_INVALID_FUNCTION; HWND h_Progman = ::GetShellWindow(); DWORD u32_ExplorerPID = 0; GetWindowThreadProcessId(h_Progman, &u32_ExplorerPID); // ATTENTION: // If UAC is turned OFF all processes run with SECURITY_MANDATORY_HIGH_RID, also Explorer! // But this does not matter because to start the new process without UAC no elevation is required. h_Process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, u32_ExplorerPID); if (!h_Process) goto _CleanUp; if (!OpenProcessToken(h_Process, TOKEN_DUPLICATE, &h_Token)) goto _CleanUp; if (!DuplicateTokenEx(h_Token, TOKEN_ALL_ACCESS, 0, SecurityImpersonation, TokenPrimary, &h_Token2)) goto _CleanUp; if (!f_CreateProcessWithTokenW(h_Token2, 0, 0, u16_CmdLine, 0, 0, (u16_WorkingDir && *u16_WorkingDir) ? u16_WorkingDir : 0, &k_StartupInfo, &k_ProcInfo)) goto _CleanUp; SetLastError(k_ProcInfo.dwProcessId); _CleanUp: DWORD u32_Error = GetLastError(); if (h_Token) CloseHandle(h_Token); if (h_Token2) CloseHandle(h_Token2); if (h_Process) CloseHandle(h_Process); CloseHandle(k_ProcInfo.hThread); CloseHandle(k_ProcInfo.hProcess); return u32_Error; }It takes the same parameters as AHK does, and uses some AHK code to parse the options parameter. It uses the search paths to find a file, tries to add file extension when necessary and accepts parameters and working directory. It will also return the process ID so a script can wait for termination of the process.

I've wrapped your code in a function so I can replace most of my run commands. I've also added the line
ProcessId := NumGet(sProcInfo, 8, "UInt")just before the CloseHandle calls and return it at the end so I still have some feedback and a target for following commands.

Thanks ABCza for providing a solution only two months prior to me requiring it :wink:.
Lucky

I'm glad you like it!

Can anyone reproduce that with the code underneath (it should start a command prompt):
RunAsUser("cmd.exe") RunAsUser(Target, Arguments="", WorkingDirectory="") { static TASK_TRIGGER_REGISTRATION := 7 ; trigger on registration. static TASK_ACTION_EXEC := 0 ; specifies an executable action. static TASK_CREATE := 2 static TASK_RUNLEVEL_LUA := 0 static TASK_LOGON_INTERACTIVE_TOKEN := 3 objService := ComObjCreate("Schedule.Service") objService.Connect() objFolder := objService.GetFolder("\") objTaskDefinition := objService.NewTask(0) principal := objTaskDefinition.Principal principal.LogonType := TASK_LOGON_INTERACTIVE_TOKEN ; Set the logon type to TASK_LOGON_PASSWORD principal.RunLevel := TASK_RUNLEVEL_LUA ; Tasks will be run with the least privileges. colTasks := objTaskDefinition.Triggers objTrigger := colTasks.Create(TASK_TRIGGER_REGISTRATION) endTime += 1, Minutes ;end time = 1 minutes from now FormatTime,endTime,%endTime%,yyyy-MM-ddTHH`:mm`:ss objTrigger.EndBoundary := endTime colActions := objTaskDefinition.Actions objAction := colActions.Create(TASK_ACTION_EXEC) objAction.ID := "7plus run" objAction.Path := Target objAction.Arguments := Arguments objAction.WorkingDirectory := WorkingDirectory ? WorkingDirectory : A_WorkingDir objInfo := objTaskDefinition.RegistrationInfo objInfo.Author := "7plus" objInfo.Description := "Runs a program as non-elevated user" objSettings := objTaskDefinition.Settings objSettings.Enabled := True objSettings.Hidden := False objSettings.DeleteExpiredTaskAfter := "PT0S" objSettings.StartWhenAvailable := True objSettings.ExecutionTimeLimit := "PT0S" objSettings.DisallowStartIfOnBatteries := False objSettings.StopIfGoingOnBatteries := False objFolder.RegisterTaskDefinition("", objTaskDefinition, TASK_CREATE , "", "", TASK_LOGON_INTERACTIVE_TOKEN ) }

I've been looking into methods of starting an "unelevated" process from a UAC-elevated process, for use in a new installer for AutoHotkey_L. I got three different methods working (in different situations):
- fragman's function which utilizes Task Scheduler.
- Creating a restricted, medium integrity level token and passing it to CreateProcessAsUser, like in ABCza's script.
- Getting the shell to run the application on my behalf.
My test systems are:
- "VISTA": Windows Vista, with limited user account "User" and administrator "Admin".
- "EIGHT": Windows 8 x64 Release Preview, with an administrator user and UAC enabled.
1. Task Scheduler
This method did not work when the script was running from a network path with saved credentials. It worked on EIGHT after moving the script to the local drive. It did not work on VISTA, and the following was logged:
Task Scheduler did not launch task "" because user "VISTA\Admin" was not logged on when the launching conditions were met.Side note: I guess the task name is "" because the task had expired and been deleted.
Changing it to use the currently logged on user rather than the user running the current process should make it work. This is actually what I want anyway; for instance, AutoHotkey.exe runs or creates AutoHotkey.ahk in %A_MyDocuments%, which would ideally be the logged on user's folder. I'm not sure how this would be done, and frankly, I'd rather not use Task Scheduler for this purpose.
2. CreateProcessAsUser
This worked on both systems, but on VISTA it ran the script as Admin (with reduced integrity level/privileges). As mentioned above, this is not suitable for my purpose.
3. Getting the shell to run an application for you
This is an ingenius method posted by blogger BrandonLive. Because
ComObjCreate("Shell.Application")
creates an in-process object, it can't be used directly. Instead, you retrieve the shell object of the process which hosts the desktop.This method probably requires Explorer as the shell - support for custom shells is not essential for my usage. The program is always executed in the context of the process which hosts the desktop; i.e. the logged on user. This is exactly what I want.
/* ShellRun by Lexikos requires: AutoHotkey_L license: http://creativecommons.org/publicdomain/zero/1.0/ Credit for explaining this method goes to BrandonLive: http://brandonlive.com/2008/04/27/getting-the-shell-to-run-an-application-for-you-part-2-how/ Shell.ShellExecute(File [, Arguments, Directory, Operation, Show]) http://msdn.microsoft.com/en-us/library/windows/desktop/gg537745 */ ShellRun(prms*) { shellWindows := ComObjCreate("{9BA05972-F6A8-11CF-A442-00A0C90A8F39}") desktop := shellWindows.Item(ComObj(19, 8)) ; VT_UI4, SCW_DESKTOP ; Retrieve top-level browser object. if ptlb := ComObjQuery(desktop , "{4C96BE40-915C-11CF-99D3-00AA004AE837}" ; SID_STopLevelBrowser , "{000214E2-0000-0000-C000-000000000046}") ; IID_IShellBrowser { ; IShellBrowser.QueryActiveShellView -> IShellView if DllCall(NumGet(NumGet(ptlb+0)+15*A_PtrSize), "ptr", ptlb, "ptr*", psv:=0) = 0 { ; Define IID_IDispatch. VarSetCapacity(IID_IDispatch, 16) NumPut(0x46000000000000C0, NumPut(0x20400, IID_IDispatch, "int64"), "int64") ; IShellView.GetItemObject -> IDispatch (object which implements IShellFolderViewDual) DllCall(NumGet(NumGet(psv+0)+15*A_PtrSize), "ptr", psv , "uint", 0, "ptr", &IID_IDispatch, "ptr*", pdisp:=0) ; Get Shell object. shell := ComObj(9,pdisp,1).Application ; IShellDispatch2.ShellExecute shell.ShellExecute(prms*) ObjRelease(psv) } ObjRelease(ptlb) } }
Windows XP
As stated at the top of this post, my interest is only in reversing UAC elevation, so Windows XP is irrelevant. However, I was curious to see how it would behave on XP. As may be expected, it did not work:
- FindWindowSW's final parameter specifies the desktop window. This flag does not exist on XP, so the window is not found.
- If the current process was started via "Run As", ComObjCreate fails with a cryptic error message. If it is changed to
ComObjCreate("Shell.Application").Windows
, retrieval of the Windows property fails with "Class not registered".
For ease of cross-platform use, the function could be modified to call ComObjCreate("Shell.Application").ShellExecute(prms*)
in the event of a failure.
Focus Problems
I didn't have any problems. It's my understanding that the launched program is responsible for activating its window, and the OS is responsible for deciding when to prevent a program from "stealing" the focus.



I guess to find the PID you could do a quick list of all PIDs, do the ShellRun and then compare the two to find the PID (other than "process, exist" which might not find the right one, if multiple instances with the same executable exist), maybe like so (quick hack, can be improved upon):
#SingleInstance, Force #NoEnv SetBatchLines, -1 #include ShellRun.ahk if not A_IsAdmin { run *runAs "%A_ScriptFullPath%" ; Requires v1.0.92.01+ ExitApp } gosub process_list ShellRun("cmd.exe") gosub process_list loop, parse, after, `, { if A_LoopField not in %before% { PID := A_LoopField break } } msgbox % PID before := after := return process_list: ; example 4: http://www.autohotkey.com/docs/commands/Process.htm d := "," ; string separator s := 4096 ; size of buffers and arrays (4 KB) l := id := Process, Exist ; sets ErrorLevel to the PID of this running script ; Get the handle of this script with PROCESS_QUERY_INFORMATION (0x0400) h := DllCall("OpenProcess", "UInt", 0x0400, "Int", false, "UInt", ErrorLevel, "Ptr") ; Open an adjustable access token with this process (TOKEN_ADJUST_PRIVILEGES = 32) DllCall("Advapi32.dll\OpenProcessToken", "Ptr", h, "UInt", 32, "PtrP", t) VarSetCapacity(ti, 16, 0) ; structure of privileges NumPut(1, ti, 0, "UInt") ; one entry in the privileges array... ; Retrieves the locally unique identifier of the debug privilege: DllCall("Advapi32.dll\LookupPrivilegeValue", "Ptr", 0, "Str", "SeDebugPrivilege", "Int64P", luid) NumPut(luid, ti, 4, "Int64") NumPut(2, ti, 12, "UInt") ; enable this privilege: SE_PRIVILEGE_ENABLED = 2 ; Update the privileges of this process with the new access token: r := DllCall("Advapi32.dll\AdjustTokenPrivileges", "Ptr", t, "Int", false, "Ptr", &ti, "UInt", 0, "Ptr", 0, "Ptr", 0) DllCall("CloseHandle", "Ptr", t) ; close this access token handle to save memory DllCall("CloseHandle", "Ptr", h) ; close this process handle to save memory hModule := DllCall("LoadLibrary", "Str", "Psapi.dll") ; increase performance by preloading the library s := VarSetCapacity(a, s) ; an array that receives the list of process identifiers: c := 0 ; counter for process idendifiers DllCall("Psapi.dll\EnumProcesses", "Ptr", &a, "UInt", s, "UIntP", r) Loop, % r // 4 ; parse array for identifiers as DWORDs (32 bits): { id .= NumGet(a, A_Index * 4, "UInt") . d, c++ ; Open process with: PROCESS_VM_READ (0x0010) | PROCESS_QUERY_INFORMATION (0x0400) h := DllCall("OpenProcess", "UInt", 0x0010 | 0x0400, "Int", false, "UInt", id, "Ptr") if !h continue VarSetCapacity(n, s, 0) ; a buffer that receives the base name of the module: e := DllCall("Psapi.dll\GetModuleBaseName", "Ptr", h, "Ptr", 0, "Str", n, "UInt", A_IsUnicode ? s//2 : s) if !e ; fall-back method for 64-bit processes when in 32-bit mode: if e := DllCall("Psapi.dll\GetProcessImageFileName", "Ptr", h, "Str", n, "UInt", A_IsUnicode ? s//2 : s) SplitPath n, n DllCall("CloseHandle", "Ptr", h) ; close process handle to save memory if (n && e) ; if image is not null add to list: l .= n . d, c++ } DllCall("FreeLibrary", "Ptr", hModule) ; unload the library to free memory if before = before := id else after := id return


yes, I tried working that into my example, but winget processname did not yield the results I was looking for so I just left it at that, was just proof of conceptThat might be an option, however you should check if the module name fits the executable you just launched.


run, explore "%path%", % pathHow would you do that with method 3?
Also, I'd like to know the best way to convert this to method 3?
run, "%gfx_program%" "%gfx_file%", % path, UseErrorLevel

Something to note is that if you specify a relative path, the current working directory of explorer.exe is used.Shell.ShellExecute(File [, Arguments, Directory, Operation, Show])
<!-- m -->http://msdn.microsof... ... p/gg537745<!-- m -->
