本文介绍了Microsoft Visual Studio安装程序项目-如何向使用Process.Start()但没有修复选项执行的MSI提供重启提示的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题如下:在MSI卸载之前,我已调用了我的自定义卸载程序.正确关闭我的应用程序后,它将调用msiexec以使用Windows Installer卸载MSI.

The problem is following: I have my custom uninstaller called before MSI uninstall. After shutting down my application properly it calls msiexec to use Windows Installer to uninstall MSI.

这是通过执行类似"msiexec/x {PRODUCT_CODE}/promptrestart"之类的方法来完成的.

It's done by executing something like "msiexec /x{PRODUCT_CODE} /promptrestart".

这很重要-如果在卸载后未重新启动系统,然后用户再次安装该应用程序,则其某些文件将在下次重新启动后被删除,因此不可接受.需要重新启动,但是,我需要迅速,自动和无条件的重新启动是邪恶的,永远都不要使用.

And here is important thing - if the system is not restarted after uninstallation, and then the user installs the app again, some of its files will be deleted after next restart, so it's not acceptable. The restart is required, however, I need prompt, automatic and unconditional restart is evil and should never ever be used.

因此,以上调用显示了STUPID卸载/修复"对话框.我不想要这个.当我使用"msiexec/x {PRODUCT_CODE}/qr/promptrestart"时,它会很好地卸载,但是之后它会拒绝重新启动.

So, the invocation above displays STUPID "uninstall / repair" dialog. I do not want it. When I use "msiexec /x{PRODUCT_CODE} /qr /promptrestart" - then it uninstalls nicely, however it refuses propt for restart afterwards.

我已阅读有关设置ARPNOREPAIR属性的信息.

I have read about setting ARPNOREPAIR property.

但是给出答案的白痴根本不在乎说在何处以及如何设置该属性.甚至...财产所属的地方,它是什么财产? MSI文件?

But the idiots who gave that answer wouldn't care to say WHERE and HOW that property could be set. Even... Where the property belongs, it's the property of what? MSI file?

然后,也许是实现此目标的另一种方式,例如从我的代码中调用提示以重新启动,但是...如何?直到那一刻,卸载程序应删除我的所有文件.卸载过程完成后,也许可以执行一种脚本吗?

Then, maybe is it another way to achieve this, like invoke the prompt for restart from my code, but... how? The uninstaller should remove all my files until that moment. Maybe it's possible to execute a kind of script after the uninstallation process is complete?

(另一个问题,Windows Installer不会删除由我的应用程序创建的所有文件和目录.如果我可以执行一些代码来更好地进行清理,那就太好了.)

(One more problem, the Windows Installer doesn't delete ALL files and directories created by my app. It would be nice if I could execute some code to clean up better.)

更新我看到了两条路:卸载结束后立即运行脚本(例如使用注册表或任务计划程序或IDK),使用Win32 API修改MSI文件,因为AFAIK可以用这种方式更改其属性.

UPDATEI see 2 paths ahead: make a script to be run once the uninstallation ends (like using Registry or Task Scheduler or IDK), use Win32 API to modify MSI file, because AFAIK it's possible to change its properties that way.

推荐答案

因此,有一个丑陋的骇客"解决了确切的问题:

So, there is an "ugly hack" which solves the exact problem:

首先-我们需要一个不受安装程序影响的可执行文件.这很容易,我们只需将已安装的exe复制到TEMP目录并从那里运行它即可.

First - we need an executable, that isn't affected by the installer. This one is easy, we just copy that one installed exe to a TEMP directory and run it from there.

下一步是该文件必须等待卸载阶段完成.有两种方法可以这样做.您可以观察安装程序的过程,也可以观察文件系统(如果删除了主程序文件).考虑到常规安装程序操作的速度,每秒轮询一次是足够好的选择.

The next step is to that file must wait unit the uninstall phase is done. There are a couple of ways of doing so. You can observe the installer process, you can observe the file system if main program file is deleted. Considering the pace of common installer operations, polling once a second is a good enough option.

下一步是可选的-删除由应用程序创建的剩余文件,空目录等.

The next step is optional - you remove remaining files created by application, empty directories and such.

下一步是重新启动自身,可以从PresentationFramework中的MessageBox.Show()询问用户,当用户回答OK或YES时,则可以以多种方式执行重新启动本身,我使用user32.dll中的ExitWindowsEx(),因为可能是msiexec内部调用的.

The next step is reboot itself, MessageBox.Show() from PresentationFramework is fine to ask user, when user answer OK or YES, then reboot itself can be performed in many ways, I use ExitWindowsEx() from user32.dll since it's probably what msiexec calls internally.

下面是示例代码:

if (MessageBox.Show(RestartPromptMsg, "", MessageBoxButton.OKCancel, MessageBoxImage.Exclamation) == MessageBoxResult.OK) {
    NativeMethods.ExitWindowsEx(
        NativeMethods.Flags.Reboot,
        NativeMethods.Reason.MajorApplication | NativeMethods.Reason.MinorInstallation | NativeMethods.Reason.FlagPlanned
    );
}

当然,某些参数可以传递给我们的特殊清理可执行文件,因此它可以做一些额外的事情,例如如果不是真正需要的话,跳过重新启动提示.

Of course some parameters could be passed to our special clean up executable, so it could do some extra things, like skip the restart prompt if it's not really required.

最后一步是删除可执行文件本身.很简单,但是很棘手.再次希望我的示例代码对您有所帮助:

The last step is to delete our executable itself. It's easy, but it's tricky. Again I hope my example code would help:

var cleanUpTempPath = Path.Combine(Path.GetTempPath(), CleanUpExe);
File.Copy(CleanUpPath, cleanUpTempPath, overwrite: true);
Process.Start(new ProcessStartInfo {
    FileName = "cmd",
    Arguments = $"/c (\"{cleanUpTempPath}\" -purge \"{InstallerDir}\") & (del \"{cleanUpTempPath}\")",
    UseShellExecute = false,
    CreateNoWindow = true
});

我们使用cmd.exe特殊功能,即&()的功能.退出前一个命令时,将执行用&分隔的命令.因此,当我们执行完的exe完成后,会被调用它的同一个cmd实例删除.请记住引用所有路径,它们可以包含空格.请记住在命令中用括号括起参数,因为否则&运算符将被视为上一个命令的参数,而不是cmd.exe.

We use cmd.exe special feature, the power of & and (). Commands separated with & gets executed when previous command exits. So when our clen up exe completes, it's gets deleted by the same cmd instance which called it. Remember to quote all paths, they can contain spaces. Remember to enclose a command with arguments in parentheses, because otherwise the & operator would be seen as a parameter to the previous command, not the cmd.exe.

我在大型生产应用程序中对其进行了测试,它具有魅力.这些代码示例仅在粘贴时不起作用,如果您要查找完整的代码,只需google即可,在pinvoke.net和StackOverflow上有很多可用的示例.

I tested it in my big production application and it works as charm. The code examples don't work when just pasted, if you're looking for complete code, just google for it, there are plenty of working examples on pinvoke.net and StackOverflow.

这篇关于Microsoft Visual Studio安装程序项目-如何向使用Process.Start()但没有修复选项执行的MSI提供重启提示的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-30 07:18