[wix-users] Fwd: WiX Error Handling

Rob Mensching rob at firegiant.com
Wed Jan 16 09:41:05 PST 2019


I’m pretty sure you can schedule a CA as the error exit.

Ideally, you should use ::MsiProcessMessage() (aka: session.Message()) since it does all the work to respect quiet modes the Windows Installer is running.


From: Todd Hoatson <todd.hoatson at gmail.com>
Sent: Wednesday, January 16, 2019 9:38 AM
To: Rob Mensching <rob at firegiant.com>; WiX Toolset Users Mailing List <wix-users at lists.wixtoolset.org>
Subject: Re: [wix-users] Fwd: WiX Error Handling

OK, new idea:

Scrap the FatalErrorDlg…  Instead, write an error message method in C#.  This method would obtain the message to be displayed from a property.  As a C# method, it could be called via custom action during either the InstallUISequence or InstallExecuteSequence, but it could also be called directly from other C# custom actions.  The only thing I'm unsure of is the proper way to set up the custom action for calling this method...

Clearly I would need something like:

<CustomAction Id="ReportError"
  Return="check"
  Execute="immediate"
  BinaryKey="CustomActions.CA.dll"
  DllEntry="ReportErrMsg"  />

Then, I would change this:

<InstallUISequence>
    <Show Dialog="FatalErrorDlg" OnExit="error" />
    …
to this(?):

<InstallUISequence>
    <Custom Action="ReportError" OnExit="error"></Custom>
    …
Could this approach work...?  Or would I need to set it up differently?

thanks,
Todd Hoatson

On Tue, Jan 15, 2019 at 4:49 PM Rob Mensching <rob at firegiant.com<mailto:rob at firegiant.com>> wrote:
7. Sorry, I should have been clearer. Properties don’t travel from server side to client side in all installations, not just per-user installations. I’ve never read Nick’s book.

9. I don’t know where Microsoft takes feedback these days. Others might have a better idea.

10. I don’t really know how to answer this: “It was never my desire / intention to learn anything at all about MSI”. That’s like saying, “It was never my desire to learn anything at all about the .NET Framework - I just wanted to learn how to make fairly-straightforward programs with C#...”


PS: CustomActions are one of the hardest things to do correctly with the Windows Installer. Only patching is harder, IMHO.

_____________________________________________________________
Short replies here. Complete answers over there: http://www.firegiant.com/

From: Todd Hoatson <todd.hoatson at gmail.com<mailto:todd.hoatson at gmail.com>>
Sent: Tuesday, January 15, 2019 1:53 PM
To: Rob Mensching <rob at firegiant.com<mailto:rob at firegiant.com>>
Cc: WiX Toolset Users Mailing List <wix-users at lists.wixtoolset.org<mailto:wix-users at lists.wixtoolset.org>>
Subject: Re: [wix-users] Fwd: WiX Error Handling

Hi Rob,
  in response...

> 7.  Not true (based on my understanding).

Could you please elaborate?  Do you feel Nick Ramirez' book is in error about this?  Or has something changed since then?

> Seems like a totally reasonable feature request. You could go ask the Windows Installer team to implement server
> side to client side property passing.

Where might one make such a request...?  Please understand, I'm fairly new to installation, as this is only the 2nd installer I've worked on.  It was never my desire / intention to learn anything at all about MSI - I just wanted to learn how to make fairly-straightforward installers with WiX...

thanks,
Todd



On Tue, Jan 15, 2019 at 12:36 AM Rob Mensching <rob at firegiant.com<mailto:rob at firegiant.com>> wrote:
1.  Yep, client side properties can be changed on the client side.
2.  Yep, server side properties can be changed on the server side.
3.  Yep, fatal error dialog lives on the client side.
4.  Yep, fatal error dialog lives on the client side.
5.  Yep. ::MsiProcessMessage() (what session.Message calls), can pump messages from the server side to the client side to be displayed in a message box.
6.  Normally the fatal error dialog doesn’t include specifics for an error message (much due to the Windows Installer design)
7.  Not true (based on my understanding).
8.  The Windows Installer team decided that sending properties “back” from the server side to the client side was not important (but you can send secure properties from the client side to the server side, thank goodness). Seems like a totally reasonable feature request. You could go ask the Windows Installer team to implement server side to client side property passing.

_____________________________________________________________
Short replies here. Complete answers over there: http://www.firegiant.com/

From: Todd Hoatson <todd.hoatson at gmail.com<mailto:todd.hoatson at gmail.com>>
Sent: Monday, January 14, 2019 8:10 PM
To: Rob Mensching <rob at firegiant.com<mailto:rob at firegiant.com>>
Subject: Re: [wix-users] Fwd: WiX Error Handling

>  I think you've confirmed Properties (even secure ones) don't come back from server.

Not exactly...  and I apologize in advance for a very long response here...  (if you like, you can skip the details and go straight to my summary observations below)

It seems I have confirmed that properties don't come back from the server *when installing PER_USER*.

And even this doesn't seem to fully explain my results...

I have other properties which are set in C# custom actions, and those seem to be picked up by the rest of the installer.

Example 1 - Font installation:
                <Property Id="SBLHEB_INSTALLED" Value='False'/>
                <Property Id="APPARATUS_INSTALLED" Value='False'/>

                <CustomAction Id="SetFontValues"
                                Return="check"
                                Execute="immediate"
                                BinaryKey="CustomActions.CA.dll"
                                DllEntry="LookForInstalledFonts"  />
                ...
                <InstallExecuteSequence>
                  <Custom Action="CloseApplications" Before="AppSearch"></Custom>
                  <Custom Action="SetFontValues" After="AppSearch"></Custom>
                  ...
                </InstallUISequence>
                ...
                <DirectoryRef Id='FontsFolder'>
                  <Component Id='Font1' Guid='*' Permanent='yes'>
                                <Condition>SBLHEB_INSTALLED="False"</Condition>
                                <File Id='HebrewFont' Source='resources\SBL_Hbrw.ttf' TrueType='yes'/>
                  </Component>
                  <Component Id='Font2' Guid='*' Permanent='yes'>
                                <Condition>APPARATUS_INSTALLED="False"</Condition>
                                <File Id='ApparatusFont' Source='resources\AppSILR.ttf' TrueType='yes'/>
                  </Component>
                  <Component Id='Font3' Guid='*' Permanent='yes'>
                                <Condition>APPARATUS_INSTALLED="False"</Condition>
                                <File Id='ApparatusBoldFont' Source='resources\AppSILB.TTF' TrueType='yes'/>
                  </Component>
                  <Component Id='Font4' Guid='*' Permanent='yes'>
                                <Condition>APPARATUS_INSTALLED="False"</Condition>
                                <File Id='ApparatusItalicFont' Source='resources\AppSILI.TTF' TrueType='yes'/>
                  </Component>
                  <Component Id='Font5' Guid='*' Permanent='yes'>
                                <Condition>APPARATUS_INSTALLED="False"</Condition>
                                <File Id='ApparatusItalicBoldFont' Source='resources\AppSILBI.TTF' TrueType='yes'/>
                  </Component>
                  ...
                </DirectoryRef>

                <Feature Id='Fonts' Title='MyApp 9.0 - Fonts' Description='Installing fonts for MyApp 9' Level='1' >
                  <ComponentRef Id='Font1'/>
                  <ComponentRef Id='Font2'/>
                  <ComponentRef Id='Font3'/>
                  <ComponentRef Id='Font4'/>
                  <ComponentRef Id='Font5'/>
                  ...
                </Feature>

And here is the C# code for the custom action:

        [CustomAction]
        public static ActionResult LookForInstalledFonts(Session session)
        {
            session.Log("--->CustomActions.LookForInstalledFonts<---");

            //Check for each font that we want to install
            session["SBLHEB_INSTALLED"] = DoesFontExist(session, "SBL Hebrew", FontStyle.Regular).ToString();
            if (session["SBLHEB_INSTALLED"].Equals("False"))
                File.Delete(@"C:\\Windows\\Fonts\\SBL_Hbrw.ttf");

            session["APPARATUS_INSTALLED"] = DoesFontExist(session, "Apparatus SIL", FontStyle.Regular).ToString();
            if (session["APPARATUS_INSTALLED"].Equals("False"))
            {
                session.Log("--->RemoveApparatus");
                File.Delete(@"C:\\Windows\\Fonts\\AppSILB.TTF");
                File.Delete(@"C:\\Windows\\Fonts\\AppSILBI.TTF");
                File.Delete(@"C:\\Windows\\Fonts\\AppSILI.TTF");
                File.Delete(@"C:\\Windows\\Fonts\\AppSILR.TTF");
            }

                    ...

            return ActionResult.Success;
        }

As you can see from the code above, the properties SBLHEB_INSTALLED and APPARATUS_INSTALLED are updated in the C# code.  The updated values are referenced in the WiX code.

When I run the installer, I see this in the log file:

...
Return Value for SBL Hebrew : Regular is True
MSI (s) (3C!E4) [14:32:09:690]: PROPERTY CHANGE: Modifying SBLHEB_INSTALLED property. Its current value is 'False'. Its new value: 'True'.
Return Value for Apparatus SIL : Regular is True
MSI (s) (3C!E4) [14:32:09:690]: PROPERTY CHANGE: Modifying APPARATUS_INSTALLED property. Its current value is 'False'. Its new value: 'True'.
...
MSI (s) (3C:08) [14:32:09:721]: Component: Font1; Installed: Absent;   Request: Local;   Action: Null
MSI (s) (3C:08) [14:32:09:721]: Component: Font2; Installed: Absent;   Request: Local;   Action: Null
MSI (s) (3C:08) [14:32:09:721]: Component: Font3; Installed: Absent;   Request: Local;   Action: Null
MSI (s) (3C:08) [14:32:09:721]: Component: Font4; Installed: Absent;   Request: Local;   Action: Null
MSI (s) (3C:08) [14:32:09:721]: Component: Font5; Installed: Absent;   Request: Local;   Action: Null
...
Property(S): SBLHEB_INSTALLED = True
Property(S): CHARIS_INSTALLED = True
Property(S): APPARATUS_INSTALLED = True
...
Property(C): SBLHEB_INSTALLED = False
Property(C): CHARIS_INSTALLED = False
Property(C): APPARATUS_INSTALLED = False

So I see that the fonts (for some reason) are installed on the server side, and for that reason they are able to receive the updated value indicating that the font has already been installed, and therefore the action is 'Null'.


Example 2 - Project folder identification (use default folder location unless there is a registry entry indicating a location previously used by a prior version of the app):
                <CustomAction Id='SetDefProjFolder' Property='DEFPROJFOLDER' Value='[WindowsVolume]MyApp 9 Projects' />
                <Property Id="PROJFOLDERFOUND" Value="unset" />
                <CustomAction Id="VerifyProjectPath"
                                Return="check"
                                Execute="immediate"
                                BinaryKey="CustomActions.CA.dll"
                                DllEntry="VerifyProjectPath"  />

                <CustomAction Id='UseDefProjFolder' Property='PROJFOLDER' Value='[DEFPROJFOLDER]' />
                <CustomAction Id='UseRegProjFolder' Property='PROJFOLDER' Value='[REGPROJFOLDER]' />
                ...
                <InstallUISequence>
                    <Show Dialog="FatalErrorDlg" OnExit="error" />
                    <Custom Action="SetDefProjFolder" After="FindRelatedProducts"></Custom>
                    <Custom Action="VerifyProjectPath" After="SetDefProjFolder"></Custom>
                </InstallUISequence>
                ...
                <Property Id='WIXUI_PROJECTSDIR' Value='PROJFOLDER'/>

And here is the C# code for the custom action:

        [CustomAction]
        public static ActionResult VerifyProjectPath(Session session)
        {
            session.Log("Begin VerifyProjectPath in custom action dll");
            string regProjPath = GetProjectDirFromRegistry();
            if (string.IsNullOrEmpty(regProjPath))
            {
                session["REGPROJFOLDER"] = null;
                session["PROJFOLDERFOUND"] = "NotFound";
                return ActionResult.Success;
            }

            session["REGPROJFOLDER"] = regProjPath;

            if (Directory.Exists(regProjPath) && Directory.GetFiles(regProjPath).Length > 0)
                session["PROJFOLDERFOUND"] = "AlreadyExisting";
            else
            {
                session["PROJFOLDERFOUND"] = "InvalidRegEntry";
            }
            return ActionResult.Success;
        }

As you can see from the code above, the properties REGPROJFOLDER and PROJFOLDERFOUND are updated in the C# code.  The updated values are referenced in the WiX code.

When I run the installer, I see this in the log file:

...
MSI (c) (20:7C) [14:32:02:210]: Doing action: SetDefProjFolder
Action 14:32:02: SetDefProjFolder.
Action start 14:32:02: SetDefProjFolder.
MSI (c) (20:7C) [14:32:02:210]: PROPERTY CHANGE: Adding DEFPROJFOLDER property. Its value is 'C:\MyApp 9 Projects'.
Action ended 14:32:02: SetDefProjFolder. Return value 1.
MSI (c) (20:7C) [14:32:02:210]: Doing action: VerifyProjectPath
Action 14:32:02: VerifyProjectPath.
Action start 14:32:02: VerifyProjectPath.
MSI (c) (20:44) [14:32:02:226]: Invoking remote custom action. DLL: C:\Users\<Me>\AppData\Local\Temp\MSIC8CA.tmp, Entrypoint: VerifyProjectPath
MSI (c) (20:40) [14:32:02:226]: Cloaking enabled.
MSI (c) (20:40) [14:32:02:226]: Attempting to enable all disabled privileges before calling Install on Server
MSI (c) (20:40) [14:32:02:226]: Connected to service for CA interface.
SFXCA: Extracting custom action to temporary directory: C:\Users\<Me>\AppData\Local\Temp\MSIC8CA.tmp-\
SFXCA: Binding to CLR version v4.0.30319
Calling custom action CustomActions!CustomActions.CustomActions.VerifyProjectPath
Begin VerifyProjectPath in custom action dll
MSI (c) (20!A0) [14:32:02:426]: PROPERTY CHANGE: Adding REGPROJFOLDER property. Its value is 'C:\MyApp 8 Projects\'.
MSI (c) (20!A0) [14:32:02:426]: PROPERTY CHANGE: Modifying PROJFOLDERFOUND property. Its current value is 'unset'. Its new value: 'AlreadyExisting'.
Action ended 14:32:02: VerifyProjectPath. Return value 1.
...
MSI (c) (20:7C) [14:32:02:448]: PROPERTY CHANGE: Adding PROJFOLDER property. Its value is 'C:\'.
...
MSI (c) (20:7C) [14:32:02:464]: Dir (target): Key: PROJFOLDER      , Object: C:\
...
MSI (c) (20:7C) [14:32:08:919]: Switching to server: APPFOLDER="C:\Program Files (x86)\MyApp 9\" PROJFOLDER="C:\" DEFPROJFOLDER="C:\ MyApp 9 Projects" EXPLANATIONTEXT="Since this is an upgrade of an existing installation, you can't change the location of the project folder." TARGETDIR="C:\" PROJFOLDERFOUND="AlreadyExisting" MSIFASTINSTALL="7" REBOOT="ReallySuppress" CURRENTDIRECTORY="C:\Projects\MyApp9\BuildDir" CLIENTUILEVEL="0" MSICLIENTUSESEXTERNALUI="1" CLIENTPROCESSID="13088" USERNAME="<me>" SOURCEDIR="C:\ProgramData\Package Cache\{24F072F4-5904-4200-87CA-BB068493FF79}v9.0.100.1\" ACTION="INSTALL" EXECUTEACTION="INSTALL" REGPROJFOLDER="C:\MyApp 8 Projects\" ROOTDRIVE="C:\" INSTALLLEVEL="1" SECONDSEQUENCE="1" WIXUI_INSTALLDIR_VALID="1"  ADDLOCAL=Application,Projects,Fonts
...
MSI (s) (3C:08) [14:32:09:035]: Command Line: APPFOLDER=C:\Program Files (x86)\MyApp 9\ PROJFOLDER=C:\ DEFPROJFOLDER=C:\ MyApp 9 Projects EXPLANATIONTEXT=Since this is an upgrade of an existing installation, you can't change the location of the project folder. TARGETDIR=C:\ PROJFOLDERFOUND=AlreadyExisting MSIFASTINSTALL=7 REBOOT=ReallySuppress CURRENTDIRECTORY=C:\Projects\MyApp9\BuildDir CLIENTUILEVEL=0 MSICLIENTUSESEXTERNALUI=1 CLIENTPROCESSID=13088 USERNAME=<me> SOURCEDIR=C:\ProgramData\Package Cache\{24F072F4-5904-4200-87CA-BB068493FF79}v9.0.100.1\ ACTION=INSTALL EXECUTEACTION=INSTALL REGPROJFOLDER=C:\MyApp 8 Projects\ ROOTDRIVE=C:\ INSTALLLEVEL=1 SECONDSEQUENCE=1 WIXUI_INSTALLDIR_VALID=1 ADDLOCAL=Application,Projects,Fonts ACTION=INSTALL
...
MSI (s) (3C:08) [14:32:09:035]: PROPERTY CHANGE: Adding PROJFOLDER property. Its value is 'C:\'.
MSI (s) (3C:08) [14:32:09:035]: PROPERTY CHANGE: Adding DEFPROJFOLDER property. Its value is 'C:\MyApp 9 Projects'.
MSI (s) (3C:08) [14:32:09:035]: PROPERTY CHANGE: Adding EXPLANATIONTEXT property. Its value is 'Since this is an upgrade of an existing installation, you can't change the location of the project folder.'.
...
MSI (s) (3C:08) [14:32:09:035]: PROPERTY CHANGE: Modifying PROJFOLDERFOUND property. Its current value is 'unset'. Its new value: 'AlreadyExisting'.
...
MSI (s) (3C:08) [14:32:09:035]: PROPERTY CHANGE: Adding REGPROJFOLDER property. Its value is 'C:\MyApp 8 Projects\'.
...
MSI (s) (3C:08) [14:32:09:706]: Dir (target): Key: PROJFOLDER      , Object: C:\
...
Property(S): PROJFOLDER = C:\
Property(S): WIXUI_PROJECTSDIR = PROJFOLDER
Property(S): DEFPROJFOLDER = C:\MyApp 9 Projects
Property(S): EXPLANATIONTEXT = Since this is an upgrade of an existing installation, you can't change the location of the project folder.
Property(S): PROJFOLDERFOUND = AlreadyExisting
Property(S): REGPROJFOLDER = C:\MyApp 8 Projects\
...
Property(C): PROJFOLDER = C:\
Property(C): WIXUI_PROJECTSDIR = PROJFOLDER
Property(C): DEFPROJFOLDER = C:\MyApp 9 Projects
Property(C): EXPLANATIONTEXT = Since this is an upgrade of an existing installation, you can't change the location of the project folder.
Property(C): PROJFOLDERFOUND = AlreadyExisting
Property(C): REGPROJFOLDER = C:\MyApp 8 Projects\

So I see from this that VerifyProjectPath is executed (for some reason) on the client side, and for that reason it is able to pass the updated values indicating the appropriate project folder location.

Observations:
1.  Custom code that is executed as part of InstallUISequence can set properties and have the updated values be visible to WiX code that is also executed (implicitly or explicitly) as part of InstallUISequence.
2.  Custom code that is executed as part of InstallExecuteSequence can set properties and have the updated values be visible to installation actions.
3.  It is possible to create a 'fatal error' dialog which is shown within the InstallUISequence (but it can only access values of properties that were also set within the InstallUISequence???)
4.  It is NOT possible to create a 'fatal error' dialog which is shown within the InstallExecuteSequence, which means it is not possible to use such a dialog to report errors which occur in the InstallExecuteSequence.
5.  As a result of #4, errors which occur in custom code that is executed as part of InstallExecuteSequence must be reported by a call to session.Message().
6.  However, the installer will still want to deal with the installation failure, and show the fatal error dialog (with the non-updated, i.e. default, value), which unfortunately produces 2 error dialogs for the same error, the second one having wrong information.
7.  This inability for custom actions in one sequence to communicate via properties to other parts of the installation in the other sequence only exists for PER_USER installations.
8.  This rather odd situation is, inexplicably, 'by design'.

Can someone please help me understand why preventing free communication via properties among parts of the installation is somehow more secure and / or why PER_USER installations are inherently less secure and needing such restrictions?

More importantly, can anyone suggest a way for our installer to avoid the problem in #6 (above)?

thanks,
Todd Hoatson


--
Todd Hoatson
Mobile: 763-291-3312
Email:   todd.hoatson at gmail.com<mailto:todd.hoatson at gmail.com>
www.linkedin.com/in/<http://www.linkedin.com/in/>toddhoatson


More information about the wix-users mailing list