在通过 Wix 托管的自定义操作安装之前优雅地关闭应用程序

发布于 2024-10-16 09:46:40 字数 3348 浏览 1 评论 0原文

在我的 WiX 安装程序中,我想正常关闭即将更新的应用程序(如果它仍在运行)。我不想提示用户关闭,也不想终止该进程。在关闭应用程序之前,我需要有机会执行一些清理等操作。

该应用程序是在系统托盘中运行的 WinForms 应用程序。主窗体有一个标题,例如“mainwindow”,但它是隐藏的并且 ShowInTaskbar = false。

通过尝试使用一些不同的测试应用程序 Process.Kill() Process.CloseMainWindow() FindWindow、SendMessage、PostMessage 等,我发现对我来说最好的方法是使用 PostMessage

var hWnd = FindWindow(null, "mainwindowtitle");
PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);

这样我就可以重写 OnFormClosing 并执行我需要的任何清理。这在我组装的测试应用程序中运行良好。问题是它在 WiX 安装程序中运行时不起作用。我有 ac# 自定义操作 CA.dll 并且安装程序肯定会调用自定义操作 - 我可以从 msiexec 日志中看到这一点,如果我将自定义操作代码更改为仅 Process.Kill() 它确实如此正确停止应用程序。但是,当它使用 PostMessage 代码运行时,应用程序不会关闭,并且 OnFormClosing 永远不会被调用。

这是我的 CustomAction 代码,

        private const int WM_CLOSE = 0x0010;

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    [CustomAction]
    public static ActionResult CloseApplicationGracefully(Session session)
    {
        session.Log("Starting the CloseApplicationGracefully Custom Action - attempting to stop DUC.");

        var hWnd = FindWindow(null, "mainwindowtitle");

        session.Log("Window handle found: " + hWnd);

        bool result = PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);

        session.Log("Result of calling app to close: " + result);

        if (result)
        {
            return ActionResult.Success;
        }
        return ActionResult.Failure;
    }

这是 wx 设置代码,

<Binary Id="WixCustomAction.dll"
        SourceFile="$(var.WixCustomAction.TargetDir)$(var.WixCustomAction.TargetName).CA.dll" />
<CustomAction Id="WixCustomAction"
              BinaryKey="WixCustomAction.dll"
              DllEntry="CloseDeploymentUpdater" />
<InstallExecuteSequence>
  <Custom Action="WixCustomAction" After="FindRelatedProducts"></Custom>
</InstallExecuteSequence>

我尝试以不同的顺序调用此自定义操作,但没有运气...... 该代码在测试应用程序中工作,并且当我使用 Process.Kill 时自定义操作可以工作,但是当放入自定义操作时代码不起作用 - 必须是事件序列吗?

编辑

按照下面答案中的建议使用 WixCloseApplications CA 会产生以下日志条目

WixCloseApplications:  App: DUC.exe found running, 1 processes, attempting to send close message.
WixCloseApplications:  Sending close message to process id 0x1978
WixCloseApplications:  Result 0x12
WixCloseApplications:  Sending close message to process id 0x1978
WixCloseApplications:  Result 0x0
WixCloseApplications:  Sending close message to process id 0x1978
WixCloseApplications:  Result 0x578
WixCloseApplications:  Sending close message to process id 0x1978
WixCloseApplications:  Result 0x0
.
.
.
MSI (s) (C8!D4) [15:00:47:985]: PROPERTY CHANGE: Adding WixCloseApplicationsDeferred property. Its value is 'DUC.exe5'.
MSI (s) (C8!D4) [15:00:48:000]: Doing action: WixCloseApplicationsDeferred
.
.
Action 15:00:48: WixCloseApplicationsDeferred. 
Action start 15:00:48: WixCloseApplicationsDeferred.
.
.
Action ended 15:00:48: WixCloseApplicationsDeferred. Return value 1.
Action ended 15:00:48: WixCloseApplications. Return value 1.

In my WiX installer I want to gracefully close the application that is about to be updated if it is still running. I do not want to prompt the user to close and I do not want to kill the process. I need to have a chance to perform some clean-up etc. before closing the application.

The app is a WinForms app that runs in the system tray. The main form has a title, let's say "mainwindow" for example, but is hidden and has ShowInTaskbar = false.

By playing around with some different tester apps trying Process.Kill() Process.CloseMainWindow() FindWindow, SendMessage, PostMessage etc. I have found that the best way for me to do this is to use PostMessage

var hWnd = FindWindow(null, "mainwindowtitle");
PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);

That way I can override OnFormClosing and perform any cleanup I need. This works fine from a tester app I threw together. The problem is that it does not work when run in the WiX Installer. I have a c# Custom Action CA.dll and the installer definitely calls the custom action - I can see that from the msiexec logs and if I change the custom action code to just Process.Kill() it does stop the app correctly. However when it runs with the PostMessage code the application does not close and OnFormClosing never gets called.

Here is my CustomAction code

        private const int WM_CLOSE = 0x0010;

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    [CustomAction]
    public static ActionResult CloseApplicationGracefully(Session session)
    {
        session.Log("Starting the CloseApplicationGracefully Custom Action - attempting to stop DUC.");

        var hWnd = FindWindow(null, "mainwindowtitle");

        session.Log("Window handle found: " + hWnd);

        bool result = PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);

        session.Log("Result of calling app to close: " + result);

        if (result)
        {
            return ActionResult.Success;
        }
        return ActionResult.Failure;
    }

here is the wx setup code

<Binary Id="WixCustomAction.dll"
        SourceFile="$(var.WixCustomAction.TargetDir)$(var.WixCustomAction.TargetName).CA.dll" />
<CustomAction Id="WixCustomAction"
              BinaryKey="WixCustomAction.dll"
              DllEntry="CloseDeploymentUpdater" />
<InstallExecuteSequence>
  <Custom Action="WixCustomAction" After="FindRelatedProducts"></Custom>
</InstallExecuteSequence>

I have tried calling this custom action in different sequences but no luck...
The code works from a tester app and the custom action works when I use Process.Kill but the code does not work when put in the custom action - must be the sequence of events?

EDIT

Using the WixCloseApplications CA as suggested below in a answer results in the following log entries

WixCloseApplications:  App: DUC.exe found running, 1 processes, attempting to send close message.
WixCloseApplications:  Sending close message to process id 0x1978
WixCloseApplications:  Result 0x12
WixCloseApplications:  Sending close message to process id 0x1978
WixCloseApplications:  Result 0x0
WixCloseApplications:  Sending close message to process id 0x1978
WixCloseApplications:  Result 0x578
WixCloseApplications:  Sending close message to process id 0x1978
WixCloseApplications:  Result 0x0
.
.
.
MSI (s) (C8!D4) [15:00:47:985]: PROPERTY CHANGE: Adding WixCloseApplicationsDeferred property. Its value is 'DUC.exe5'.
MSI (s) (C8!D4) [15:00:48:000]: Doing action: WixCloseApplicationsDeferred
.
.
Action 15:00:48: WixCloseApplicationsDeferred. 
Action start 15:00:48: WixCloseApplicationsDeferred.
.
.
Action ended 15:00:48: WixCloseApplicationsDeferred. Return value 1.
Action ended 15:00:48: WixCloseApplications. Return value 1.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

赤濁 2024-10-23 09:46:40

如果正确理解 Wix 中的 CloseApp CustomAction,您应该枚举进程中的所有窗口。

http://wix.codeplex.com /SourceControl/changeset/view/175af30efe78#src%2fca%2fwixca%2fdll%2fCloseApps.cpp

所以你需要实现一个 EnumWindows(EnumCallBack, HANDLE processToClose)
在 EnumCallBack 中,您实际上为每个窗口发送了 WM_CLOSE 消息。

If understand the CloseApp CustomAction from Wix correctly you should enumerate over all windows in a process.

http://wix.codeplex.com/SourceControl/changeset/view/175af30efe78#src%2fca%2fwixca%2fdll%2fCloseApps.cpp

So you will need to implement an EnumWindows(EnumCallBack, HANDLE processToClose)
And in the EnumCallBack you actually PostMessage WM_CLOSE for each window.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文