2

I have written a C# custom action for my WIX V3 Installer which is supposed to modify my appsettings.json in the INSTALLFOLDER. The action´s Execute-attribute is set to immediate and Impersonate="no",it is called after InstallFinalize but it encounters a problem within this action which is the missing admin permission. The action modifies appsettings.json in the INSTALLFOLDER which is Program File (x86).

The custom action reads, deserializes, modifies, and serializes the data normally with no error. The error happens during writing to appsettings.json in InstallFolder. Although the error appears the rest of the application is installed and is working fine.

I have tried combining Execute and Custom actions in ALL possible combinations, and while I get privileges to write to InstallFolder if I change the Custom Action to run before the installation is finished I can't find the appsettings.json file because all the files at that point are temporary files (.tmp), and with non-relevant names.

The error that appears: Error message

Part of my Product.wsx code:

      <Property Id="Password" Value="Error password" />
      <Property Id="FilePath" Value="C:\Program Files (x86)\Company\Product\" />
      
      <CustomAction Id="SetUserName" Property="Username" Value="[ACCOUNT]"/>
      <CustomAction Id="SetPassword" Property="Password" Value="[PASSWORD]"/>
      <CustomAction Id="SetFilePath" Property="FilePath" Value="[INSTALLFOLDER]"/>
      
      <Binary Id="GetData" SourceFile="$(var.SetupExtensions.TargetDir)\$(var.SetupExtensions.TargetName).CA.dll" />  
      <CustomAction Id="ChangeJSON" BinaryKey="GetData" DllEntry="CustomAction1" Execute="immediate" Impersonate="no"  Return="check"/>
      
      <InstallExecuteSequence>
          <Custom Action="SetUserName" After="CostFinalize" />
          <Custom Action="SetPassword" After="CostFinalize" />
          <Custom Action="SetFilePath" After="CostFinalize"/>
          <Custom Action='ChangeJSON'  After='InstallFinalize'></Custom>
      </InstallExecuteSequence> 

My Custom Action code:

      public static ActionResult CustomAction1(Session session)
      {
          try
          {
              session.Log( "Begin CustomAction1" );
              string user = session["Username"];
              string password = session["Password"];
              string loc = session["FilePath"];

              var json = System.IO.File.ReadAllText( loc +"appsettings.json" );
              var root = JsonConvert.DeserializeObject<Root>(json);

              root.Default.UserName = user;
              root.Default.Password = password;
          
              json = JsonConvert.SerializeObject( root, Formatting.Indented );
              System.IO.File.WriteAllText( loc + "appsettings.json", json );
              //The MessageBox bellow shows(and is with correct info) when I remove System.IO.File.WriteAllText above ^^
              MessageBox.Show("Username: "+ user +"\nPassword: "+password +"\nFilePath: " + loc);
              return ActionResult.Success;
          }
          catch(Exception ex )
          {
              session.Log( "Error: " + ex.Message );
              MessageBox.Show(ex.Message);
              return ActionResult.Failure;
          }

How do I modify appsettings.json through my Custom Action?

  • It seems that you should set `execute="deferred"` in your custom action. See: https://stackoverflow.com/a/24486554/2109769 – Quercus Dec 18 '20 at 14:05

2 Answers2

1

Custom actions that change the system state should run between InstallIntialize and InstallFinalize. This means you should schedule it Before InstallFinalize not after.

It should also run in deferred execution with no impersonation. You will need another custom action scheduled prior to this one that creates custom action data and passes the data to the deferred custom action.

Ideally you should also have rollback and commit actions to support rollbacks and test using the WIXFAILWHENDEFERRED custom action.

Read: http://www.installsite.org/pages/en/isnews/200108/index.htm http://blog.iswix.com/2011/10/beam-me-up-using-json-to-serialize.html

Christopher Painter
  • 52,390
  • 6
  • 60
  • 97
  • That [WIXFAILWHENDEFERRED](https://wixtoolset.org/documentation/manual/v3/customactions/wixfailwhendeferred.html) custom action feature is nice - I keep forgetting it. A crucial element when it comes to testing rollback. Great to not have to write this yourself for every project. Do you know the JSON WiX project I point to in my answer? Any other projects? – Stein Åsmul Dec 19 '20 at 02:29
  • Hi, Tkank you for your answer. I tried running it as deferred before InstallFinalize, but I get an error saying "could not find appsettings.json", because the files were not yet installed. Would it be possible somehow to extract the appsettings.json from the .msi installer, and send it to my custom action? That way I just alter the file before installation(I have a dialog which changes the username and password, and I would get the data from the dialog, and alter the appsetings.json with them). – Professional_Amateur_Dev Dec 21 '20 at 10:42
1

Application Launch: I would consider updating the JSON via the application launch sequence if you can. 1) Single source solution, 2) familiar territory for developers, 3) easier and better debugging features and 4) no impersonation-, sequencing- and conditioning-complexities like you get for custom actions: Why is it a good idea to limit the use of custom actions in my WiX / MSI setups?


Deferred CA Sample: You can find a sample of a deferred mode custom action WiX solution here: https://github.com/glytzhkof/WiXDeferredModeSample

Inline Sample: This older answer shows the key constructs inline in the answer: Wix Custom Action - session empty and error on deferred action.

CustomActionData: Deferred mode custom actions do not have access to the session objects property values like immediate mode custom actions do. As Painter has written you need to "send" text or settings to deferred mode by writing the data into the execution script. The data is then available in deferred mode by reading the special property CustomActionData. The above sample should have the required constructs to see how this works. See the MSI documentation as well and also Robert Dickau's MSI Properties and Deferred Execution.

Transaction: Deferred mode custom actions can only exist between InstallInitialize and InstallFinalize. Actions between these actions run with elevated rights and can write to per-machine locations (not writeable for normal users). You can schedule yourself right before InstallFinalize for starters to test your current delete mechanism (I would try other approaches).

Rollback CAs: Rollback custom actions are intended to undo what was done with your JSON custom action and then revert everything to the previous state (cleanup after failed install). Writing these is quite involved and takes a lot of testing - many just skip them. It is best to try to find a library or a framework that does the job for you. I am not aware of any except the one linked to below, and I don't know its state. Rollback custom actions must precede the actual CA in the InstallExecuteSequence (when an MSI is rolling back it executes the sequence in reverse).

With all this complexity, custom actions become the leading source of deployment errors: https://robmensching.com/blog/posts/2007/8/17/zataoca-custom-actions-are-generally-an-admission-of-failure/


Links:

Stein Åsmul
  • 34,628
  • 23
  • 78
  • 140
  • Hello, thanks for answering my question. I tried the Json extension, but unfortunately it is not working. Rollback CA are a nice idea, I'll be sure to implement that. Would it be possible to get appsettings.json before InstallFinalize, send it to my Custom action to alter, and then send the altered appsettings.json back to the .msi installer? Basically I need a dialog which changes the username and password, and save it to appsettings.json (and I already have a working dialog, which passes the data to the CA) – Professional_Amateur_Dev Dec 21 '20 at 10:59