Closed grizoood closed 3 months ago
hiho, in msi AFAIK you have to specify reinstallmode. From view of Msi , is Repair - reintall, real Repair-Installation not happens. Standard is -omus, something like that. And there is also another way to use repair from msiexec. For example Adobe Reader DC , write reinstall command to registry and execute it if user click repair.
MS link to it : https://learn.microsoft.com/en-us/windows/win32/msi/reinstallmode
What should I do ?
This :
session["REINSTALLMODE"] = "o";
public void Repair()
{
if (session != null)
{
session["REINSTALL"] = "ALL";
session["REINSTALLMODE"] = "o";
session["MODIFY_ACTION"] = "Repair";
JumpToProgressDialog();
}
}
probably, "omus" or just "o". Try it. also looks in your msi description if reininstallmode spefifyed there. msiproject.ReinstallMode Property should be set, which one, you can choose or tested, what do u want. Important is : MajorUpgradeStrategy shoud be also set.
info from wix# self
The REINSTALLMODE property is a string that contains letters specifying the type // of reinstall to perform. Options are case-insensitive and order-independent. // This property should normally always be used in conjunction with the REINSTALL // property. // // Note, REINSTALLMODE property will be created only in the automatically produced // WiX definition file only if WixSharp.Project.MajorUpgrade is set.
The default is "omus".
//
// Résumé :
// The REINSTALLMODE property is a string that contains letters specifying the type
// of reinstall to perform. Options are case-insensitive and order-independent.
// This property should normally always be used in conjunction with the REINSTALL
// property.
//
// Note, REINSTALLMODE property will be created only in the automatically produced
// WiX definition file only if WixSharp.Project.MajorUpgrade is set.
//
// Read more: https://docs.microsoft.com/en-us/windows/desktop/msi/reinstallmode
public string ReinstallMode = "omus";
So I don't need to write this:
public void Repair()
{
if (session != null)
{
session["REINSTALL"] = "ALL";
session["REINSTALLMODE"] = "o";
session["MODIFY_ACTION"] = "Repair";
JumpToProgressDialog();
}
}
But this is enough, you just have to specify Reinstall
session["REINSTALL"] = "ALL";
public void Repair()
{
if (session != null)
{
session["REINSTALL"] = "ALL";
session["MODIFY_ACTION"] = "Repair";
JumpToProgressDialog();
}
}
Hi @grizoood. You wrote:
I have the impression that
session["MODIFY_ACTION"] = "Repair";
does nothing at all.Syntax
msiexec.exe [/f{p|o|e|d|c|a|u|m|s|v}]
This makes me think that you are expecting MODIFY_ACTION
to be set to one of the msiexec
args.
While this may even work it is not something that should not be assumed. session
and msiexec
are very different creatures and by default we have to assume that they do not share the interface. Unless of course, you found some evidence of sharing in the documentation.
Anyway, I decided to test your assumption directly. The assumption of session["MODIFY_ACTION"] = "Repair";
not triggering the repair option.
I just created a fresh project from the VS template (wix4 WPF) and it seems to repair correctly either from msi execution or from ARP. I have attached the working sample for you.
So quite possibly your problem is caused by something else, not by the incorrect value of the MODIFY_ACTION
property.
Hello @oleg-shilo
Having a problem with Wix4 for the %AppData% or %CommonAppData% folder (https://github.com/oleg-shilo/wixsharp/issues/1503), I am currently using the wix3 WPF template.
Here are the 2 screens on Wix4 of the access path:
I don't have access to version 2.1.6, I couldn't test it.
I carried out some tests using %ProgramFiles% in Wix3 and Wix4, it launches the reinstallation correctly (it copies the missing files) I also tried %CommonAppData% in Wix3, it launches the reinstallation fine.
But if I use %AppData%, (I was only able to do it in Wix3 because I had a problem with AppData in Wix4), it does nothing.
I simply modified the example to put %AppData%:
//string rootFolder = "%ProgramFiles%";
//string rootFolder = "%CommonAppData%";
string rootFolder = "%AppData%";
var project = new ManagedProject("MyProductWix3",
new Dir($@"{rootFolder}\My Company\My Product Wix3",
new File("Program.cs")));
project.InstallScope = InstallScope.perUser;
I am attaching the 2 projects Wix4 and Wix3. WixSharp.Setup.zip
Yeah, the #1503 fix (v.2.1.6) is not released yet. It's only available on my machine. My bad. I'll do the release right away.
Can you try with %AppData% and project.InstallScope = InstallScope.perUser with version 2.6.1 and wix4? Does it work?
Sorry, I think we are better off keeping focus and not jumping over all possible issues. :)
This very issue #1515 I cannot reproduce the problem. When I built a fresh project it works fine. I shared the sample with you. The sample is referencing the package that is not released yet so you can downgrade it to v2.1.5. It still works as expected. I just tested that very sample I shared with you.
Another reported but unrelated issue #1503 It's fixed but not released yet. I intend to release it right away. %AppData% problem should be discussed there, where it is reported. But I will retest it again before releasing it.
Done. v2.1.6 has been released
@oleg-shilo ,
The repair still does not work with %AppData%. Please test the project. (https://github.com/oleg-shilo/wixsharp/issues/1515#issuecomment-2088217398)
Sorry, it seems like two problems with the package not only that you describe
NuGet v2.1.6 was packaged with the wrong binaries. %AppData% is not expended in the installDir even though it works OK in the codebase. I will need to find out what happened to the package and rerelease it.
Even the msi that is build with the codebase binaries does not Repair correctly if it is %AppData% but does for the %ProgramFiles%. I have no explanation for that. BUt let me focus on the first problem first.
Problem 1 is a false alarm. I had a wrong NuGet cache on my PC so after clearing it it's all worked. Basically it was the case of "it does not work on my machine" :)
Problem 2 is a real problem caused by the differences in the wxs generated for %progfiles% and %appdata%. This difference was required because the appdata is a user profile location and it requires certain extra elements to be injected in wxs. Otherwise the ICE validation of msi fails. I am looking if disabling or partialy lifting these extra elements can address the problem.
Indeed disabling injection UserProfile element solves the problem. You can do it by selling the value of the AutoKey property before calling Project.Build.Msi()
:
AutoElements.DisableAutoUserProfileRegistry = true;
Just in case I have attached the sample project. WixSharp Setup1.zip
When it comes for the DisableAutoUserProfileRegistry
it insertion of the UserProfile registry it is required for the scenarios/samples listed in the property documentation:
//
// Summary:
// Disables automatic insertion of user profile registry elements. Required for:
// AllInOne, ConditionalInstallation, CustomAttributes, ReleaseFolder, Shortcuts,
// Shortcuts (advertised), Shortcuts-2, WildCardFiles samples.
//
// Can also be managed by disabling ICE validation via Light.exe command line arguments.
public static bool DisableAutoUserProfileRegistry = false;
Since your report is the very first case of the reported side effect of such technique I will need to decide if it should be enabled by default or not. I am not yet sure what the decision will be.
It seems to work by adding this:
AutoElements.DisableAutoUserProfileRegistry = true;
I no longer need to add this in MaintenanceTypeDialogModel :
session["REINSTALL"] = "ALL";
session["MODIFY_ACTION"] = "Repair";
Just this works with DisableAutoUserProfileRegistry = true
public void Repair()
{
if (session != null)
{
session["MODIFY_ACTION"] = "Repair";
JumpToProgressDialog();
}
}
Thks @oleg-shilo
Allow me to reopen the issue.
I subscribed to the AfterInstall event. In this event I have to create a file if (e.IsInstalling || e.IsRepairing) = true and delete the file if e.IsUninstalling = true
project.AfterInstall += e =>
{
Debugger.Launch();
MessageBox.Show(e.ToString());
foreach (var year in YEARS)
{
string directory = IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), $@"Autodesk\Revit\Addins\{year}");
string addinPath = IO.Path.Combine(directory, AddinBuilder.AddinName);
if (e.IsInstalling || e.IsRepairing)
{
if (!IO.Directory.Exists(directory))
IO.Directory.CreateDirectory(directory);
string dllPath = IO.Path.Combine(e.InstallDir, $"{year}");
var builder = new AddinBuilder(dllPath);
builder.Build(addinPath);
}
else if (e.IsUninstalling)
{
if (IO.File.Exists(addinPath))
IO.File.Delete(addinPath);
}
}
};
Following the click on the Repair button of the WPF interface :
I expect e.IsRepairing to be true and it is not.
I read elsewhere that (https://github.com/oleg-shilo/wixsharp/issues/1129) but the problem is that the modify action is not equal to "REPAIR", it is empty the property.
The results of e.ToString() :
I expect that
ModifyAction is equal to "REPAIR" via:
public void Repair()
{
if (session != null)
{
session["MODIFY_ACTION"] = "Repair";
JumpToProgressDialog();
}
}
I have ModifyAction = "Repair" in the Load and BeforeInstall events. So I have to use the BeforeInstall event to perform the processing of adding or deleting a file?
I did this instead, added empty .addin files for each version (2025, 2024, 2023, ...)
static void AddFiles(this ManagedProject project)
{
string installDir = @"%AppDataFolder%";
var files = new Dir($@"{installDir}",
new InstallDir($@"MyCompany\MyApp\",
//#### Revit 2025 ####
new Dir(@"2025\",
new Dir(@"fr-FR\",
new File(new Id("MyApp.resources.dll.fr_FR.2025"), @"..\MyApp\bin\Release R25\fr-FR\MyApp.resources.dll")),
new File(new Id("MyApp.dll.2025"), @"..\MyApp\bin\Release R25\MyApp.dll")),
//#### Revit 2024 ####
new Dir(@"2024\",
new Dir(@"fr-FR\",
new File(new Id("MyApp.resources.dll.fr_FR.2024"), @"..\MyApp\bin\Release R24\fr-FR\MyApp.resources.dll")),
new File(new Id("MyApp.dll.2024"), @"..\MyApp\bin\Release R24\MyApp.dll")),
//#### Revit 2023 ####
new Dir(@"2023\",
new Dir(@"fr-FR\",
new File(new Id("MyApp.resources.dll.fr_FR.2023"), @"..\MyApp\bin\Release R23\fr-FR\MyApp.resources.dll")),
new File(new Id("MyApp.dll.2023"), @"..\MyApp\bin\Release R23\MyApp.dll")),
//#### Revit 2022 ####
new Dir(@"2022\",
new Dir(@"fr-FR\",
new File(new Id("MyApp.resources.dll.fr_FR.2022"), @"..\MyApp\bin\Release R22\fr-FR\MyApp.resources.dll")),
new File(new Id("MyApp.dll.2022"), @"..\MyApp\bin\Release R22\MyApp.dll")),
//#### Revit 2021 ####
new Dir(@"2021\",
new Dir(@"fr-FR\",
new File(new Id("MyApp.resources.dll.fr_FR.2021"), @"..\MyApp\bin\Release R21\fr-FR\MyApp.resources.dll")),
new File(new Id("MyApp.dll.2021"), @"..\MyApp\bin\Release R21\MyApp.dll")),
new Dir($@"Resources\",
new File(new Id("UserSettings.xml"), @"..\Resources\UserSettings.xml"))),
new Dir(@"Autodesk\Revit\Addins",
new Dir(@"2025\",
new File(new Id("addin.2025"), @"..\Addin\MyApp.addin")),
new Dir(@"2024\",
new File(new Id("addin.2024"), @"..\Addin\MyApp.addin")),
new Dir(@"2023\",
new File(new Id("addin.2023"), @"..\Addin\MyApp.addin")),
new Dir(@"2022\",
new File(new Id("addin.2022"), @"..\Addin\MyApp.addin")),
new Dir(@"2021\",
new File(new Id("addin.2021"), @"..\Addin\MyApp.addin")))
);
project.AddDir(files);
}
then edited the file in AfterInstall. As in the example: Setup Events :
static void project_AfterInstall(SetupEventArgs e)
{
//Note AfterInstall is an event based on deferred Custom Action. All properties that have
//been pushed to e.Session.CustomActionData with project.DefaultDeferredProperties are
//also set as environment variables just before invoking this event handler.
//Similarly the all content of e.Data is also pushed to the environment variables.
MessageBox.Show(e.Session.GetMainWindow(),
e.ToString() +
"\npersisted_data = " + e.Data["persisted_data"] +
"\nADDFEATURES = " + e.Session.Property("ADDFEATURES") +
"\n'MyApp_Binaries' enabled = " + e.Session.IsFeatureEnabled("MyApp Binaries") +
"\nEnvVar('INSTALLDIR') -> " + Environment.ExpandEnvironmentVariables("%INSTALLDIR%My App.exe"),
caption: "AfterInstall ");
try
{
System.IO.File.WriteAllText(@"C:\Program Files (x86)\My Company\My Product\Docs\readme.txt", "test");
}
catch { }
}
I simply check if the file exists because if I am uninstalling it means that the file no longer exists so I do not modify it.
project.AfterInstall += e =>
{
foreach (var year in YEARS)
{
string directory = IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), $@"Autodesk\Revit\Addins\{year}");
string addinPath = IO.Path.Combine(directory, AddinBuilder.AddinName);
string dllPath = IO.Path.Combine(e.InstallDir, $"{year}");
var builder = new AddinBuilder(dllPath);
if (IO.File.Exists(addinPath))
IO.File.WriteAllText(addinPath, builder.ToString());
}
};
I need to do this because the "Assembly" property in the addin file changes depending on the year and I need to get the correct directory or its installed files:
<Assembly>C:\Users\USERNAME\AppData\Roaming\MyCompany\MyApp\2024\MyApp.dll</Assembly>
<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
<AddIn Type="Application">
<Name>MyApp</Name>
<Assembly>C:\Users\USERNAME\AppData\Roaming\MyCompany\MyApp\2024\MyApp.dll</Assembly>
<AddInId>61b750c3-0a9a-423c-9c64-472bbe7b05fc</AddInId>
<FullClassName>MyApp.Application</FullClassName>
<VendorId>MyCo</VendorId>
<VendorDescription>My Company, http://www.my-company.com</VendorDescription>
</AddIn>
</RevitAddIns>
I have another problem when I edit the "UserSettings.xml". I expect this file to be replaced during reinstallation but that's not the case either :/
new Dir($@"Resources\",
new File(new Id("UserSettings.xml"), @"..\Resources\UserSettings.xml"))),
I try this but not work :
new File(new Id("UserSettings.xml"), @"..\Resources\UserSettings.xml") { OverwriteOnInstall = true }
@oleg-shilo Can you help me resolve this problem?
Step to reproduce the problem:
1) Install the program for the first time 2) Go to the %AppData%\My Company\My App\2025 folder 3) Edit the File.txt file (adding text inside). 4) Restart the installation using “Repair”
The File.txt file is not replaced, however when using Repair via the drop-down menu it works.
For the Repair function to work from the wix# interface, I have to do this:
public void Repair()
{
if (session != null)
{
//session["MODIFY_ACTION"] = "Repair";
session["REINSTALL"] = "ALL";
session["MODIFY_ACTION"] = "Repair";
JumpToProgressDialog();
}
}
Is there a problem doing this?
Using this in Repair function of MaintenanceTypeDialog, it is working
public void Repair()
{
if (session != null)
{
//session["MODIFY_ACTION"] = "Repair";
session["REINSTALL"] = "ALL";
session["MODIFY_ACTION"] = "Repair";
JumpToProgressDialog();
}
}
Is there a problem using session["REINSTALL"] = "ALL";
in the Repair function?
@grizoood, this is a default behaviour of MSI install process. If the file has been modified then it's considered as no longer the file that was installed. I am surprized Repair from ARP (conrol panel) works.
The good practice rules require never modifying any installed files. All modifiable files (e.g. config files) are to be created by the product app on the first run. But, I understand, sometimes following rules is difficult.
Anyway, I wanted to suggest setting "OverwriteOnInstall" but you have already discovered this trik. And yes I have tested you sample and OverwriteOnInstall
does not help.
Now session["REINSTALL"] = "ALL"
. When I was implementing maintenance dialog I could not find and guidance anywhere hos to trigger the repair mode from ARP. I was only able to reverse engeneer session["MODIFY_ACTION"] = "Repair";
. Thus I do not know if session["REINSTALL"] = "ALL"
is going to cause a problem.
I google it right now and again, nothing concrete ablut this. My feeling is that you can use it and it will be just fine.
Potentially it can cause the problems when you have features and "REINSTALL:ALL" will reinstall the all default features and not nesesarely respect user feature selecting from the first session. But I have no evidence of that. :o(
Thk @oleg-shilo,
When you say:
All modifiable files (e.g. config files) are to be created by the product app on the first run.
Do you have an example in the WixSharp.Suite, to do this, I have several xml or json files to create.
No it's outside of WixSharp. It's even outside of msi.
MS articulated this deployment architecture fist time (long time ago) with the introduction of the "Certified for Vista" badge for SW products. Thus you could only certify if the deployment copies the required files (e.g. progfiles) and the product application never writes to the deployment dir. They were testing the product for that before granting the certification approval.
On the first start of the application itself, it creates the config files in the user profile. This architecture serves a few purposes:
With the approach that you have to use now restoring the XML files is impossible without restoring the binaries even though you do not have the reason for it. Your user only wanted to reset the config...
Hope this explains.
I did this instead, added empty .addin files for each version (2025, 2024, 2023, ...)
static void AddFiles(this ManagedProject project) { string installDir = @"%AppDataFolder%"; var files = new Dir($@"{installDir}", new InstallDir($@"MyCompany\MyApp\", //#### Revit 2025 #### new Dir(@"2025\", new Dir(@"fr-FR\", new File(new Id("MyApp.resources.dll.fr_FR.2025"), @"..\MyApp\bin\Release R25\fr-FR\MyApp.resources.dll")), new File(new Id("MyApp.dll.2025"), @"..\MyApp\bin\Release R25\MyApp.dll")), //#### Revit 2024 #### new Dir(@"2024\", new Dir(@"fr-FR\", new File(new Id("MyApp.resources.dll.fr_FR.2024"), @"..\MyApp\bin\Release R24\fr-FR\MyApp.resources.dll")), new File(new Id("MyApp.dll.2024"), @"..\MyApp\bin\Release R24\MyApp.dll")), //#### Revit 2023 #### new Dir(@"2023\", new Dir(@"fr-FR\", new File(new Id("MyApp.resources.dll.fr_FR.2023"), @"..\MyApp\bin\Release R23\fr-FR\MyApp.resources.dll")), new File(new Id("MyApp.dll.2023"), @"..\MyApp\bin\Release R23\MyApp.dll")), //#### Revit 2022 #### new Dir(@"2022\", new Dir(@"fr-FR\", new File(new Id("MyApp.resources.dll.fr_FR.2022"), @"..\MyApp\bin\Release R22\fr-FR\MyApp.resources.dll")), new File(new Id("MyApp.dll.2022"), @"..\MyApp\bin\Release R22\MyApp.dll")), //#### Revit 2021 #### new Dir(@"2021\", new Dir(@"fr-FR\", new File(new Id("MyApp.resources.dll.fr_FR.2021"), @"..\MyApp\bin\Release R21\fr-FR\MyApp.resources.dll")), new File(new Id("MyApp.dll.2021"), @"..\MyApp\bin\Release R21\MyApp.dll")), new Dir($@"Resources\", new File(new Id("UserSettings.xml"), @"..\Resources\UserSettings.xml"))), new Dir(@"Autodesk\Revit\Addins", new Dir(@"2025\", new File(new Id("addin.2025"), @"..\Addin\MyApp.addin")), new Dir(@"2024\", new File(new Id("addin.2024"), @"..\Addin\MyApp.addin")), new Dir(@"2023\", new File(new Id("addin.2023"), @"..\Addin\MyApp.addin")), new Dir(@"2022\", new File(new Id("addin.2022"), @"..\Addin\MyApp.addin")), new Dir(@"2021\", new File(new Id("addin.2021"), @"..\Addin\MyApp.addin"))) ); project.AddDir(files); }
then edited the file in AfterInstall. As in the example: Setup Events :
static void project_AfterInstall(SetupEventArgs e) { //Note AfterInstall is an event based on deferred Custom Action. All properties that have //been pushed to e.Session.CustomActionData with project.DefaultDeferredProperties are //also set as environment variables just before invoking this event handler. //Similarly the all content of e.Data is also pushed to the environment variables. MessageBox.Show(e.Session.GetMainWindow(), e.ToString() + "\npersisted_data = " + e.Data["persisted_data"] + "\nADDFEATURES = " + e.Session.Property("ADDFEATURES") + "\n'MyApp_Binaries' enabled = " + e.Session.IsFeatureEnabled("MyApp Binaries") + "\nEnvVar('INSTALLDIR') -> " + Environment.ExpandEnvironmentVariables("%INSTALLDIR%My App.exe"), caption: "AfterInstall "); try { System.IO.File.WriteAllText(@"C:\Program Files (x86)\My Company\My Product\Docs\readme.txt", "test"); } catch { } }
I simply check if the file exists because if I am uninstalling it means that the file no longer exists so I do not modify it.
project.AfterInstall += e => { foreach (var year in YEARS) { string directory = IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), $@"Autodesk\Revit\Addins\{year}"); string addinPath = IO.Path.Combine(directory, AddinBuilder.AddinName); string dllPath = IO.Path.Combine(e.InstallDir, $"{year}"); var builder = new AddinBuilder(dllPath); if (IO.File.Exists(addinPath)) IO.File.WriteAllText(addinPath, builder.ToString()); } };
I need to do this because the "Assembly" property in the addin file changes depending on the year and I need to get the correct directory or its installed files:
<Assembly>C:\Users\USERNAME\AppData\Roaming\MyCompany\MyApp\2024\MyApp.dll</Assembly>
<?xml version="1.0" encoding="utf-8"?> <RevitAddIns> <AddIn Type="Application"> <Name>MyApp</Name> <Assembly>C:\Users\USERNAME\AppData\Roaming\MyCompany\MyApp\2024\MyApp.dll</Assembly> <AddInId>61b750c3-0a9a-423c-9c64-472bbe7b05fc</AddInId> <FullClassName>MyApp.Application</FullClassName> <VendorId>MyCo</VendorId> <VendorDescription>My Company, http://www.my-company.com</VendorDescription> </AddIn> </RevitAddIns>
Hi @grizoood you can create that addin file with relative path.
MyApp .\Myaddinfolder\MyApp.dll 61b750c3-0a9a-423c-9c64-472bbe7b05fc MyApp.Application MyCo My Company, http://www.my-company.com
I'm using Custom UI WPF.
I am trying to carry out a repair, for this I first install the program, then I delete the installed folders, I restart the msi, I am on the MaintenanceTypeDialog window, I click on Repair, I expect that 'it copies the missing files again but it doesn't do it. Another thing I noticed is that if I do it from the control panel, and I click on Repair it works.
I replaced this:
by this:
I don't know if this is the right way to do it?
I have the impression that
session["MODIFY_ACTION"] = "Repair";
does nothing at all.https://learn.microsoft.com/fr-fr/windows-server/administration/windows-commands/msiexec
Here is the command executed by Windows (Task manager select "Details") when clicking on the “Repair” button: