 |
|
 |
This has helped me immensely in my work. Thanks so much for making it available!
Nicholas
|
|
|
|
 |
|
 |
This seems like a nice way to do it, provided you don't care about situations where the Shell is not running (e.g. possibly some Terminal Services application-only setups, perhaps, though I'm not sure):
http://brandonlive.com/2008/04/27/getting-the-shell-to-run-an-application-for-you-part-2-how/
It gets an interface to Explorer.exe, which should be running in the user's normal context, and asks Explorer to execute a command in its behalf. This is done just using simple, documented COM interfaces and without having to mess around with process tokens or code/DLL injection.
|
|
|
|
 |
|
 |
I like the idea, but has anyone got this to work? The documentation is practically non-existent.
- Pfft. Coddled kids. In my day, we used to telnet to port 80, then render the page with pencil and paper-- and that's the way we liked it!
- Pshaw! Youngster. Your UID barely fits inside 16 bits. In _my_ day we had to whistle the 1's and 0's through an acoustic coupler!
Tools that support "all of UTF-8 as long as it starts with ASCII" and tools that cannot handle these three [BOM] bytes at all are not really supporting UTF-8.
- Michael Kaplan
|
|
|
|
 |
|
 |
Hi Andrei,
First, thank you so much for your work with this!
I'm using these DLL's for my Setup program which is used by a number of people. However, I just discovered that specifying a working directory is not working. This is important for many programs run at the end of Setup/install because they are looking for files relative to the program, etc. I've looked at the source and see that the problem is that the vistalib dll's don't parse the command-line for working directory....they stop right after looking for arguments. But yet, it looks like the functionality is there in the underlying vistatools.cxx file.
Sadly, I am not really a C++ programmer (and even all I have is VC6, which doesn't seem to work), so I can't really change this. It looks like a really easy change though - just an extra parsing layer on the command-line if I'm not mistaken. Is this something you can quickly just change and either repost here or email to me?
It would be greatly appreciated (by me, and many I'm sure).
Chris
|
|
|
|
 |
|
|
 |
|
 |
Hello
Thanks for posting this link.
You are right: You can do it without DLLs - much easier.
But the code on the page you link to is very ugly and has SIX bugs!
I completely cleaned the code and fixed all the bugs.
I tested it on Windows 2000, XP, Vista and Windows 7.
It works perfectly.
If you compile and run this code "As Administrator" it will start the Calculator process under the account of the currently logged-in user. In Taskmanager you will see the new Calculator running under the same user account as Explorer. This is important in an installer because the new process must access the correct "Documents and Settings" folder.
Elmü
#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);
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;
}
DWORD CreateProcessMediumIL(WCHAR* u16_Path, WCHAR* u16_CmdLine)
{
HANDLE h_Process = 0;
HANDLE h_Token = 0;
HANDLE h_Token2 = 0;
PROCESS_INFORMATION k_ProcInfo = {0};
STARTUPINFOW k_StartupInfo = {0};
BOOL b_UseToken = FALSE;
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;
}
if (!b_UseToken)
{
if (!CreateProcessW(u16_Path, u16_CmdLine, 0, 0, FALSE, 0, 0, 0, &k_StartupInfo, &k_ProcInfo))
return GetLastError();
return ERROR_SUCCESS;
}
defCreateProcessWithTokenW f_CreateProcessWithTokenW =
(defCreateProcessWithTokenW) GetProcAddress(GetModuleHandleA("Advapi32"), "CreateProcessWithTokenW");
if (!f_CreateProcessWithTokenW) return ERROR_INVALID_FUNCTION;
HWND h_Progman = ::FindWindow("Progman", NULL);
if (!h_Progman) return ERROR_INVALID_WINDOW_HANDLE;
DWORD u32_ExplorerPID = 0;
GetWindowThreadProcessId(h_Progman, &u32_ExplorerPID);
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, u16_Path, u16_CmdLine, 0, 0, 0, &k_StartupInfo, &k_ProcInfo))
goto _CleanUp;
SetLastError(ERROR_SUCCESS);
_CleanUp:
DWORD u32_Error = GetLastError();
if (h_Token) CloseHandle(h_Token);
if (h_Token2) CloseHandle(h_Token2);
if (h_Process) CloseHandle(h_Process);
return u32_Error;
}
int main(int argc, char* argv[])
{
DWORD u32_Err = CreateProcessMediumIL(L"C:\\Windows\\System32\\Calc.exe", NULL);
printf("CreateProcessMediumIL() exited with error %d\r\n", u32_Err);
Sleep(2000);
return 0;
}
|
|
|
|
 |
|
 |
Hi Andrei,
If we ship these 2 dlls in our installer, do we need your written permission ? Thank you.
Regards,
Kenny
|
|
|
|
 |
|
 |
> If we ship these 2 dlls in our installer, do we need your written permission ? Thank you.
Kenny,
you (or anyone else) don't need a written permission, the libraries are free to use and redistribute, as long as you agree that they come with no warranties, yadda, yadda. (You can find the terms of use in the Readme.txt file included in the source code of the library).
Thanks for asking though.
Good luck!
Andrei Belogortseff
http://www.winability.com
|
|
|
|
 |
|
 |
Hi Andrei,
I am having problem with the RunNonElevated (VE32.dll) method in order to start a process on Vista.
I am having a NSIS installer which is installing an executable file and the calls RunNonElevated in order to run the installed program.
That works all fine on XP (administrator/standard user rights) and on Vista with administrator rights. But when I try to install it on Vista from a standard user account it does not work.
When I start the installer Vista pops up a window to put in credentials of an administrator with user name and password. After entering the information the NSIS installer runs, copies the executable file to the program files folder and then it calls RunNonElevated from the VE32.dll in order to start the program.
After I debugged RunNonElevated it looks like everything works as supposed to be but the hook "VistaElevator_HookProc_MsgRet" is never receiving the message "VistaElevatorMsg".
Would you have an idea what is going wrong or why the hook is never receiving the message.
It would be great if you could give me some help.
Thanks
Mike
|
|
|
|
 |
|
 |
Mike,
If I remember correctly, when I tested this code while writing the article, it used to work with the standard users.
Are you sure you are using the correct DLL? Because you mentioned VE32.DLL, which is form another article of mine. This article comes with a dll named VistaLib32.dll, you may want to try it instead of VE32.DLL.
Hope this helps.
Andrei Belogortseff
http://www.winability.com
|
|
|
|
 |
|
 |
Why are people so offended by this idea? If you haven't installed Windows Vista yet, or you haven't attempted to solve the problem on your own, then you probably shouldn't criticize.
The solution is pretty good actually, because it is low overhead, and the complications with FindWindow (that everyone seems to focusing on) are not the main point of the article. It is fixed easily by (A) finding a different window via heuristic or (B) using another method to find a non-administrative process.
|
|
|
|
 |
|
 |
I have successfully integrated your pre-built VistaLib32 and VistaLib64 DLLs into our Inno Setup-based installer (completely painlessly thanks to your detailed instructions may I add), and it does work in that it launches the app non-elevated after installation is finished on both 32- and 64-bit Vistas. So far so good.
However, on 32-bit versions of Vista, the app is launched twice most of the time. It seems to happen in something like 80% of the installs I've tried so far. On 64-bit Vista, I have never seen it launch twice.
I'm using the following lines in the InnoSetup script:
[Run]
Filename: "RunDll32.exe"; Parameters: "{code:AddQuotes|{app}\VistaLib32.dll},RunNonElevated {code:AddQuotes|{app}\LastFM.exe}"; Description: "{cm:LaunchProgram,Last.fm}"; Flags: nowait postinstall; MinVersion: 0,6
If I revert back to a normal launch, i.e.
Filename: "{app}\LastFM.exe"; Description: "{cm:LaunchProgram,Last.fm}"; Flags: nowait postinstall
it never launches twice so I can only deduce that it's the VistaLib32 DLL that does it. Any ideas why?
Thanks,
Erik
|
|
|
|
 |
|
 |
Just discovered that this only seems to happen when launching two executables after installation. The above was a simplification, the Run section looks like this:
Filename: "RunDll32.exe"; Parameters: "{code:AddQuotes|{app}\VistaLib32.dll},RunNonElevated {code:AddQuotes|{app}\LastFMHelper.exe}"; Flags: nowait runhidden; MinVersion: 0,6
Filename: "RunDll32.exe"; Parameters: "{code:AddQuotes|{app}\VistaLib32.dll},RunNonElevated {code:AddQuotes|{app}\LastFM.exe}"; Description: "{cm:LaunchProgram,Last.fm}"; Flags: nowait postinstall; MinVersion: 0,6
If I remove either line, the remaining exe launches fine, and only once. But if I leave both lines in, it seems either of the two (or both) get launched twice. Is this not a good technique when you need to launch more than one exe?
|
|
|
|
 |
|
 |
I know this article is about Vista security
But I have a similar problem with a limited user account under XP or W2K running an installer with a temporary administrator account. (through the login prompt, using "runas" context menu or when the installer is named "Install.exe" or "Setup.exe")
When the installer has finished its job, how to make it run a non-admin program, using the initial user credentials, not as the temporary administrator account
I couldn't find a good answer on Internet...
|
|
|
|
 |
|
 |
Olivier,
I did not try it, but the same method of hooking into the shell process should work with XP just as well. Of course, the code of the DLL should be modified to make the DLL load under XP (that is, all static calls to the Vista-only APIs should be changed to the dynamic ones). If you do try it, please let us know the results here. Thanks!
Andrei Belogortseff
http://www.winability.com
|
|
|
|
 |
|
 |
I am using InstallShield 12 to install our application and we would like to also have our apps triggered to run in non-elevated mode during the install so I would like to find out more about the command line to trigger it.
In InstallShield we can call an executable and set the parameters that we have to pass to it, but my question is how would you set this up if the app you are calling from the install also requires some parameters?
Can this be done or will the Rundll32.exe see the extra parameters as something passed to it and therefore not work at all, or do the parameter need to be wrapped within the exe command. ie:
RunDll32.exe "app path\VistaLib32.dll,RunNonElevated" "app path\our application.exe,-NOSPU"
If this does not support this then what would the next best thing be?
Thanks,
Tim.
|
|
|
|
 |
|
 |
> my question is how would you set this up if the app you are calling from the install also requires some parameters?
You should enter the parameters on the same command line that you pass to RunDLL32, after the application path. The sample setup script included with the article shows how to do that (look for /some_args - it illustrates how you can pass the command line paramenters to your application by calling RunDll32).
HTH
Andrei Belogortseff
http://www.winability.com
|
|
|
|
 |
|
 |
Thanks your work on this Andrei.
Yes, adding /some_args after the path to the application to execute works, but I can't seem to be able to specify the working directory for starting the application using this method (rundll32). Using:
ProcessStartInfo pi = new ProcessStartInfo("rundll32.exe");
pi.Arguments = "VistaLib32.dll,RunNonElevated \"" + fileName + "\" " + arguments + " " + workingDir;
Process.Start(pi);
The process is launched, but Process explorer shows that the arguments passed to it are {arguments workingdir} and the actual working directory is set to some default (e.g. c:\windows\system32). Is there any way to specify the working directory for the process using a call to rundll32.exe? Perhaps this has to do with RunDLL32 expecting the entrypoint function it is calling to do the parsing (as stated here http://support.microsoft.com/kb/164787)...
As an alternative I tried to call RunNonElevated directly (from C#), however my DLLImport prototype must be wrong, because I just get a messagebox entitled RunNonElevatedA with some garbage characters.
[DllImport("VistaLib32.dll", EntryPoint = "RunNonElevated")]
public static extern bool RunNonElevated(
IntPtr hwnd,
[MarshalAs(UnmanagedType.LPTStr)] string filePath,
[MarshalAs(UnmanagedType.LPTStr)] string arguments,
[MarshalAs(UnmanagedType.LPTStr)] string workingDir);
Can you offer any advice how to achieve this? Thanks.
|
|
|
|
 |
|
 |
Ok I figured this out - firstly I was looking at the source to VC32.dll which was confusing me. Once I found the correct source for VistaLib32.dll, I saw that only RunNonElevatedA and RunNonElevatedW are exported as C functions in the .def file. And these two functions do not support passing a working directory for the process to be created. Additionally, there is a bug in RunNonElevatedA caused by the following two lines:
TCHAR szCmdLineW[ MAX_PATH ];
MessageBox( hwnd, szCmdLineW, _T("RunNonElevatedA"), MB_OK );
which shows the message box I was seeing with garbage characters. This should be converting the ANSI string to wide like RunElevatedA.
So the solution for me was adding RunNonElevated to DLL.def. Then I can use the following DLLImport:
[DllImport("VistaLib32.dll", EntryPoint = "RunNonElevated")]
public static extern int RunNonElevated(
IntPtr hwnd,
[MarshalAs(UnmanagedType.LPTStr)] string filePath,
[MarshalAs(UnmanagedType.LPTStr)] string arguments,
[MarshalAs(UnmanagedType.LPTStr)] string workingDir,
IntPtr hProcess);
Once again thanks for writing an article about this and providing source code. I find it incredible that MS didn't include a API for doing this. But then that's consistent with the mess that is Vista
|
|
|
|
 |
|
 |
I wonder if your library can be used to solve the problem of elevation privileges in Windows Vista when the setup is run by a standard (non admin) user. The desktop icons and all the Current User registry settings are saved to the admin account and not the standard user account. Any idea on how to fix that?
Thanks.
Andrea
|
|
|
|
 |
|
 |
> I wonder if your library can be used to solve the problem of elevation privileges in Windows Vista when the setup is run by a standard (non admin) user. The desktop icons and all the Current User registry settings are saved to the admin account and not the standard user account. Any idea on how to fix that?
Andrea,
I see that you have asked this question in the InnoSetup newsgroup as well and Jordan has already answered it.
No, this DLL will not solve such a problem directly. If you must create the user-specific items during the installation, then you can solve the problem by creating a separate executable that does only the user-specific customizations, and you can launch it non-elevated from the main installer using my DLL. However, an easier way would be to do what Jordan suggested: do not create any user-specifc items during the install. Instead, let your program detect when it's being run for the first time, and if so, create those items from within the program itself.
However, that would not remove such items during uninstall... It may be better not to create any user-specific items at all...
Good luck!
Andrei Belogortseff
http://www.winability.com
|
|
|
|
 |
|
 |
Thank you.
I'm thinking about creating an /uninstall parameter for my application so that when the user runs the Uninstall, it can be called using your library (with non elevated privileges).
I'm wondering what happens in this scenario.
User Standard actually uses the application but he's not an administrator.
When Standard uninstall the application, Vista requires admin privileges so user Admin is the one who actually runs the setup. If I launch an application using your library who's the active user? The admin or the standard one? If it's the admin, there's no way I can remove any setting from the registry who are specific to the user who's actually running the application.
Thanks.
|
|
|
|
 |
|
 |
Hey,
I am not able to build VistaLib solution. Following error is reported,
1>e:\study\vista\vistalib\source\vistatools.cxx(179) : error C2065: 'TOKEN_ELEVATION_TYPE' : undeclared identifier
1>e:\study\vista\vistalib\source\vistatools.cxx(179) : error C2065: 'ptet' : undeclared identifier
1>e:\study\vista\vistalib\source\vistatools.cxx(179) : fatal error C1903: unable to recover from previous error(s); stopping compilation
I know that these errors are due to not having proper header file (Platform SDK). So, downloaded the latest SDK from followint site:
http://www.microsoft.com/downloads/details.aspx?FamilyId=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en[^]
and downloaded PSDK-x86.exe
Do I have to download anything else or am I missing something here.
Please let me know on this.
Thanks,
Venki
|
|
|
|
 |
|
 |
Hello Venki,
I was able to resolve the issue you indicated by configuring Visual Studio 2005 SP1 in the following manner:
Tools > Options > Projects and Solutions > VC++ Directories > Platform: Win32
> Executable Files
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\bin
> Include Files
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\Include
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\Include\gl
> Library files
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\lib
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\Lib\x64
> Exclude Directories
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\Include
Add C:\Program Files\Microsoft SDKs\Windows\v6.0\Include\gl
I had to place these directories first (at the very top) because they are searched in that order (top to bottom)
Hope that works out for you.
-Rami
|
|
|
|
 |
|
 |
Thanks Rami,
I'll check this out.
Regards,
Venki
|
|
|
|
 |