I'm having a hard time understanding how to properly install and uninstall custom actions, and what the purpose of rollback is. I have a custom action called CreateFSRegistryLink that creates a REG_LINK registry entry (which cannot be created by MSI/InstallShield directly AFAIK). I think I have this running properly for the most part because if the link is already there, it just returns ERROR_FUNCTION_NOT_CALLED, which MSI seems to handle gracefully, proceeding with the rest of the install. This ensures that multiple instances of the product can be installed cleanly (we have a multi-instance product).
The problem comes during un-install. CreateFSRegistryLink appears to be running again in non-rollback mode. From the MSI log I can see that it's running as it should during an install and but it also runs during an uninstall:
I'm checking the mode with:
if (!MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK))
When the condition is true I log a message, "CreateFSRegistryLink is running in non-rollback mode." When it is false, I log a message, "CreateFSRegistryLink is running in rollback mode, so was skipped." I have never seen the second message show up in the log.
I have CreateFSRegistryLink set up with In-Script execution of "Deferred Execution in System Context." I also have another custom action DeleteFSRegistryLink set up with In-Script execution of "Rollback Execution in System Context". I see it getting skipped during an install, but not during an un-install (I suspect it's running normally during an un-install, but have not added logging to confirm this).
I also have a custom action CountOtherFSSystems that sets FS_SystemCount to the number of systems (instances) besides the current instance. I set DeleteFSRegisryLink to have a condition to only run when FS_SystemCount<1 in the Exec sequence. This is how I can tell that it is being skipped during an install because MSI reports that the condition wasn't met and so DeleteFSRegistryLink was skipped. I expect this to help ensure that it only runs when the last instance is being un-installed. I think this condition is working based on log output, but I don't know how to get this DeleteFSRegistryLink custom action to run properly during un-install without the CreateFSRegistryLink action re-installing the link. The last reference to DeleteFSRegistryLink I see in the log is:
MSI (s) (08:CC) [09:42:23:708]: Executing op: CustomActionSchedule(Action=DeleteFSRegistryLink,ActionType=3329,Source=BinaryData,Target=DeleteFSRegistryLink,)
I haven't added logging to this function yet, so I don't know if it ran, but when the un-install is done, the link in the registry is still there. This is not entirely surprising because immediately after that I see that CreateFSRegistryLink ran again:
MSI (s) (08:CC) [09:42:23:708]: Executing op: ActionStart(Name=CreateFSRegistryLink,,)
Action 9:42:23: CreateFSRegistryLink.
MSI (s) (08:CC) [09:42:23:708]: Executing op: CustomActionSchedule(Action=CreateFSRegistryLink,ActionType=3073,Source=BinaryData,Target=CreateFSRegistryLink,)
MSI (s) (08:0C) [09:42:23:739]: Invoking remote custom action. DLL: C:\windows\Installer\MSI37E1.tmp, Entrypoint: CreateFSRegistryLink
MSI (s) (08:70) [09:42:23:739]: Generating random cookie.
MSI (s) (08:70) [09:42:23:739]: Created Custom Action Server with PID 7640 (0x1DD8).
MSI (s) (08:18) [09:42:23:786]: Running as a service.
MSI (s) (08:18) [09:42:23:786]: Hello, I'm your 32bit Elevated custom action server.
CreateFSRegistryLink is running in non-rollback mode.
I followed the rule at https://msdn.microsoft.com/en-us/library/aa371369(v=vs.85).aspx of "A rollback custom action must always precede the deferred custom action it rolls back in the action sequence" which is still really not making sense to me seeing this log output and results. I think I'm missing a few key points here.
This is on my 'required reading' list for MSI and a good place to start:
Installation Phases and In-Script Execution Options for Custom Actions in Windows Installer
The idea is that every change made by MSI should be transactional. You should be able to rollback the state change on failure during an install, upgrade, repair or uninstall.
Occasionally you'll come across an API where this is not possible. An example would be deleting a user account or interacting with the old IIS metabase API. If the API doesn't support a .commit() .rollback() ability then you have to just make the change in the commit phase execution. Considering that commit phase can be disabled by disabling rollback you have to do it early in those scenarios.
Read the white paper a few times, digest it a bit and then follow up with any other questions that you still have.
Edit: This is how I ended up setting up my custom actions:
CountOtherFSSystems runs with Immediate Execution after InstallInitialize under all circumstances to set FS_SystemCount to the number of other instances that are installed.
RollbackFSRegistryLink runs with Rollback Execution in System Context after CountOtherFSSystems under the condition FS_SystemCount<1 And $FSRegistry = 3 (When the FSRegistry component was being installed local). It calls the function to delete the registry link.
CreateFSRegistryLink runs with Deferred Execution in System Context after RollbackFSRegistryLink under the condition $FSRegistry=3. It calls the function to create the registry link.
A bunch of other functions in the sequence execute, and then we get to the standard action WriteRegistryValues.
RollbackDeleteFSRegistryLink runs with Rollback Execution in System Context after WriteRegistryValues under the condition $FSRegistry<>3 (when the FSRegistry component was being removed, but the rollback needs to put it back). It calls the function to create the registry link.
DeleteFSRegistryLink runs with Deferred Execution in System Context after RollbackDeleteFSRegistryLink under the condition FS_SystemCount<1 AND $FSRegistry <> 3. It calls the function to delete the registry link.
TestError runs with Deferred Execution in System Context after DeleteFSRegistryLink. It calls a test function that just returns an error condition if the user says it's OK (via MSIProcessMessage) to introduce an error for test purposes here. (This function will be disabled for the production builds.)
I tested the following cases:
Error during installation of first instance - no registry entries or links are created.
Error during installation of second instance - only the first instance's registry entries remain, and the link remains also.
Error during uninstall of second instance - both instances and the link remain in the registry.
Error during un-install of last remaining instance - While the error is still displayed (before rollback occurs) I can see that the registry entries and link are all gone, and after proceeding, I see the rollback has restored the registry entries and the link.
Successful un-install of second instance - link and first instance's registry entries remain.
Successful un-install of last remaining instance - link and all registry entries are removed.
Please comment if you see anything I missed here. It seems pretty comprehensive to me.
To throw in my 2 cents: The issue seems to be related to custom action conditions. Regarding your call to MsiGetMode to see if it's a rollback, why bother? You sequence a rollback custom action before your actual deferred CA, and by definition it will be called only if the original custom action was called, and it's defined as a rollback CA and it needs no conditions. It may be that your uninstall CA can be the same as the rollback CA, but strictly speaking an uninstall CA can assume that its counterpart install CA worked correctly if it is coded correctly and failure causes the install to fail, whereas a rollback CA may need to assume that the install CA may have only partially worked and needs to check more system state.
If CreateRegistryFSLink is being called on uninstall then your condfition on that CA is incorrect.
If your code did or did not do something then it's up to you to remember what it did and the rollback CA undoes it.
The rest of it appears to be about the conditions on your custom actions. If you want one to be called only on uninstall of the product, then use REMOVE="ALL". If you have CAs related specifically to feature or component uninstall then (as Chris says) use a component or feature condition, they are here:
https://msdn.microsoft.com/en-us/library/aa368012(v=vs.85).aspx
If you want an uninstall CA to run as long as the product is not being upgraded, then REMOVE="ALL" and NOT UPGRADINGPRODUCTCODE will work.
So if you're still stuck, it may help to post your definitions of the CAs, in particular the conditions and types.
Related
PROBLEM: In a Sharpoint + BPM solution, running Windows 2012 with IIS 8, after the application pool is recycled, the first call of any type of process fails, all subsequent calls are successful.
Starting from an ascx embedded on an Sharpoint solution, one ascx per process type, we click a button that originates a server.transfer to a new page that creates a new instance of the pretended process type, if this suceeds, from here we response.redirect, with query string rewrite, to the new process instance just created. In the case of error (1st process after recycling) this last step does not ocurr.
As the page that instantiates de process is dynamic, depending on the type of process choosed, it could not be pré-loaded before start the solution, if we force the pré-instantiation of these pages at start-up, we could end up with a dummy process, one of each kind, at every new application pool recycle (once a day).
QUESTION: How can I locate [MyApp].XMLSerializers.dll and unload it in order to validate my theory that it’s absence is responsible for the 1st processes call failure?
SOLUTIONS ATTEMPTED:
Optimization of the Application Pool and Site configuration
No Results
Search for DLL bind errors
Using FUSLOGVW, after recycling application pool it seems that, in a first try, the [myApp].XMLSerializers.DLL, among others, is missing. As this is a time consuming step and the error does not happen when tracing (even only for event viewer) is enabled I supose that the on the fly generation of the DLL with all serializable types could be related to this issue.
Findings:
Afects also processes that don’t consume web services
When trace is on there is no error
Afects all environments
Any advice greatly appreciated
Many thanks, LTS
The MSI installation would call my (native/C++) custom action functions. Since the DLL is freshly loaded, and the MSIEXEC.EXE process is launched separately for each function (the callable actions, as specified in MSI/WiX script), I cannot use any global data in C/C++ program.
How (or Where) can I store some information about the installation going on?
I cannot use named objects (like shared-memory) as the "process" that launches the DLL to call the "action" function would exit, and OS will not keep the named-object.
I may use an external file to store, but then how would I know (in the DLL's function):
When to delete the external file.
When to find that this function call is the first call (Action/function call Before="LaunchConditions" may help, not very sure).
If I cannot delete the file, I cannot know if "information" is current or stale (i.e. belonging to earlier failed/succeeded MSI run).
"Temporary MSI tables" I have heard of, but not sure how to utilize it.
Preserve Settings: I am a little confused what your custom actions do, to be honest. However, it sounds like they preserve settings from an older application and setup version and put them back in place if the MSI fails to install properly?
Migration Suggestion (please seriously consider this option): Could you install your new MSI package and delete all shortcuts and access to the old application whilst leaving it
installed instead? Your new application version installs to a new path
and a new registry hive, and then you migrate all settings on first
launch of the new application and then kick off the uninstall of the
old application - somehow - or just leave it installed if that is
acceptable? Are there COM servers in your old install? Other things that have global registration?
Custom Action Abstinence: The above is just a suggestion to avoid custom actions. There are many reasons to avoid custom actions (propaganda piece against custom actions). If you migrate settings on application launch you avoid all sequencing, conditioning, impersonation issues along with the technical issues you have already faced (there are many more) associated with custom action use. And crucially you are in a familiar debugging context (application launch code) as opposed to the unfamiliar world of setups and their poor debugability.
Preserving Settings & Data: With regards to saving data and settings in a running MSI instance, the built in mechanism is basically to set properties using Session.Property (COM / VBScript) or MsiSetProperty (Win32) calls. This allows you to preserve strings inside the MSI's Session object. Sort of global data.
Note that properties can only be set in immediate mode (custom actions that don't change the system), and sending the data to deferred mode custom actions (that can make system changes) is quite involved centering around the CustomActionData concept (more on deferred mode & CustomActionData).
Essentially you send a string to the deferred mode custom action by means of a SetProperty custom action in immediate mode. Typically a "home grown" delimited string that you construct in immediate mode and chew up into information pieces when receiving it in deferred mode. You could try to use JSON-strings and similar to make transfer easier and more reliable by serializing and de-serializing objects via JSON strings.
Alternatives?: This set property approach is involved. Some people write to and from the registry during installation, or to a temp file (in the temp folder) and then they clean up during the commit phase of MSI, but I don't like this approach for several reasons. For one thing commit custom actions might not run based on policies on target systems (when rollback is disabled, no commit script is created - see "Commit Execution" section), and it isn't best practice. Adding temporary rows is an interesting option that I have never spent much time on. I doubt you would be able to easily use this to achieve what you need, although I don't really know what you need in detail. I haven't used it properly. Quick sample. This RemoveFile example from WiX might be better.
Good day
Background:
I am creating an OpenVPN wrapper application for Linux systems which is near completion. I have run into a little snag.
OpenVPN requires root access to modify routing tables (add and remove routes). Here is where things get a little vague and confusing.
Hopefully, by extending this question, some industry standard answers and solutions could be shared.
Documentation:
So, after a number of hours searching, I compiled a list of possible methods for acquiring root access, however none seem to be official nor any real solid guidance given to acquire this SU privilege.
Let us consider the following methods.
1. Using pkexec & polkits
Please find official freedesktop polkit documentation here and here for some information on best practises
There are a few tutorials found online using pkexec and polkits
- here, this helped me create my polkit file.
- SO Thread
- And a lovely small tutorial for Qt applications
To give a brief explanation (of my understanding) about pkexec and polkits:
polkits:
polkits consist of actions and rules (see documenation for indepth reading and explaination). It defines actions of an application and the rules associated with it. Rules can be defined as a user belonging to a specific group, whereby the action queries the rule, it it has succeeded in passing the rule, the user is authenticated automatically (with no popup password prompt), else they are required to enter an administrator password
pkexec:
a application used to interface with the polkit actions and authenticate an application to acquire root access.
These require adding an action into /usr/share/polkit-1/actions/ and /usr/share/polkit-1/rules.d/ (amongst other directories, see documentation for all locations)
This method seems to be well used (but requires a bit more explaination to be understood easily, imo)
note: There are qt-polkit libraries made available for usage, see their github repository
For a simple TL;DR version, see this
The polkit file I created (note this may not be correct, but it does work for me):
Location where it can be found/added (there are others too)
/usr/share/polkit-1/actions
Policy Kit file name:
com.myappname.something.policy // the .policy is required
Note:
com.myappname.something
is refereed to as the namespace of the policy (reading the documenation, this will not be clear)
Policy Kit content
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD polkit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/software/polkit/policyconfig-1.dtd">
<policyconfig>
<vendor>My App Name</vendor>
<vendor_url>http://myappurl.com/</vendor_url>
<action id="com.myappname.something.myaction-name">
<description>Run the polkit for My App which requires it for X usage</description>
<message>My App requires access to X, which requires root authentication, please let me have su access</message>
<icon_name>myappname</icon_name>
<defaults>
<allow_any>auth_admin_keep</allow_any>
<allow_inactive>auth_admin_keep</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/bin/myappname</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
</policyconfig>
Notes about my policy file (the important bits)
This is just an example, see documentation for official examples and descriptions:
<vendor>My App Name</vendor> is the application name, it can have spaces
<action id="com.myappname.something.myaction-name"> any action name here.
NOTE!
The file name -> com.myappname.something.policy and,
the action id -> com.myappname.something.myaction-name should have the same namespace
The icon name should be in accordance with with the latest freedesktop icon spec found here
TL;DR (or don't want to):
icon locations are:
1. /home/yourusername/.icons (sometimes not there)
2. /home/yourusername/.local/share/icons
2. /usr/share/icons
as long as they conform to the sizes and are a .png, you can pass the filename only (omitting the format)
Very Important:
<annotate key="org.freedesktop.policykit.exec.path">/usr/bin/myappname</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
When calling pkexec <myappname>, and NOT having these lines (honestly, I am not quite sure purpose of them), one will run into an error similar to this:
2017-12-19 12::58:24 Fatal: QXcbConnection: Could not connect to display ((null):0, (null))
Aborted (core dumped)
Note: keep the key the same, however you can and probably should change the exec.path key to your application location.
How do policy kits work?
In short, if you review the lovely example mentioned earlier (and skip all the very similar names of files), it becomes clear.
When one runs:
pkexec <myappname>
this calls the local authentication agent to run the application (in our case) as root.
This is handled by the actions (the policy kit mentioned above). Further more, the rules utilize the action id to perform addition queries, etc which can be viewed in the provided examples above.
Once the admin password has been entered, depending on the 'settings' entered into defaults (see here), we have:
auth_admin_keep
Like auth_admin but the authorization is kept for a brief period (e.g. five minutes).
Thus, the user can (in my OpenVPN application) connect to an OpenVPN connection for the next 5 minutes, until the password is requested again.
2. Sudo (/etc/sudoers):
This seems to be the go to method for most users who required root access, however it is not recommended:
e.g. Check root access before running the main application by calling a singleShot QProcess with arguments:
/bin/sh -c sudo -v
will result in exit code of 1 in various Linux distributions (thus causing me to search for alternatives)
3. setuid():
A very good example can be found here, unfortunatly it doesn't seem to work on modern Linux distros for the reason of it being a security hole which can easily be exploited.
In short, the process requires one to:
chmod +x <executable>
and checking that the s bit is set in the application using getuid() to get the user id and getgid() to get the group id of the user.
These functions can be found in Linux headers defined by:
<sys/types.h> // defines structs
<unistd.h> // defines methods
However, this does not seem to work with modern day Linux systems. Here is the output of executing a root owned application with the s bit set, run as a normal (unprivileged) user:
2017-12-19 12::21:08 Fatal: FATAL: The application binary appears to be running setuid, this is a security hole. ((null):0, (null))
Aborted (core dumped)
Farewell, setuid()
4. Other methods:
PAM
for further reading, see
man page
tutorial/explanation
A QT Forum question regarding cross platform PAM intergration
QT usermode
Qt provides a small implementation for acquiring root access, but it is only available to Linux distrobutions using yum package manager.
A question followed up on this, see this QT Forum question
Problem:
Above may only include a small portion of available methods to acquire root access, however considering that some applications are used globally, they are often attacked or pried apart or even compromised.
After doing this research, it has broadened my knowledge, but hasn't given me a sure-fire method which is recommended, but only hints.
Question:
Which method above is preferred in industry i.e. when should I use the one over the other (PAM vs polkits vs simple sudo) and if other methods are available, are these preferred?
However, this does not seem to work with modern day Linux systems.
Here is the output of executing a root owned application with the s
bit set, run as a normal (unprivileged) user:
2017-12-19 12::21:08 Fatal: FATAL: The application binary appears to be running setuid, this is a security hole. ((null):0, (null))
The above error has nothing to do with modern day Linux systems. Its a Qt protection against stupid developers using setuid without understanding it.
Simply call
QCoreApplication::setSetuidAllowed(true)
when your application starts, and then you can do setuid() just fine. You can even run privileged commands before dropping down to 'normal' user.
Summary:
Your Qt application must have root owner, and setuid bit set. Example debmaker is my Qt application that I want to perform privileged actions from. So after I build debmaker, I do:
sudo chown root:root debmaker
sudo chmod 4755 debmaker
(The latter sets the setuid bit)
Now run the Qt application
./debmaker
First thing the app does is check geteuid()==0 and getuid()==1000 (1000 is my user id, 0 is the root)
Then it launches a new process (using QProcess in Qt). This will run in privileged mode. (Example my child process is called chroot)
Now drop the main application (my debmaker) to normal user level by calling
setuid(getuid());
chroot (the child process ) will keep running as root user.
The main application, now no longer is running in elevated mode, but can send requests to its child process which is still running in elevated mode.
QProcess *chroot = new QProcess;
blah blah setup the chroot and start it
chroot->write("chown root:root /home/oosman/foo");
The last line will send a message to the child process. You read the stdin in the child process, parse the command, check it to ensure its not malicious (or malicious depending on your intent!) and execute the command.
Good research. But want add new case:
I think better way is make new Unix group and grant write access to target config file for group members. You will just add users to group. And only that users will change routes.
If routes defines with particular program. You can allow to run that program only for group members.
I have a .msi file created by Wix toolset, used to install 5 drivers. And I have a setup application to launch the .msi by CreateProcess with msiexec.exe command, and provide an UI. Currently, my requirement is get the detailed result of the installation – which drivers installed successfully, which failed. Since I can just get the result of CreateProcess, how can I retrieve the detailed result from the installation? Very appreciate if you can provide some information on this issue.
I created the .msi file with the difx:Driver flag like below:
<difx:Driver AddRemovePrograms="no" DeleteFiles="no" ForceInstall="no" Legacy="no" PlugAndPlayPrompt="no" />
An MSI-based setup is transactional. It either all works or all fails and rolls back the system to its previous state. It seems that you have made a choice to defeat this paradigm and have it partially succeed leaving some drivers installed and others not.
It also appears that you have suppressed the installer's UI so that error information cannot be found.
I have two recommendations:
Don't use CreateProcess() and the "fire and forget" model. Use MsiSetExternalUIRecord with this model:
https://msdn.microsoft.com/en-us/library/windows/desktop/bb309215(v=vs.85).aspx
There are C# p/invoke equivalents out there too. If you don't want to show all the UI then just collect the error messages and show them to the user if that's the goal. That's the only reliable way to get the actual error messages. This is the supported way for you to own the UI and collect only the messages that you think are important.
Allow a failed driver install to fail the entire install and roll it all back. It might actually be like this already. If the install partially succeeds and four drivers are not installed, what's the plan? You can't run the MSI again because it will go into repair/maintenance mode. If the user needs to fix something and do the install again then the product needs to be uninstalled anyway.
You can retrieve the verbose installation log using the /L*V parameter:
msiexec /i "C:\MyPackage\Example.msi" /L*V "C:\log\example.log"
You can read more here.
The general structure is:
msiexec.exe [/i][/x] <path_to_package> [/L{i|w|e|a|r|u|c|m|o|p|v|x+|!|*}][/log]
/L - enable logging
i - include status messages
w - include non-fatal warnings
e - include all error messages
a - mention when an action is started
r - include action-specific records
u - include user requests
c - include the initial UI parameters
m - include out-of-memory or fatal exit information
o - include out-of-disk-space messages
p - include terminal properties
v - verbose output
x - include extra debugging information
+ - append to an existing log file
! - flush each line to the log
* - log all information, except for v and x options
Another simpler method, instead of parsing the log, would be to write a small C# custom action to check if the drivers are installed on the machine.
You need to schedule that custom action close the end of the installation process, as deferred (not immediate).
You can generate a log (as suggested by Harsh) or you can create a custom action (either deferred as suggested by Bogdan if you are using the method he suggests) or sequenced after InstallFinalize (if you have some other method that doesn't require elevation), but that custom action would probably need to use some sort of IPC to communicate what it finds back to your program.
One possibility for IPC might be the MsiProcessMessage function in your custom action with the INSTALLMESSAGE_INFO message type (what you send will also show up in the log) that you can receive in your application, but that will require using the MsiSetExternalUIRecord function which will require replacing your CreateProcess calling msiexec with something from the Installation and Configuration Functions section of that page.
Or if writing custom actions isn't where you need to go it may be easier for you to call MsiGetFeatureState or MsiGetComponentState with MsiOpenProduct, assuming that gives you the granularity of detail you're after.
I have ETW tracing in a driver; the manifest file is created properly, the resources are all compiled in, etc. On the target machine, I run this command as administrator:
wevtutil im myManifest.xml
I get no errors. Then, I run (as administrator):
logman create trace myProviderName -o Log.etl -p "{myProviderGUID}" -f bincirc -max 1000
With no errors. Then I wait enough time for some traces to have been performed, and then I run these comands (as administrator):
logman stop myProviderName
tracerpt Log000001.etl
Now the problem I'm having is that the generated file, dumpfile.xml shows records of none of my traces at all. It shows a basic structure of setup details including the Provider GUID, etc.
So my question: Is there a step I'm missing in the above procedure, or must the problem be with my tracing code?
It turns out that there was a problem; though it wasn't with my code. I did not include opcodes for my events in the manifest, and as such, no events were being recorded.
If someone stumbles upon this post, it may help you to note that your events must have a channel, level, opcode, and template for even basic functionality. Additionally, the above procedure is missing one step. I needed to do the following:
Right click 'My Computer' and select 'Manage'
Click Performance > Data Collector Sets > User Defined > myProvider
Right click myProvider in right-hand pane, select 'Properties'
Set Keywords(Any), Keywords(All), and Level according to what I specified in the manifest
Restart my machine, and re-enable trace via logman.
The above procedure (in the question + the supplemental directly above) will create a log session and produce a basic readable log output from the generated ETL file.