|
|
Introduction
Many installation packages offer an option for the user to launch the application at the end of the installation:

It works, except for one small problem: if installed on a Vista computer, the application started in such a way gets executed at the elevated level, with the full administrator rights. This article discusses why this is bad, and offers a reusable DLL that can be included in the existing installation packages to run the application non-elevated at the end of the installation. A sample setup script for the popular InnoSetup software is provided as well. The included source code also contains several other functions that can be of use when programming for Windows Vista.
Background
You've followed the Microsoft guidelines and carefully updated your application to run well in the context of a restricted user (non-administrator). You have added the asInvoker value to the application manifest, to make sure that when the user launches your application, it is started without the administrative privileges. You've tested it, it works well, and you think your new Vista-compatible version is ready for the release. So you create the setup package and test it, and now you are up for a nasty surprise: if you select the option to launch the application at the end of the installation, the application gets started as administrator, with full administrative privileges.
Why running your application elevated is bad? Because things may break unexpectedly. For example:
- When started elevated, your application gets access to the folders where normally it should not get access to. Aside from the obvious security implications, it may create problems for your users: if the user saves a document into one of such folders, then she or he won't be able to open that document the next time she or he runs your application (because when the user starts the application next time, it will get executed without the administrative rights!)
- If your application is installed by a "real" standard user (who is not a member of the Administrators group), and the installation has been authorized by the actual administrator (by entering his password in the UAC prompt "over the shoulder" of the standard user), then when your application is started by the installer, it gets access to the administrator's personal folders (My Documents, etc.), different from those of the standard user. The administrators don't like it when other users get unrestricted access to their personal folders
- If your application needs to interact with the shell (for example, to react to the notification messages broadcasted by the shell, etc.), such interaction may break: by default, Vista UAC prevents most messages sent from the non-elevated applications (such as the shell) from reaching the elevated processes.
- If your application creates secondary processes (for example, to show a taskbar notification icon, or run a hot-key monitor, etc.), such processes will start elevated, too. If they need to interact with the shell, such interaction may break, as well.
- And last but not the least: you never know what kind of vulnerability might be discovered in your application that might open the way for the badware to the high elevation. A virus might be sitting on the user's computer and quietly waiting for an application like yours to start elevated, so that it could hijack your elevated process to elevate itself and start doing bad things with full administrative rights.
Why does your application run elevated when started automatically by the installer? Because the installation process runs with the full administrative rights, and when it creates child processes, such as the one for your application, such processes execute at the same elevated level, just like the setup program itself.
How to solve this problem?
If you've searched the Microsoft SDK documentation for a solution, you've undoubtedly been up for another surprise, even nastier than the first one: Microsoft has not provided for a way to start a non-elevated process from an elevated one. That's right, there is no API call, no special value to specify in the manifest, not even a flag for the ShellExecute() API to allow for that. If you've found yourself stuck in this situation, read on, this article is for you.
Let's consider the options that we have:
- We could create a separate helper executable that would help our main application launch a non-elevated task, when necessary. That is, it could work as follows:
- When a user wants to install the application (by running setup.exe), she would start by launching the helper executable (helper.exe) first.
- The helper process would start non-elevated, but it would launch setup.exe, which would start elevated, by means of the
requireAdministrator value in the setup.exe's manifest.
- After the installation is complete, setup.exe would signal back to helper.exe that the user wants to start the application (app.exe). Having received the signal, helper.exe would start app.exe on setup.exe's behalf. Since helper.exe was started non-elevated, it would start app.exe non-elevated, too.
This method would work, but it's messy, since it requires creating a separate helper executable, as well as designing a communication protocol between the setup utility and the helper, which is not a trivial task.
- A simpler approach could be by making use of the capabilities of the built-in Task Scheduler of Windows Vista: our elevated process could register a task with Task Scheduler to be started at the non-elevated level immediately upon its registration. (I've described this method in detail in my previous article Vista Elevator). This method is much easier to implement than the previous one, and it works rather well when the installation is preformed by the administrator. However, if the application is installed by a standard user (with the administrator authorizing the installation "over the shoulder" of the standard user), the procedure does not work as expected: Task Scheduler is scheduling the application to start in the context of the administrator, rather than in the context of the original standard user. The application would launch not at the end of the installation, but later on, when the administrator logs on to the system, which is far from what one would expect.
Another problem with the second method is that the target machine could have Task Scheduler disabled. In such a case, this method would fail to start the application at all.
- Instead of creating a helper executable to launch our application, we could find an existing non-elevated process already running on the target computer, and make it start a non-elevated process on our behalf by injecting our code into that process. The perfect candidate for the code injection is the Windows shell process: it is running non-elevated, and we can be sure it is always present on a computer running Windows Vista (when was the last time you saw a Windows computer without its shell running?).
The Solution: Code Injection in the shell process
Specifically, this method could work as follows:
- The elevated process would find a window that belongs to the shell, and that is guaranteed to be available at any time. A good window for this purpose is "Progman": it is responsible for displaying the user's desktop. We can call the
FindWindow() API to obtain a handle to this window:
HWND hwndShell = ::FindWindow( _T("Progman"), NULL);
- Our elevated process would call the
RegisterWindowsMessage() API to register a unique message that we would use to communicate with the shell's Window:
uVEMsg = ::RegisterWindowMessage( _T("VistaElevatorMsg") );
- Our elevated process would call
SetWindowsHookEx() API to install a global hook, to be invoked when a Windows message gets processed by any process running on the system:
hVEHook = ::SetWindowsHookEx( WH_CALLWNDPROCRET,
(HOOKPROC)VistaElevator_HookProc_MsgRet, hModule, 0);
- Once the hook is installed we would send our unique message to the shell's window, and that would make our hook procedure get invoked. (That's how we inject our code into the shell process!):
::SendMessage( hwndShell, uVEMsg, 0, 0 );
- When the hook procedure is called (in the context of the shell process), it would call
ShellExecute() API to launch the non-elevated process that we need. Such a process would start non-elevated because the shell's process is not elevated, and our process would inherit the shell's elevation level:
LRESULT CALLBACK
VistaElevator_HookProc_MsgRet( int code, WPARAM wParam, LPARAM lParam )
{
if ( code >= 0 && lParam )
{
CWPRETSTRUCT * pwrs = (CWPRETSTRUCT *)lParam;
if (pwrs->message == uVEMsg )
{
bVESuccess = VistaTools::MyShellExec(
pwrs->hwnd,
NULL,
szVE_Path,
szVE_Parameters,
szVE_Directory,
bVE_NeedProcessHandle ? &hVE_Process : NULL );
}
}
return ::CallNextHookEx( hVEHook, code, wParam, lParam );
}
- Finally, we would remove the hook, as we no longer need it:
::UnhookWindowsHookEx( hVEHook );
Using the code in C++ projects
The method described above is implemented as the function RunNonElevated(), defined in the file VistaTools.cxx. If you need to start a non-elevated process from your own application, you can add this file to your C++ project and call this function directly. The detailed instructions on how to use the file and this function are provided in the VistaTools.cxx file itself.
Note, however, that in order for the RunNonElevated() function to work, you must compile it in a DLL project. The reason for that is that the global hook code needs to reside in a DLL, it cannot be in an executable file. Also note that if you plan to run the code under the x64 versions of Windows as well, you need to compile a separate 64-bit version of the DLL in order for it to work as expected. The reason for this is that on the x64 versions of Windows, the shell is a native 64-bit process. In order to inject our code into it, your DLL must contain the native 64-bit code too.
Note also that VistaTools.cxx contains several other functions that you may find of use when programming for Windows Vista, such as IsVista(), GetElevationType(), and more. They are described in the file itself.
Using the code in the setup scripts
If you don't use C++, or if all you need is call the RunNonElevated() function from your setup script, then you can use the precompiled DLLs I've included in the file Redist.zip. This package contains the VistaLib32.dll and VistaLib64.dll files which export the RunNonElevated() function in such a way that you can invoke it by running the RunDll32.exe utility (which is part of the standard Windows distributions).
For example, if you use InnoSetup (a popular software installation package), then usually when you want to run your application at the end of the installation, you would include the following lines in your setup script:
[Run]
Filename: "{app}\SampleApp32.EXE"; Description: "Launch application";
The above command launches the SampleApp32.EXE, that would start at the elevated level. To launch the same application at the non-elevated level, you would change the above lines to:
[Run]
Filename: "RunDll32.exe"; Parameters:
"{code:AddQuotes|{app}\VistaLib32.dll},RunNonElevated
{code:AddQuotes|{app}\SampleApp32.EXE}";
Description: "Launch application";
Such a command would make the setup utility launch RunDll32.exe at the end of the installation. It, in turn, would load VistaLib32.dll and call its RunNonElevated entry point, passing the path to our application SampleApp32.EXE, enclosed in double quotation marks, to it. (Optional command line parameters for the application can be passed there, as well.) SampleApp32.exe is a very simple application that does nothing except that it calls a few functions exported by the VistaLib32.dll and displays the result in a message box:

To see it all in action, download the file SampleApp-setup.zip (see the link at the top of this article). It contains a pre-compiled setup utility that installs the sample application and runs it non-elevated at the end of the installation.
What about backward compatibility?
The code in VistaLib32/64.dll is backward compatible down to Windows 2000 (I did not test it with the earlier versions of Windows). If you call RunNonElevated() under Windows XP or 2000, it would simply use the regular ShellExecute() API to launch the application, as if ShellExecute() was called directly. This means that you can use the same setup script with both Vista and pre-Vista versions of Windows.
Note, however, that the setup script shown above is for the 32-bit version of Windows. To make it work with 64-bit versions, you need to modify the script to use the file VistaLib64.dll instead of VistaLib32.dll.
More information
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 40 (Total in Forum: 40) (Refresh) | FirstPrevNext |
|
|
 |
|
|
Hi Andrei,
If we ship these 2 dlls in our installer, do we need your written permission ? Thank you.
Regards, Kenny
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
> 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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
> 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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
> 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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I also find the idea of injecting code into another process, especially a process that you didn't write and have no control over, scary.
Here's an alternative idea:
The setup program's process runs unelevated but, through the UAC API, creates an elevated COM object to use for those parts of the install which require administrator rights. The COM object only has to be created once and can then be re-used, so there would still only be a single UAC prompt. Since the main process is still running unelevated it can launch other unelevated applications directly, and since it has its COM object it can perform whatever elevated actions it needs to through the course of the installation.
I believe this is a far better way to do the job although it does, of course, require modification of the installer itself. So it's something that should be built into InnoSetup (etc.) rather than something we can easily do as users of the installer creators.
BTW, creating a single elevated COM object and then keeping it around for several operations is a perfectly legal use of the UAC model. It's been tried and tested in Directory Opus 9 (see my guide here: http://nudel.dopus.com/opus9/page4.html#vistauac ). The only reason that Explorer and many installers pop up multiple UAC prompts for the same logical operation is lazy programming.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
> I believe this is a far better way to do the job
I agree!
> although it does, of course, require modification of the installer itself.
And that's the drawback. If you are willing to rewrite the installer, then it's much better to redesign it and make it work like you described, or maybe split it in two parts, one to run elevated and perform the real administrative operations, and another one, to run non-elevated, to perform the user-specific configuration (sort of like the helper and the main modules I described in in the article).
However, if rewriting the installer is not an option, then the code injection method should get the job done.
Andrei Belogortseff http://www.winability.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
InnoSetup can call DLLs directly (in the CODE section).
Of course some thinking is needed if using C++ because of the different declaration syntax. Samples are included with ISS.
Did you submit this in the InnoSetup forum or to the author? I think it might be a hot candidate for an internal feature of InnoSetup to run myApp unelevated.
Regards, RuneB.
P.S. Off Topic I am also behind suggesting changesto InnoSetup, I have the need to change / add to the space needed and to request it seperately for TEMP.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
> Why using RunDLL32.exe
I wanted to illustrate the point in the simplest posible way, that's why I chose RunDll32. Of course, there are other ways of achieving the goal, everyone is free to use them.
> Did you submit this in the InnoSetup forum or to the author?
Yes, I did.
Thanks,
Andrei Belogortseff http://www.winability.com
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
| | |