How to retrieve detailed result from msi installation - c++

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.

Related

Persistence of data for MSI installation

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.

Proper method to acquire root access on Linux for Qt applications

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.

Redirect syslog facility log to custom file

I have an embedded system with busy box in it. Busy box manual page states that:
"Note that this version of syslogd ignores /etc/syslog.conf."
There is an option -O, but it redirects all messages to custom file.
I`m sending messages from C++.
Found somewhere option -f for settings external config file - does not work.
That is how I connect to logger from my application:
bool Log::start()
{
/* Launch process here */
setlogmask(LOG_UPTO (LOG_DEBUG));
openlog(LOG_IDENTITY, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
return true;
}
Can different log location for certain facility or facility mask for whole syslog by calling functions from my application? Or somehow?
We faced this same problem (not Busybox, but redirecting an application's syslog to a different log file using the bare-bones syslogd). The solution is two steps:
Update the application's call to openlog() to set the 3rd parameter to int facility and then create the configuration code that converts LOCAL0 through LOCAL7 into the LOG_LOCAL0 through LOG_LOCAL7 labels. Then we configure the application to specify LOCAL3 for those hosts where only that application's log data is needed.
Then our Operations folks are working to configure syslogd to redirect the log data for the LOCAL3 facility to a separate file. I don't know exactly what they have come up with, so I won't speculate further. But they had asked me to configure the facility in the knowledge that they can redirect syslogd output based on the facility.
One thing that I noticed is that you use LOG_CONS. I used to do that, but found that when syslogd could not be written to, this caused syslog to fork a child process and the Solaris x86_64 process table filled with zombies. So I don't use that flag anymore.

Catching system events in C Linux

I am writing an application on Linux which requires me to catch system events like:
System reboot
User 'xyz' logs in
'xyz' application crashes etc.
and need to execute some functionality based on that. For e.g.:
Run backup script
Run recovery program etc.
Can anyone please tell me how to catch system events in C/Linux ?
P.S: I am not talking about 'file system' events here :P
There is no concept of "system event". You need to specify which events you need to handle and implement appropriate mechanism for handling each:
System startup: The init process calls scripts from /etc/init.d during startup. The exact infrastructure differs slightly between distributions, but Linux Standards Base System Initialization should generally work on all.
User login/logout: The LSB also defines interface to the Pluggable Authentication Modules library. You can implement a shared library that will be called during login (and also other actions that require authentication and authorization). Depending on what you want to do there may already be a module that will work for you, so try looking for it first. In either case I don't think there is distribution-independent way of installing it and even on given distribution you have to consider that administrator might have made custom modification, so the installation will need manual intervention by the administrator.
Application crashes: You would have to instrument it.
I think you should consider reading systems logs - everything you ask about is logged to the syslog (for standard configuration). If your system uses syslog-ng, then you could even configure it to write directly to your program, see http://www.syslog.org/syslog-ng/v2/#id2536904 for details. But even with any other syslog daemon, you can always read file (or files) from /var/log just like tail -f does, end react on particular messages.
I'm not sure about catching application crashes - there's a kernel option to log every SIGSEGV in user processes, but AFAIK it is available only on ARM architecture - last resort would be to instrument your application (as Jan Hudec pointed out) to log something to syslog.

Is MsiOpenProduct the correct way to read properties from an installed product?

Given an MSI product code I want to get the upgrade code (among other properties) from an already installed product. I have tried this by calling the MsiOpenProduct method, followed by MsiGetProductProperty(). An (abbreviated) example looks like this:
MSIHANDLE handle = NULL;
MsiOpenProduct(strProductCode,&handle);
CString strUpgradeCode;
MsiGetProductProperty(handle,_T("UpgradeCode"), strUpgradeCode.GetBuffer(GUID_LENGTH), &dwSize);
strUpgradeCode.ReleaseBuffer();
MsiCloseHandle(handle);
This gets me the desired value, and judging from the MSDN documentation this seems like a valid way to do this:
The MsiOpenProduct function opens a
product for use with the functions
that access the product database. The
MsiCloseHandle function must be called
with the handle when the handle is no
longer needed.
However the call to MsiOpenProduct() pops up the "Windows installer is preparing the installation..." dialog. The call to MsiCloseHandle() makes it disappear again.
This left me wondering:
What does the call to MsiOpenProduct() do under the hood? I do not want to trigger any actions, I just want to read properties.
I don't mind the dialog popping up, as this is only for unit test code as long as this has no side effects. And as there are many unit tests that do this, it must still work when opening and closing handles in rapid succession.
Although I stumbled over the MsiGetProductInfo method, there seems to be no way to get the upgrade code. Am I right?
Is MsiOpenProduct the correct way to read properties like the upgrade code?
MsiOpenProduct should be fine So long as you don't run any sequences or actions, it won't do anything. If you want to silence the dialog, you can with careful use of either MsiSetInternalUI() or MsiSetExternalUI().
Another approach you can take, as long as the ProductCode and UpgradeCode are safely static (i.e. as long as they aren't changed by transforms), is to locate the database using MsiGetProductInfo() and call MsiOpenDatabase() on that. The difference is that MsiOpenProduct() (or similarly MsiOpenPackage) applies the transforms that were used at installation time and prepares a session, whereas MsiOpenDatabase() does neither.
There is a comprehensive answer with information on how to get the UpgradeCode using Powershell or VBScript and WMI here: How can I find the Upgrade Code for an installed MSI file?
Below is a quick, basic example using VBScript / COM automation (MSI API, not WMI) and the approach discussed by OP (using the OpenProduct method - the COM equivalent to the Win32 installer function).
As discussed in my comment above, I will just add this little VBScript to do the same as OP does in C++. Note that Windows Installer can be accessed via WMI (Win32_Product object), COM automation and Win32 C++ installer functions.
For some reason the UpgradeCode for a package appears to not be available directly from the COM API or the Win32 API. Very strange indeed, especially since it is an input parameter to functions like Installer.RelatedProducts - it is not clear in the documentation that the actual call should be RelatedProducts(UpgradeCode), but looking in the msi.IDL you see: StringList* RelatedProducts([in] BSTR UpgradeCode);
The WMI option works, but so does this OpenProduct call demonstrated below (which is significantly faster and appears safe - WMI is completely read-only as far as I know though - but heaven knows what they are doing "under the hood". Are they spinning up a session object? Or are they reading from a WMI database? WMI does "feels" safer somehow).
The beauty of the below method is that it will apply all transforms that were applied to the product in question at installation time. If you want to write to disk instead of showing message boxes and can't be bothered looking up the docs, here is a similar VBScript that writes package info to a desktop text file: How can I find the product GUID of an installed MSI setup? - quite a bit down the page, just copy a couple of lines and you are message box free).
Note! The script below will create one log file per opened MSI if automatic logging is enabled on the system. As it stands the script will only open one MSI before it exists though (the Exit For construct).
On Error Resume Next ' This "tersified" script has no error handling
Const msiUILevelNone = 2
Set installer = CreateObject("WindowsInstaller.Installer")
Set products = installer.ProductsEx("", "", 7)
installer.UILevel = msiUILevelNone ' Suppress GUI (MSI progress dialog)
'Iterate over all MSI packages on the box
For Each product In products
' productcode = product.ProductCode
' name = product.InstallProperty("ProductName")
' version = product.InstallProperty("VersionString")
' pkgcode = product.InstallProperty("PackageCode")
Set session = installer.OpenProduct(product.ProductCode)
upgradecode = session.ProductProperty("UpgradeCode")
MsgBox upgradecode
Set session = Nothing ' Important, close the session (doesn't work in Javascript btw)
Exit For ' End after one iteration (so you don't get a million message boxes)
' Alternatively something like: If i > 4 Then Exit For
Next
Set installer = Nothing
MsgBox "Finished"
I have tried to look in the C++ Win32 installer functions for any other way to retrieve the UpgradeCode, but I can't see anything obvious. The session approach should work in C++ as well, but I am a little apprehensive about the release of handles and resources. I am not properly potty-trained with C++, but know more than enough to be dangerous. Fire In The Hole. Etc...
I wonder if the OP retrieved all packages on the box, or just a single one. I wonder if the timing issues and concurrent session object problems seen with Javascript would strike in C++ as well? I will give it a go I think - someday.
For the information you want, it sounds like you can just call ::MsiGetProductInfo(). ::MsiOpenDatabase() is a very slow operation while ::MsiGetProductInfo() is (IIRC) more on par with registry look ups.