[PATCH 3/5] msi: Implement UI messages for dialogs.

Zebediah Figura z.figura12 at gmail.com
Wed Jul 5 23:31:53 CDT 2017


Signed-off-by: Zebediah Figura <z.figura12 at gmail.com>
---
 dlls/msi/action.c        |  74 +++++++++++++--------------
 dlls/msi/custom.c        |   2 +-
 dlls/msi/dialog.c        |  55 +++++++++++++++++++-
 dlls/msi/msi.rc          |   3 +-
 dlls/msi/msipriv.h       |   1 +
 dlls/msi/package.c       |   7 +++
 dlls/msi/resource.h      |   1 +
 dlls/msi/tests/package.c | 130 +++++++++++++++++++++++++++++++++++++++++++++--
 8 files changed, 225 insertions(+), 48 deletions(-)

diff --git a/dlls/msi/action.c b/dlls/msi/action.c
index e016b43..fef076f 100644
--- a/dlls/msi/action.c
+++ b/dlls/msi/action.c
@@ -158,15 +158,17 @@ static const WCHAR szValidateProductID[] =
 static const WCHAR szWriteEnvironmentStrings[] =
     {'W','r','i','t','e','E','n','v','i','r','o','n','m','e','n','t','S','t','r','i','n','g','s',0};
 
-static void ui_actionstart(MSIPACKAGE *package, LPCWSTR action, LPCWSTR description, LPCWSTR template)
+static INT ui_actionstart(MSIPACKAGE *package, LPCWSTR action, LPCWSTR description, LPCWSTR template)
 {
     MSIRECORD *row = MSI_CreateRecord(3);
-    if (!row) return;
+    INT rc;
+    if (!row) return -1;
     MSI_RecordSetStringW(row, 1, action);
     MSI_RecordSetStringW(row, 2, description);
     MSI_RecordSetStringW(row, 3, template);
-    MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONSTART, row);
+    rc = MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONSTART, row);
     msiobj_release(&row->hdr);
+    return rc;
 }
 
 static void ui_actioninfo(MSIPACKAGE *package, LPCWSTR action, BOOL start, 
@@ -608,22 +610,26 @@ static UINT ACTION_ProcessUISequence(MSIPACKAGE *package)
 /********************************************************
  * ACTION helper functions and functions that perform the actions
  *******************************************************/
-static BOOL ACTION_HandleCustomAction( MSIPACKAGE *package, LPCWSTR action, UINT *rc, UINT script )
+static UINT ACTION_HandleCustomAction(MSIPACKAGE *package, LPCWSTR action, UINT script)
 {
-    BOOL ret=FALSE;
     UINT arc;
+    INT uirc;
 
-    ui_actionstart(package, action, NULL, NULL);
+    uirc = ui_actionstart(package, action, NULL, NULL);
+    if (uirc == IDCANCEL)
+        return ERROR_INSTALL_USEREXIT;
     ui_actioninfo(package, action, TRUE, 0);
     arc = ACTION_CustomAction( package, action, script );
+
+    if (arc == ERROR_FUNCTION_NOT_CALLED && needs_ui_sequence(package))
+        arc = ACTION_ShowDialog(package, action);
+
+    if (arc == ERROR_INSTALL_USEREXIT) /* dialog UI returned -1 */
+        return ERROR_SUCCESS;
+
     ui_actioninfo(package, action, FALSE, arc);
 
-    if (arc != ERROR_CALL_NOT_IMPLEMENTED)
-    {
-        *rc = arc;
-        ret = TRUE;
-    }
-    return ret;
+    return arc;
 }
 
 MSICOMPONENT *msi_get_loaded_component( MSIPACKAGE *package, const WCHAR *Component )
@@ -7771,9 +7777,9 @@ StandardActions[] =
     { 0 }
 };
 
-static BOOL ACTION_HandleStandardAction( MSIPACKAGE *package, LPCWSTR action, UINT *rc )
+static UINT ACTION_HandleStandardAction(MSIPACKAGE *package, LPCWSTR action)
 {
-    BOOL ret = FALSE;
+    UINT rc = ERROR_FUNCTION_NOT_CALLED;
     UINT i;
 
     i = 0;
@@ -7792,8 +7798,8 @@ static BOOL ACTION_HandleStandardAction( MSIPACKAGE *package, LPCWSTR action, UI
             if (StandardActions[i].handler)
             {
                 ui_actioninfo( package, action, TRUE, 0 );
-                *rc = StandardActions[i].handler( package );
-                ui_actioninfo( package, action, FALSE, *rc );
+                rc = StandardActions[i].handler( package );
+                ui_actioninfo( package, action, FALSE, rc );
 
                 if (StandardActions[i].action_rollback && !package->need_rollback)
                 {
@@ -7804,58 +7810,46 @@ static BOOL ACTION_HandleStandardAction( MSIPACKAGE *package, LPCWSTR action, UI
             else
             {
                 FIXME("unhandled standard action %s\n", debugstr_w(action));
-                *rc = ERROR_SUCCESS;
+                rc = ERROR_SUCCESS;
             }
-            ret = TRUE;
             break;
         }
         i++;
     }
-    return ret;
+    return rc;
 }
 
 UINT ACTION_PerformAction(MSIPACKAGE *package, const WCHAR *action, UINT script)
 {
-    UINT rc = ERROR_SUCCESS;
-    BOOL handled;
+    UINT rc;
 
     TRACE("Performing action (%s)\n", debugstr_w(action));
 
-    handled = ACTION_HandleStandardAction(package, action, &rc);
+    rc = ACTION_HandleStandardAction(package, action);
 
-    if (!handled)
-        handled = ACTION_HandleCustomAction(package, action, &rc, script);
+    if (rc == ERROR_FUNCTION_NOT_CALLED)
+        rc = ACTION_HandleCustomAction(package, action, script);
 
-    if (!handled)
-    {
+    if (rc == ERROR_FUNCTION_NOT_CALLED)
         WARN("unhandled msi action %s\n", debugstr_w(action));
-        rc = ERROR_FUNCTION_NOT_CALLED;
-    }
 
     return rc;
 }
 
 UINT ACTION_PerformUIAction(MSIPACKAGE *package, const WCHAR *action, UINT script)
 {
-    UINT rc = ERROR_SUCCESS;
-    BOOL handled = FALSE;
+    UINT rc;
 
     TRACE("Performing action (%s)\n", debugstr_w(action));
 
     package->action_progress_increment = 0;
-    handled = ACTION_HandleStandardAction(package, action, &rc);
-
-    if (!handled)
-        handled = ACTION_HandleCustomAction(package, action, &rc, script);
+    rc = ACTION_HandleStandardAction(package, action);
 
-    if( !handled && ACTION_DialogBox(package, action) == ERROR_SUCCESS )
-        handled = TRUE;
+    if (rc == ERROR_FUNCTION_NOT_CALLED)
+        rc = ACTION_HandleCustomAction(package, action, script);
 
-    if (!handled)
-    {
+    if (rc == ERROR_FUNCTION_NOT_CALLED)
         WARN("unhandled msi action %s\n", debugstr_w(action));
-        rc = ERROR_FUNCTION_NOT_CALLED;
-    }
 
     return rc;
 }
diff --git a/dlls/msi/custom.c b/dlls/msi/custom.c
index 35a1b5c..922927e 100644
--- a/dlls/msi/custom.c
+++ b/dlls/msi/custom.c
@@ -1215,7 +1215,7 @@ UINT ACTION_CustomAction( MSIPACKAGE *package, LPCWSTR action, UINT script )
 
     row = MSI_QueryGetRecord( package->db, query, action );
     if (!row)
-        return ERROR_CALL_NOT_IMPLEMENTED;
+        return ERROR_FUNCTION_NOT_CALLED;
 
     type = MSI_RecordGetInteger(row,2);
     source = MSI_RecordGetString(row,3);
diff --git a/dlls/msi/dialog.c b/dlls/msi/dialog.c
index fc9c24c..93a3bad 100644
--- a/dlls/msi/dialog.c
+++ b/dlls/msi/dialog.c
@@ -30,7 +30,6 @@
 #include "winuser.h"
 #include "winnls.h"
 #include "msi.h"
-#include "msipriv.h"
 #include "msidefs.h"
 #include "ocidl.h"
 #include "olectl.h"
@@ -38,12 +37,15 @@
 #include "commctrl.h"
 #include "winreg.h"
 #include "shlwapi.h"
-#include "msiserver.h"
 #include "shellapi.h"
 
 #include "wine/debug.h"
 #include "wine/unicode.h"
 
+#include "msipriv.h"
+#include "msiserver.h"
+#include "resource.h"
+
 WINE_DEFAULT_DEBUG_CHANNEL(msi);
 
 extern HINSTANCE msi_hInstance;
@@ -4508,6 +4510,44 @@ static UINT event_reset( msi_dialog *dialog, const WCHAR *argument )
     return ERROR_SUCCESS;
 }
 
+UINT ACTION_ShowDialog( MSIPACKAGE *package, const WCHAR *dialog )
+{
+    static const WCHAR szDialog[] = {'D','i','a','l','o','g',0};
+    MSIRECORD *row;
+    INT rc;
+
+    if (!TABLE_Exists(package->db, szDialog)) return ERROR_FUNCTION_NOT_CALLED;
+
+    row = MSI_CreateRecord(0);
+    if (!row) return ERROR_OUTOFMEMORY;
+    MSI_RecordSetStringW(row, 0, dialog);
+    rc = MSI_ProcessMessage(package, INSTALLMESSAGE_SHOWDIALOG, row);
+    msiobj_release(&row->hdr);
+    if (rc == -1) return ERROR_INSTALL_USEREXIT;
+
+    if (!rc)
+    {
+        static const WCHAR szActionNotFound[] =
+            {'D','E','B','U','G',':',' ','E','r','r','o','r',' ','[','1',']',':',' ',' ',
+             'A','c','t','i','o','n',' ','n','o','t',' ','f','o','u','n','d',':',' ','[','2',']',0};
+        WCHAR template[1024];
+        MSIRECORD *row = MSI_CreateRecord(2);
+        if (!row) return -1;
+        MSI_RecordSetStringW(row, 0, szActionNotFound); /* FIXME: this shouldn't attach "Info [1]." */
+        MSI_RecordSetInteger(row, 1, 2726);
+        MSI_RecordSetStringW(row, 2, dialog);
+        MSI_ProcessMessage(package, INSTALLMESSAGE_INFO, row);
+
+        LoadStringW(msi_hInstance, IDS_INSTALLERROR, template, 1024);
+        MSI_RecordSetStringW(row, 0, template);
+        MSI_ProcessMessage(package, INSTALLMESSAGE_INFO, row);
+
+        msiobj_release(&row->hdr);
+        return ERROR_FUNCTION_NOT_CALLED;
+    }
+    return ERROR_SUCCESS;
+}
+
 /* Return ERROR_SUCCESS if dialog is process and ERROR_FUNCTION_FAILED
  * if the given parameter is not a dialog box
  */
@@ -4536,6 +4576,17 @@ UINT ACTION_DialogBox( MSIPACKAGE *package, const WCHAR *dialog )
         msi_free( name );
     }
     if (r == ERROR_IO_PENDING) r = ERROR_SUCCESS;
+    if (r == ERROR_SUCCESS)
+    {
+        static const WCHAR szDialogCreated[] =
+            {'D','i','a','l','o','g',' ','c','r','e','a','t','e','d',0};
+        MSIRECORD *row = MSI_CreateRecord(2);
+        if (!row) return ERROR_OUTOFMEMORY;
+        MSI_RecordSetStringW(row, 1, dialog);
+        MSI_RecordSetStringW(row, 2, szDialogCreated);
+        MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONSTART, row);
+        msiobj_release(&row->hdr);
+    }
     return r;
 }
 
diff --git a/dlls/msi/msi.rc b/dlls/msi/msi.rc
index 5bd9956..2c86fae 100644
--- a/dlls/msi/msi.rc
+++ b/dlls/msi/msi.rc
@@ -72,12 +72,13 @@ STRINGTABLE
     IDS_COMMONDATA "Message type: [1], Argument: [2]{, [3]}"
 }
 
-/* INSTALLMESSAGE_INFO strings */
+/* Install message template strings */
 STRINGTABLE
 {
     IDS_INFO_ACTIONSTART "Action start %s: [1]."
     IDS_INFO_ACTIONENDED "Action ended %s: [1]. Return value [2]."
     IDS_INFO_LOGGINGSTART "=== Logging started: %s  %s ==="
+    IDS_INSTALLERROR "The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is [1]. {{The arguments are: [2], [3], [4]}}"
 }
 
 /* Standard action description strings */
diff --git a/dlls/msi/msipriv.h b/dlls/msi/msipriv.h
index cc42316..9271065 100644
--- a/dlls/msi/msipriv.h
+++ b/dlls/msi/msipriv.h
@@ -800,6 +800,7 @@ extern void msi_free_patchinfo( MSIPATCHINFO *patch ) DECLSPEC_HIDDEN;
 
 /* action internals */
 extern UINT MSI_InstallPackage( MSIPACKAGE *, LPCWSTR, LPCWSTR ) DECLSPEC_HIDDEN;
+extern UINT ACTION_ShowDialog( MSIPACKAGE*, LPCWSTR) DECLSPEC_HIDDEN;
 extern UINT ACTION_DialogBox( MSIPACKAGE*, LPCWSTR) DECLSPEC_HIDDEN;
 extern UINT ACTION_ForceReboot(MSIPACKAGE *package) DECLSPEC_HIDDEN;
 extern UINT MSI_Sequence( MSIPACKAGE *package, LPCWSTR szTable ) DECLSPEC_HIDDEN;
diff --git a/dlls/msi/package.c b/dlls/msi/package.c
index e762f62..bd11656 100644
--- a/dlls/msi/package.c
+++ b/dlls/msi/package.c
@@ -1788,6 +1788,13 @@ static INT internal_ui_handler(MSIPACKAGE *package, INSTALLMESSAGE eMessageType,
     case INSTALLMESSAGE_INITIALIZE:
     case INSTALLMESSAGE_TERMINATE:
         return 0;
+    case INSTALLMESSAGE_SHOWDIALOG:
+    {
+        LPWSTR dialog = msi_dup_record_field(record, 0);
+        UINT rc = ACTION_DialogBox(package, dialog);
+        msi_free(dialog);
+        return (rc == ERROR_SUCCESS);
+    }
     case INSTALLMESSAGE_ACTIONSTART:
     {
         LPWSTR deformatted;
diff --git a/dlls/msi/resource.h b/dlls/msi/resource.h
index 92fed33..6392c91 100644
--- a/dlls/msi/resource.h
+++ b/dlls/msi/resource.h
@@ -23,6 +23,7 @@
 #define IDS_INFO_ACTIONSTART 1050
 #define IDS_INFO_ACTIONENDED 1051
 #define IDS_INFO_LOGGINGSTART 1052
+#define IDS_INSTALLERROR 1053
 
 #define IDS_DESC_ALLOCATEREGISTRYSPACE 1100
 #define IDS_DESC_APPSEARCH 1101
diff --git a/dlls/msi/tests/package.c b/dlls/msi/tests/package.c
index 44d5513..f1e85c6 100644
--- a/dlls/msi/tests/package.c
+++ b/dlls/msi/tests/package.c
@@ -642,6 +642,42 @@ static UINT create_custom_action_table( MSIHANDLE hdb )
             "PRIMARY KEY `Action`)" );
 }
 
+static UINT create_dialog_table( MSIHANDLE hdb )
+{
+    return run_query(hdb,
+            "CREATE TABLE `Dialog` ("
+            "`Dialog` CHAR(72) NOT NULL, "
+            "`HCentering` SHORT NOT NULL, "
+            "`VCentering` SHORT NOT NULL, "
+            "`Width` SHORT NOT NULL, "
+            "`Height` SHORT NOT NULL, "
+            "`Attributes` LONG, "
+            "`Title` CHAR(128) LOCALIZABLE, "
+            "`Control_First` CHAR(50) NOT NULL, "
+            "`Control_Default` CHAR(50), "
+            "`Control_Cancel` CHAR(50) "
+            "PRIMARY KEY `Dialog`)");
+}
+
+static UINT create_control_table( MSIHANDLE hdb )
+{
+    return run_query(hdb,
+            "CREATE TABLE `Control` ("
+            "`Dialog_` CHAR(72) NOT NULL, "
+            "`Control` CHAR(50) NOT NULL, "
+            "`Type` CHAR(20) NOT NULL, "
+            "`X` SHORT NOT NULL, "
+            "`Y` SHORT NOT NULL, "
+            "`Width` SHORT NOT NULL, "
+            "`Height` SHORT NOT NULL, "
+            "`Attributes` LONG, "
+            "`Property` CHAR(50), "
+            "`Text` CHAR(0) LOCALIZABLE, "
+            "`Control_Next` CHAR(50), "
+            "`Help` CHAR(255) LOCALIZABLE "
+            "PRIMARY KEY `Dialog_`, `Control`)");
+}
+
 #define make_add_entry(type, qtext) \
     static UINT add##_##type##_##entry( MSIHANDLE hdb, const char *values ) \
     { \
@@ -9330,6 +9366,40 @@ static const struct externalui_message doaction_custom_sequence[] = {
     {0}
 };
 
+static const struct externalui_message doaction_custom_fullui_sequence[] = {
+    {INSTALLMESSAGE_ACTIONSTART, 3, {"", "custom", "", ""}, {0, 1, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "custom", ""}, {0, 1, 1}},
+    {INSTALLMESSAGE_SHOWDIALOG, 0, {"custom"}, {1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "custom", "1"}, {0, 1, 1}},
+    {0}
+};
+
+static const struct externalui_message doaction_dialog_nonexistent_sequence[] = {
+    {INSTALLMESSAGE_ACTIONSTART, 3, {"", "custom", "", ""}, {0, 1, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "custom", "1"}, {0, 1, 1}},
+    {INSTALLMESSAGE_SHOWDIALOG, 0, {"custom"}, {1}},
+    {INSTALLMESSAGE_INFO, 2, {"DEBUG: Error [1]:  Action not found: [2]", "2726", "custom"}, {1, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "2726", "custom"}, {0, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "custom", "0"}, {0, 1, 1}},
+    {0}
+};
+
+static const struct externalui_message doaction_dialog_sequence[] = {
+    {INSTALLMESSAGE_ACTIONSTART, 3, {"", "dialog", "", ""}, {0, 1, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "dialog", "0"}, {0, 1, 1}},
+    {INSTALLMESSAGE_SHOWDIALOG, 0, {"dialog"}, {1}},
+    {INSTALLMESSAGE_ACTIONSTART, 2, {"", "dialog", "Dialog created"}, {0, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "dialog", "1"}, {0, 1, 1}},
+    {0}
+};
+
+static const struct externalui_message doaction_dialog_error_sequence[] = {
+    {INSTALLMESSAGE_ACTIONSTART, 3, {"", "error", "", ""}, {0, 1, 1, 1}},
+    {INSTALLMESSAGE_INFO, 2, {"", "error", "1"}, {0, 1, 1}},
+    {INSTALLMESSAGE_SHOWDIALOG, 0, {"error"}, {1}},
+    {0}
+};
+
 static const struct externalui_message closehandle_sequence[] = {
     {INSTALLMESSAGE_TERMINATE, -1},
     {0}
@@ -9337,6 +9407,7 @@ static const struct externalui_message closehandle_sequence[] = {
 
 static INT CALLBACK externalui_message_string_callback(void *context, UINT message, LPCSTR string)
 {
+    INT retval = context ? *((INT *)context) : 0;
     struct externalui_message msg;
 
     msg.message = message;
@@ -9344,11 +9415,12 @@ static INT CALLBACK externalui_message_string_callback(void *context, UINT messa
     strcpy(msg.field[0], string);
     add_message(&msg);
 
-    return 1;
+    return retval;
 }
 
 static INT CALLBACK externalui_message_callback(void *context, UINT message, MSIHANDLE hrecord)
 {
+    INT retval = context ? *((INT *)context) : 0;
     struct externalui_message msg;
     char buffer[100];
     DWORD length = 100;
@@ -9372,7 +9444,7 @@ static INT CALLBACK externalui_message_callback(void *context, UINT message, MSI
 
     add_message(&msg);
 
-    return 1;
+    return retval;
 }
 
 static void test_externalui_message(void)
@@ -9381,11 +9453,14 @@ static void test_externalui_message(void)
 
     INSTALLUI_HANDLER_RECORD prev;
     MSIHANDLE hdb, hpkg;
+    INT retval = 1;
     UINT r;
 
+    MsiSetInternalUI(INSTALLUILEVEL_FULL, NULL);
+
     /* processing SHOWDIALOG with a record handler causes a crash on XP */
-    MsiSetExternalUIA(externalui_message_string_callback, INSTALLLOGMODE_SHOWDIALOG, NULL);
-    r = pMsiSetExternalUIRecord(externalui_message_callback, 0xffffffff ^ INSTALLLOGMODE_PROGRESS ^ INSTALLLOGMODE_SHOWDIALOG, NULL, &prev);
+    MsiSetExternalUIA(externalui_message_string_callback, INSTALLLOGMODE_SHOWDIALOG, &retval);
+    r = pMsiSetExternalUIRecord(externalui_message_callback, 0xffffffff ^ INSTALLLOGMODE_PROGRESS ^ INSTALLLOGMODE_SHOWDIALOG, &retval, &prev);
 
     flush_sequence();
 
@@ -9430,6 +9505,53 @@ static void test_externalui_message(void)
     MsiCloseHandle(hpkg);
     ok_sequence(closehandle_sequence, "MsiCloseHandle()", FALSE);
 
+    /* Test dialogs */
+    hdb = create_package_db();
+    ok(hdb, "failed to create database\n");
+
+    r = MsiDatabaseImportA(hdb, CURR_DIR, "forcecodepage.idt");
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d", r);
+
+    r = create_dialog_table(hdb);
+    ok(r == ERROR_SUCCESS, "failed to create dialog table %u\n", r);
+    r = run_query(hdb, "INSERT INTO `Dialog` (`Dialog`, `HCentering`, "
+                  "`VCentering`, `Width`, `Height`, `Control_First`) "
+                  "VALUES ('dialog', 5, 5, 100, 100, 'dummy')");
+    ok(r == ERROR_SUCCESS, "failed to insert into dialog table %u\n", r);
+
+    r = create_control_table(hdb);
+    ok(r == ERROR_SUCCESS, "failed to create control table %u\n", r);
+    r = run_query(hdb, "INSERT INTO `Control` (`Dialog_`, `Control`, "
+                  "`Type`, `X`, `Y`, `Width`, `Height`) "
+                  "VALUES('dialog', 'dummy', 'Text', 5, 5, 5, 5)");
+    ok(r == ERROR_SUCCESS, "failed to insert into control table %u", r);
+
+    r = package_from_db(hdb, &hpkg);
+    ok(r == ERROR_SUCCESS, "failed to create package %u\n", r);
+    ok_sequence(openpackage_sequence, "MsiOpenPackage with valid db", TRUE);
+
+    /* Test a custom action */
+    r = MsiDoActionA(hpkg, "custom");
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
+    ok_sequence(doaction_custom_fullui_sequence, "MsiDoAction(\"custom\")", TRUE);
+
+    retval = 0;
+    r = MsiDoActionA(hpkg, "custom");
+    ok(r == ERROR_FUNCTION_NOT_CALLED, "Expected ERROR_FUNCTION_NOT_CALLED, got %d\n", r);
+    ok_sequence(doaction_dialog_nonexistent_sequence, "MsiDoAction(\"custom\")", TRUE);
+
+    r = MsiDoActionA(hpkg, "dialog");
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
+    ok_sequence(doaction_dialog_sequence, "MsiDoAction(\"dialog\")", TRUE);
+
+    retval = -1;
+    r = MsiDoActionA(hpkg, "error");
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r);
+    ok_sequence(doaction_dialog_error_sequence, "MsiDoAction(\"error\")", FALSE);
+
+    MsiCloseHandle(hpkg);
+    ok_sequence(closehandle_sequence, "MsiCloseHandle()", TRUE);
+
     CoUninitialize();
     DeleteFileA(msifile);
     DeleteFileA("forcecodepage.idt");
-- 
2.7.4




More information about the wine-patches mailing list