Releasing executable file from running process

Hello :slight_smile:

I’ve written an uninstall tool that removes our software. The initial design for this software envisaged it being a downloadable tool that users (administrators) would typically run from their desktops. However, a recent development has led to the tool being included as a file within our software distribution. There’s one catch: the tool wants to remove the very directory it is running out of!

At this point, I’ve come to a partial solution in the following way:

* When the app launches, if it detects that it is in the folder it wishes to delete, it makes a copy of itself to the TEMP folder and launches that copy.
* The initial instance then exits, ensuring the file is no longer being held open (at least by itself).
* The copy running out of TEMP does the uninstallation.
* When the copy running out of TEMP finishes, it creates a CMD script that loops on TASKLIST with a filter on PID until the main process exits, then deletes the copy in TEMP, then deletes itself (because of course CMD scripts can do that :-).

In this manner, when the tool has finished running, the CMD script is able to do that last bit of cleanup and leave the system completely bare.

However, there is a caveat to this. When the uninstaller is launched from “Programs and Features”, the process that “Programs and Features” launches and is waiting upon exits almost immediately, and the actual uninstallation is performed by a child process that it does not know about. This means that when the actual uninstallation completes, “Programs and Features” does not know to refresh its view – I was able to come up with a solution using Shell.Application to force it to refresh when the copy running out of TEMP completes.

It also means, though, that if you switch back to “Programs and Features” before the uninstallation has completed, you are presented with a message that says that it looks like the uninstallation didn’t work, and would you like to try again, do nothing or assume that it worked and delete the uninstall registration. In addition, there is no way to indicate to “Programs and Features” if the user cancels the uninstallation, and the dialog appears in that scenario as well. (Though I haven’t actually been able to figure out how to indicate a cancellation to it anyway at this point, I’m sure it must be possible – *if* the signal is coming from the process that “Programs and Features” launched!)

Now, while I was poking around looking for ways to self-delete the EXE file (which ultimately led to the low-tech CMD script solution described above), I came across this:

http://www.catch22.net/tuts/self-deleting-executables

In particular, I noticed that these fancy solutions, relying on “return” addresses on the call stack to call a series of API calls after the binary’s code has been unloaded, seemed to be able to release the EXE file the process was running out of *while* the process was running. Up to Windows 2000, calling UnmapViewOfFile on handle 4 would release the file, because file handle 4 was always the handle to the executable. On XP and up, the site claims that you can call FreeLibrary on yourself – *if* you’re a DLL. So now you have to be launched via RUNDLL32. Well, that’s not very convenient. :slight_smile:

I also stumbled across some techniques for launching processes out of memory buffers. The predominant technique seems to be to create a process off some arbitrary EXE, say CMD.EXE, with the CREATE_SUSPENDED flag. Then, before any code runs in the new process, you look up its PEB, unmap the file-backed section providing the pages of CMD.EXE to the process, and then analyze the EXE you *actually* want to load (whether it be a resource or an arbitrary memory buffer or what have you), allocate adequate memory for the image, use WriteProcessMemory to copy each section in and then VirtualProtectEx to set up the pages with the correct protection bits. When all is said and done, you can then resume that initial thread, and it’ll start running the code you copied in, instead of CMD.EXE. One thing not mentioned at all, though, I noticed, is that the process presumably still has an open file handle to CMD.EXE.

I found myself wondering if these two techniques could be combined to make an EXE that, upon launch, overwrites its own executable pages with the same data but not file-backed, and then *releases* the EXE file out from under itself. So, then you have a process running the code from the EXE, but the EXE itself is no longer implicated in the process and can be deleted.

My experimentation with this was in .NET, which I figured made a good candidate because the actual machine code would be JIT output, not file-backed. In theory, once the method doing the replacement of the EXE was JITted, it could unmap the file with the MSIL code, and then load that MSIL code back in from the file to regular old committed memory pages. As long as I suspend all other threads while I’m doing it and avoid allocating memory (which could trigger a GC), the .NET framework need never know that for a fraction of a second, the program code wasn’t present at all, right? :slight_smile: (I’ve also considered that it might be possible to change the pages to PAGE_WRITECOPY or PAGE_EXECUTE_WRITECOPY, as appropriate, and then trigger that copy-on-write by overwriting a byte with the value already there – then there wouldn’t even be a window where the code wasn’t present.)

In my experimentation, I did see that calling ZwUnmapViewOfSection would cause the PEB to disappear from RAM, and I could then VirtualAllocEx the same pages back in. But, even though there’s no mapped view of the section, the section *still exists*. The file is still open and locked.

How can I actually release the file? Is it possible? Or would it depend on knowing a handle that is unique on each run and stored only within kernel space? Does anyone know authoritatively? :slight_smile:

Many thanks!

Jonathan Gilbert

>distribution. There’s one catch: the tool wants to remove the very directory it is running out of!

Register it in PendingFileRenameOperations and reboot.

Also: the MSI install service (governed by MSIEXEC and the Control Panel/AddRemove Programs) I think also supports traceless uninstall scenarios.

BTW: going on with ZwCreateSection-level hacks just to uninstall your uninstaller is surely a waste of the development time. Just leaving the uninstaller “as is” is a better solution.

Modern days, not many software titles take care of leaving the computer clean. For instance, Adobe Flash Player used to depend on some files it created in TEMP directory on install.

So, if you delete the TEMP directory contents, you have audio lost in Youtube. Reinstall of the Flash Player is the solution.

–
Maxim S. Shatskih
Microsoft MVP on File System And Storage
xxxxx@storagecraft.com
http://www.storagecraft.com

SetCurrentDirectory to a different directory. Copy yourself to %temp% and run. Delete your directory. Delete your own running file.

@Alex, that is pretty much exactly what I am currently doing. I don’t bother with SetCurrentDirectory directly – I just set the working directory of the TEMP copy process when launching it. I delete the TEMP copy itself using a CMD script. But the problem is that Programs and Features doesn’t know when my uninstaller exits or why, so it is prone to asking inappropriate questions of the user.

@Maxim, well, switching to MSI isn’t an option at this stage. Our team inherited a bit of a monstrosity involving PowerShell scripts and custom-built installers using Inno Setup using very specific conventions, and everything ties into it, from the update system to the build process to all the current client deployments. I don’t want to force a reboot. I *could* register it in PendingFileRenameOperations, and then just assume that the user will *eventually* reboot, but I don’t really like doing that. It means the installer has to specifically check for pending operations and then either resolve them or refuse to install (otherwise the user could reinstall the app and then discover that parts of it go missing after a reboot). To be clear, the solution I have in place right now is “pretty good” – the bit that’s frustrating me is the integration with Programs and Features.

I suppose if the installation folder is on the same drive as the TEMP folder, I could rename the running EXE – I’m pretty sure “move” operations like that are permitted by the filesystem. But, if they aren’t on the same drive letter, then that approach is up the creek. Hmm…

> -----Original Message-----

[snip]

* When the app launches, if it detects that it is in the folder it wishes
to delete, it makes a copy of itself to the TEMP folder and launches that
copy.
* The initial instance then exits, ensuring the file is no longer being
held open (at least by itself).
* The copy running out of TEMP does the uninstallation.
* When the copy running out of TEMP finishes, it creates a CMD script that
loops on TASKLIST with a filter on PID until the main process exits, then
deletes the copy in TEMP, then deletes itself (because of course CMD
scripts can do that :-).

[snip]

Why make a copy in temp? If you just point your CMD script at the image in the install directory, you’ll get the same effect. And you’ll get the behavior you want.

Phil

Not speaking for LogRhythm
Phil Barila | Senior Software Engineer
720.881.5364 (w)
LogRhythm, Inc.?
A LEADER in Gartner’s SIEM Magic Quadrant four consecutive years (2012-2015)
Highest Score in Gartner’s 2015 SIEM Critical Capabilities Report
A CHAMPION in Info-Tech Research Group’s 2015 SIEM Vendor Landscape Report
SANS “Best of the Year” in SIEM, 2014
Perfect 5-Star Rating in SC Magazine (2009-2014)

???

Why not have your uninstall program mark itself and the desired directory as delete on close?

FILE_FLAG_DELETE_ON_CLOSE

https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx

far less complicated that all of this scripting, child processes etc.

Sent from Mailhttps: for Windows 10

From: xxxxx@iQmetrix.commailto:xxxxx
Sent: February 26, 2016 4:18 PM
To: Windows System Software Devs Interest Listmailto:xxxxx
Subject: [ntdev] Releasing executable file from running process

Hello :slight_smile:

I’ve written an uninstall tool that removes our software. The initial design for this software envisaged it being a downloadable tool that users (administrators) would typically run from their desktops. However, a recent development has led to the tool being included as a file within our software distribution. There’s one catch: the tool wants to remove the very directory it is running out of!

At this point, I’ve come to a partial solution in the following way:

* When the app launches, if it detects that it is in the folder it wishes to delete, it makes a copy of itself to the TEMP folder and launches that copy.
* The initial instance then exits, ensuring the file is no longer being held open (at least by itself).
* The copy running out of TEMP does the uninstallation.
* When the copy running out of TEMP finishes, it creates a CMD script that loops on TASKLIST with a filter on PID until the main process exits, then deletes the copy in TEMP, then deletes itself (because of course CMD scripts can do that :-).

In this manner, when the tool has finished running, the CMD script is able to do that last bit of cleanup and leave the system completely bare.

However, there is a caveat to this. When the uninstaller is launched from “Programs and Features”, the process that “Programs and Features” launches and is waiting upon exits almost immediately, and the actual uninstallation is performed by a child process that it does not know about. This means that when the actual uninstallation completes, “Programs and Features” does not know to refresh its view – I was able to come up with a solution using Shell.Application to force it to refresh when the copy running out of TEMP completes.

It also means, though, that if you switch back to “Programs and Features” before the uninstallation has completed, you are presented with a message that says that it looks like the uninstallation didn’t work, and would you like to try again, do nothing or assume that it worked and delete the uninstall registration. In addition, there is no way to indicate to “Programs and Features” if the user cancels the uninstallation, and the dialog appears in that scenario as well. (Though I haven’t actually been able to figure out how to indicate a cancellation to it anyway at this point, I’m sure it must be possible – if the signal is coming from the process that “Programs and Features” launched!)

Now, while I was poking around looking for ways to self-delete the EXE file (which ultimately led to the low-tech CMD script solution described above), I came across this:

http://www.catch22.net/tuts/self-deleting-executables

In particular, I noticed that these fancy solutions, relying on “return” addresses on the call stack to call a series of API calls after the binary’s code has been unloaded, seemed to be able to release the EXE file the process was running out of while the process was running. Up to Windows 2000, calling UnmapViewOfFile on handle 4 would release the file, because file handle 4 was always the handle to the executable. On XP and up, the site claims that you can call FreeLibrary on yourself – if you’re a DLL. So now you have to be launched via RUNDLL32. Well, that’s not very convenient. :slight_smile:

I also stumbled across some techniques for launching processes out of memory buffers. The predominant technique seems to be to create a process off some arbitrary EXE, say CMD.EXE, with the CREATE_SUSPENDED flag. Then, before any code runs in the new process, you look up its PEB, unmap the file-backed section providing the pages of CMD.EXE to the process, and then analyze the EXE you actually want to load (whether it be a resource or an arbitrary memory buffer or what have you), allocate adequate memory for the image, use WriteProcessMemory to copy each section in and then VirtualProtectEx to set up the pages with the correct protection bits. When all is said and done, you can then resume that initial thread, and it’ll start running the code you copied in, instead of CMD.EXE. One thing not mentioned at all, though, I noticed, is that the process presumably still has an open file handle to CMD.EXE.

I found myself wondering if these two techniques could be combined to make an EXE that, upon launch, overwrites its own executable pages with the same data but not file-backed, and then releases the EXE file out from under itself. So, then you have a process running the code from the EXE, but the EXE itself is no longer implicated in the process and can be deleted.

My experimentation with this was in .NET, which I figured made a good candidate because the actual machine code would be JIT output, not file-backed. In theory, once the method doing the replacement of the EXE was JITted, it could unmap the file with the MSIL code, and then load that MSIL code back in from the file to regular old committed memory pages. As long as I suspend all other threads while I’m doing it and avoid allocating memory (which could trigger a GC), the .NET framework need never know that for a fraction of a second, the program code wasn’t present at all, right? :slight_smile: (I’ve also considered that it might be possible to change the pages to PAGE_WRITECOPY or PAGE_EXECUTE_WRITECOPY, as appropriate, and then trigger that copy-on-write by overwriting a byte with the value already there – then there wouldn’t even be a window where the code wasn’t present.)

In my experimentation, I did see that calling ZwUnmapViewOfSection would cause the PEB to disappear from RAM, and I could then VirtualAllocEx the same pages back in. But, even though there’s no mapped view of the section, the section still exists. The file is still open and locked.

How can I actually release the file? Is it possible? Or would it depend on knowing a handle that is unique on each run and stored only within kernel space? Does anyone know authoritatively? :slight_smile:

Many thanks!

Jonathan Gilbert

—
NTDEV is sponsored by OSR

Visit the list online at: http:

MONTHLY seminars on crash dump analysis, WDF, Windows internals and software drivers!
Details at http:

To unsubscribe, visit the List Server section of OSR Online at http:</http:></http:></http:></mailto:xxxxx></mailto:xxxxx></https:>

@M M, I think I’ve even used that technique in the past, but I don’t think it works any more. The problem is (and this is well-documented) that FILE_FLAG_DELETE_ON_CLOSE respects file sharing rules. You can only specify it when opening a file if all other handles specify FILE_SHARE_DELETE (or there are no other handles). Once you have a handle open with FILE_FLAG_DELETE_ON_CLOSE, nobody else can open the file unless they specify FILE_SHARE_DELETE. It appears that CreateFile does *not* specify FILE_SHARE_DELETE (maybe it did pre-Vista? like I say, I could have sworn I’d seen the technique actually work in the past…), so if you open a handle to an EXE with FILE_FLAG_DELETE_ON_CLOSE and then try to launch the EXE, you just get an error indicating that it couldn’t open the file.

I’m also not certain whether it is possible to use FILE_FLAG_DELETE_ON_CLOSE with directories at all… Directories don’t follow all the same rules as files. For one thing, it is documented that the only way to open a handle to a directory is to specify FILE_FLAG_BACKUP_SEMANTICS. In any event, even if it can be done, it’s a non-starter when you can’t launch a process out of a file with FILE_FLAG_DELETE_ON_CLOSE. :stuck_out_tongue:

It’s too bad, because that really would be an awesome solution for making a self-deleting EXE. :slight_smile:

The problem I’m hoping to solve, though, is less about making a self-deleting EXE and more about making an EXE that can be deleted *while it is still running*.

@Phil, the reason I move it to TEMP is that the code was already written before this new requirement came into play, and it uses a variety of helper functions from isolated steps that have single responsibilities. Those responsibilities were not written with the expectation that it might actually be running out of one of the directories. It would significantly increase the complexity of the code to have it consider a directory removal a success if only its own file remained, and then delay the removal of the file and directory to app exit. It was far, far simpler to make a copy in TEMP and run that (about 20 lines of C# code, including error handling), and then subsequently delete it after the main message loop returns (another 20 lines of C# code, counting the lines of the CMD script in a string literal). Those functions are conceptually isolated from the functions that remove program files. Keeping those responsibilities separate makes the code far more maintainable. The only problem with the approach (as far as I’ve been able to detect) *is* that Programs and Features doesn’t know when the real uninstaller process actually exits.

I understand that you have complex code, but I was not suggesting that you mark the file for deletion before you try to execute it, but rather that the EXE delete itself by marking itself as a deleted file during runtime.

Based on your other response, you don?t want to change your core logic ? which IMHO is the source of all of your problems. It wasn?t designed to work this way and now I have to make it but I don?t want to change anything is rarely a recipe for success. I suggest you actually assess the complexity of what your installer does and then determine which is less work ? migrating it to a supported technology like MSI (WIX is a great toolset) or fixing you old code.

If you do decide that continuing with a big mess is better, then lookup deferred file movements in MSDN and require a reboot

Sent from Mailhttps: for Windows 10

From: xxxxx@iQmetrix.commailto:xxxxx
Sent: February 29, 2016 11:14 AM
To: Windows System Software Devs Interest Listmailto:xxxxx
Subject: RE:[ntdev] Releasing executable file from running process

@M M, I think I’ve even used that technique in the past, but I don’t think it works any more. The problem is (and this is well-documented) that FILE_FLAG_DELETE_ON_CLOSE respects file sharing rules. You can only specify it when opening a file if all other handles specify FILE_SHARE_DELETE (or there are no other handles). Once you have a handle open with FILE_FLAG_DELETE_ON_CLOSE, nobody else can open the file unless they specify FILE_SHARE_DELETE. It appears that CreateFile does not specify FILE_SHARE_DELETE (maybe it did pre-Vista? like I say, I could have sworn I’d seen the technique actually work in the past…), so if you open a handle to an EXE with FILE_FLAG_DELETE_ON_CLOSE and then try to launch the EXE, you just get an error indicating that it couldn’t open the file.

I’m also not certain whether it is possible to use FILE_FLAG_DELETE_ON_CLOSE with directories at all… Directories don’t follow all the same rules as files. For one thing, it is documented that the only way to open a handle to a directory is to specify FILE_FLAG_BACKUP_SEMANTICS. In any event, even if it can be done, it’s a non-starter when you can’t launch a process out of a file with FILE_FLAG_DELETE_ON_CLOSE. :stuck_out_tongue:

It’s too bad, because that really would be an awesome solution for making a self-deleting EXE. :slight_smile:

The problem I’m hoping to solve, though, is less about making a self-deleting EXE and more about making an EXE that can be deleted while it is still running.

@Phil, the reason I move it to TEMP is that the code was already written before this new requirement came into play, and it uses a variety of helper functions from isolated steps that have single responsibilities. Those responsibilities were not written with the expectation that it might actually be running out of one of the directories. It would significantly increase the complexity of the code to have it consider a directory removal a success if only its own file remained, and then delay the removal of the file and directory to app exit. It was far, far simpler to make a copy in TEMP and run that (about 20 lines of C# code, including error handling), and then subsequently delete it after the main message loop returns (another 20 lines of C# code, counting the lines of the CMD script in a string literal). Those functions are conceptually isolated from the functions that remove program files. Keeping those responsibilities separate makes the code far more maintainable. The only problem with the approach (as far as I’ve been able to detect) is that Programs and Features doesn’t know when the real uninstaller process actually exits.

—
NTDEV is sponsored by OSR

Visit the list online at: http:

MONTHLY seminars on crash dump analysis, WDF, Windows internals and software drivers!
Details at http:

To unsubscribe, visit the List Server section of OSR Online at http:</http:></http:></http:></mailto:xxxxx></mailto:xxxxx></https:>

No @M M, you misunderstand. :slight_smile: The code is not old, complex or poorly-written. The guiding principle here is not that old code is automatically bad code, nor is it even that complex code is automatically bad code. It is that good code is easily maintainable, and maintainable code segregates responsibilities.

The code in question was written recently, just before the idea of tying it into Programs and Features came under consideration. It is carefully modularized and the boundaries between modules are well-enforced.

What I am railing against – no, flat out refusing to do – is break those boundaries and split up a responsibility so that part of it is handled within the module handling a particular step and part of it is deferred to the app shutdown in its main entrypoint method.

Code is easiest to understand and maintain when everything pertaining to a particular task is in the same place. Right now, the code does a lot but it is not really complicated. If I add logic to separate out the handling of its own program file from all the adjacent program files, it will make it complex.

Do you think opening the executable with FILE_FLAG_DELETE_ON_CLOSE after it is running will actually work? I haven’t tried it, but I rather suspect it will fail. The reason that doing it in the other order fails is that CreateProcess opens the file without FILE_SHARE_DELETE. Well, the documentation for FILE_FLAG_DELETE_ON_CLOSE specifically states that it must satisfy file sharing rules at open time as well. The existing handle on the EXE doesn’t allow deletion – the Windows kernel is explicitly saying, “As long as I am using this file, it must not be deleted” – so the kernel can’t grant the process a handle with FILE_FLAG_DELETE_ON_CLOSE. If it did and the handle was closed before the process exited, then the file would be deleted while the kernel’s handle was open, violating the sharing rule (or the file wouldn’t be deleted, violating the contract of FILE_FLAG_DELETE_ON_CLOSE).

The solution I actually used in the codebase is this: I reimplemented the shadow file copy logic in VBScript, set up the installation process to deliver the .VBS file, and then registered the UninstallString as “WScript.exe UninstallHelper.vbs”. Turns out the Windows Scripting engine reads in the entire file and then detaches it when launching a script, so by the time it is running, the .VBS file can be deleted out from under it without affecting execution. Since the VBScript script is still running, it is able to wait for the TEMP copy of the tool to finish running (which is where the script file gets deleted, as part of the step in charge of program files), and then forward on its exit code to Programs and Features, which is also able to wait because it is waiting on WScript.exe and not one of our app’s program files.

I also figured out at least one way of telling Programs and Features that the user cancelled (and thus that it is okay that the uninstall registration is still present in the registry, no need to pop up a dialog asking me if the uninstall worked or not). I return the same exit code as Windows Installer uses, 1602, and it seems to do the trick. The source code to Inno Setup seems to indicate that perhaps simply 2 would work too. I don’t know whether Programs and Features specifically detects 2 and 1602, or whether perhaps *any* non-zero exit code suppresses the dialog, but 1602 definitely works. :slight_smile:

> The code in question was written recently, just before the idea of tying it into Programs and

Features came under consideration.

This was a recipe for disaster.

All Windows software packages should register there. This is not new, this is since XP for like 15 years.

Your code is good. You install scripts are abysmal.

–
Maxim S. Shatskih
Microsoft MVP on File System And Storage
xxxxx@storagecraft.com
http://www.storagecraft.com

Okay, so you like your design. That doesn?t invalidate my previous advise.

I will suggest that relying on the implementation detail that the scripting engine allows deletion of the script while it runs sounds like a great way to make things break in the field, but if it works for you great

For completeness MOVEFILE_DELAY_UNTIL_REBOOT

https://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx

Sent from Mailhttps: for Windows 10

From: xxxxx@iQmetrix.commailto:xxxxx
Sent: February 29, 2016 11:49 PM
To: Windows System Software Devs Interest Listmailto:xxxxx
Subject: RE:[ntdev] Releasing executable file from running process

No @M M, you misunderstand. :slight_smile: The code is not old, complex or poorly-written. The guiding principle here is not that old code is automatically bad code, nor is it even that complex code is automatically bad code. It is that good code is easily maintainable, and maintainable code segregates responsibilities.

The code in question was written recently, just before the idea of tying it into Programs and Features came under consideration. It is carefully modularized and the boundaries between modules are well-enforced.

What I am railing against – no, flat out refusing to do – is break those boundaries and split up a responsibility so that part of it is handled within the module handling a particular step and part of it is deferred to the app shutdown in its main entrypoint method.

Code is easiest to understand and maintain when everything pertaining to a particular task is in the same place. Right now, the code does a lot but it is not really complicated. If I add logic to separate out the handling of its own program file from all the adjacent program files, it will make it complex.

Do you think opening the executable with FILE_FLAG_DELETE_ON_CLOSE after it is running will actually work? I haven’t tried it, but I rather suspect it will fail. The reason that doing it in the other order fails is that CreateProcess opens the file without FILE_SHARE_DELETE. Well, the documentation for FILE_FLAG_DELETE_ON_CLOSE specifically states that it must satisfy file sharing rules at open time as well. The existing handle on the EXE doesn’t allow deletion – the Windows kernel is explicitly saying, “As long as I am using this file, it must not be deleted” – so the kernel can’t grant the process a handle with FILE_FLAG_DELETE_ON_CLOSE. If it did and the handle was closed before the process exited, then the file would be deleted while the kernel’s handle was open, violating the sharing rule (or the file wouldn’t be deleted, violating the contract of FILE_FLAG_DELETE_ON_CLOSE).

The solution I actually used in the codebase is this: I reimplemented the shadow file copy logic in VBScript, set up the installation process to deliver the .VBS file, and then registered the UninstallString as “WScript.exe UninstallHelper.vbs”. Turns out the Windows Scripting engine reads in the entire file and then detaches it when launching a script, so by the time it is running, the .VBS file can be deleted out from under it without affecting execution. Since the VBScript script is still running, it is able to wait for the TEMP copy of the tool to finish running (which is where the script file gets deleted, as part of the step in charge of program files), and then forward on its exit code to Programs and Features, which is also able to wait because it is waiting on WScript.exe and not one of our app’s program files.

I also figured out at least one way of telling Programs and Features that the user cancelled (and thus that it is okay that the uninstall registration is still present in the registry, no need to pop up a dialog asking me if the uninstall worked or not). I return the same exit code as Windows Installer uses, 1602, and it seems to do the trick. The source code to Inno Setup seems to indicate that perhaps simply 2 would work too. I don’t know whether Programs and Features specifically detects 2 and 1602, or whether perhaps any non-zero exit code suppresses the dialog, but 1602 definitely works. :slight_smile:

—
NTDEV is sponsored by OSR

Visit the list online at: http:

MONTHLY seminars on crash dump analysis, WDF, Windows internals and software drivers!
Details at http:

To unsubscribe, visit the List Server section of OSR Online at http:</http:></http:></http:></mailto:xxxxx></mailto:xxxxx></https:>