wix: get install location from custom dll - c++

I have a problem with WiX and C++ custom actions DLL: I want to get the installation location specified in the .wxs file from custom action dll, I am using WcaGetTargetPath, but this API doesn't work. Anyone any idea how to do achieve this?
WiX file:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="PFiles">
<Directory Id="CompanyDir" Name="ABC">
<Directory Id="PCMINSTALLDIR" Name="ClientMonitor">
<Directory Id="STDSCIPTSDIR" Name="standard_scripts">
</Directory>
<Directory Id="CSTMSCIPTSDIR" Name="custom_scripts">
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
.....
<CustomAction Id="ChangeConfig"
BinaryKey="PcmConfig"
DllEntry="ModifyConfigFile"
Execute="deferred"
Return="check"
Impersonate="no"
HideTarget="no" />
<InstallExecuteSequence>
<Custom Action="ChangeConfig" Before="InstallFinalize" />
</InstallExecuteSequence>
Custom Action method:
LPWSTR pwzTargetPath = NULL;
hr = WcaGetTargetPath(L"PCMINSTALLDIR", &pwzTargetPath);
ExitOnFailure(hr, "Failed to get the PCM installation path.");

Related

How do I use the OrignalDatabase from MSI Installer in WIX and pass it to the C++ custom action code

Introduction:
I have a codebase being used for the updation of the MSI and that I am doing using the MD5 hash and every-time an update arrives at server, the code has functionalities that check the MD5 from DB and if it doesn't match then update it but for the first time when a new user installs it I need to find the current hash of the MSI at runtime, since user can change it's location and name and it can result in hash changes.
Problem Statement:
I have the WIX Installer for my MSI and I want to get to get the [Original Database] property from the WIX and send it to the C++ Custom Action DLL for generating MD5 at runtime, I am looking to do it post installation because the Database will be setup after the installation and the app starts running. So I can store the MD5 in a temporary file or do it post installation. I know other people also have questions already posted on StackOverflow regarding OriginalDatabase but I couldn't find them quite satisfactory and addressing the problem that I am facing and there was no proper code example for this and so I am writing this question.
The problem I am facing are these
How to get the path in WIX and send it to the C++ Custom Action
How to store the MD5 in the DB through WIX, though this is not too important but if I can get a solution for this, it would be great.
WIX Code:
<?xml version="1.0"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Product Id="{}" UpgradeCode="{}"
Name="Secureone Agent" Version="1.0.0" Manufacturer="MJ Inc." Language="1033">
<Package InstallerVersion="200" Compressed="yes" Comments="MJ Client Installer Package" Platform="x64"/>
<Media Id="1" Cabinet="product.cab" EmbedCab="yes"/>
<!-- Pre-Install checks -->
<Condition Message="This application is only supported on Windows Vista, Windows Server 2008, or higher.">
<![CDATA[Installed OR (VersionNT >= 600)]]>
</Condition>
<Condition Message="Setup can not be installed on x32 machine.">
<![CDATA[Not VersionNT32]]>
</Condition>
<?define Platform=x64 ?>
<?define BIN_PATH='.\bin' ?>
<?if $(var.Platform) = x64 ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<!-- License agreement -->
<WixVariable Id="WixUILicenseRtf" Value=".\eula\eula-en.rtf" />
<!--Application logo-->
<Icon Id="Logo.ico" SourceFile=".\resources\logo.ico" />
<!--Application logo for Control Panel-->
<Property Id="ARPPRODUCTICON" Value="Logo.ico" />
<!--Top Banner UI Logo-->
<WixVariable Id="WixUIBannerBmp" Overridable="yes" Value=".\resources\TopBanner.jpg" />
<!--Verticle Banner UI Logo-->
<WixVariable Id="WixUIDialogBmp" Overridable="yes" Value=".\resources\BackgroundLogo.jpg" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="INSTALLDIR" Name="Secureone">
<Component Id="ServiceConfigFile" Guid="{}">
<File Id="ServiceConfigFile" Source="$(var.BIN_PATH)\core.db"/>
</Component>
<Component Id="ServiceFile" Guid="{}">
<File Id="ServiceFile" Source="$(var.BIN_PATH)\lzsvc.exe"/>
<!-- Install service -->
<ServiceInstall Id="ServiceInstaller"
Type="ownProcess"
Name="LZAgentService"
DisplayName="LZ Agent Service"
Description="Service for LZ Agent"
Start="auto"
ErrorControl="normal"
Account="LocalSystem">
<util:ServiceConfig
FirstFailureActionType="restart"
SecondFailureActionType="restart"
ThirdFailureActionType="none"
ResetPeriodInDays="1"
RestartServiceDelayInSeconds="0" />
</ServiceInstall>
<ServiceControl Id="Service_start" Start="install" Stop="both" Name="LZAgentService" Wait="no" />
<ServiceControl Id="Service_stop" Stop="both" Remove="uninstall" Name="LZAgentService" Wait="no" />
<!-- Install service end -->
</Component>
</Directory>
</Directory>
</Directory>
<UIRef Id="WixUI_Minimal"/>
<Feature Id="DefaultFeature" Level="1">
<ComponentRef Id="ServiceFile"/>
<ComponentRef Id="ServiceConfigFile"/>
<ComponentRef Id="ProductComponent" />
</Feature>
<InstallExecuteSequence>
<Custom Action="RollbackCA" After="InstallFiles" />
<Custom Action='UserAuth' After='InstallFiles'/>
</InstallExecuteSequence>
</Product>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLDIR">
<Component Id="ProductComponent" Guid="{B2C908AF-C09A-41D0-92D1-AC7BEAC8F68D}">
<RemoveFile Id='lzsvc_log' On='uninstall' Name='lzsvc.log' />
<RemoveFile Id='lzsvc.log.bak' On='uninstall' Name='lzsvc.log.bak' />
<RemoveFile Id='lzcore_log' On='uninstall' Name='lzcore.log' />
</Component>
</ComponentGroup>
</Fragment>
<Fragment>
<CustomAction Id='UserAuth'
BinaryKey='AuthBinary'
DllEntry='AuthenticateUser'
Execute='deferred'
Return='check'
Impersonate="yes"
/>
<CustomAction Id='RollbackCA'
BinaryKey='AuthBinary'
DllEntry='RollbackInstall'
Execute='rollback'
Return='check'
Impersonate="yes"
/>
<Binary Id='AuthBinary' SourceFile='$(var.BIN_PATH)\CustomAction.dll'/>
</Fragment>
</Wix>
So the answer was actually a bit tricky at first but then I found it as my senior helped in it, so here it is. Currently this code finds the current path of the MSI on runtime and writes the MD5 of it in the C:\Windows\Temp\Path.txt but don't forget to include the md5 and md5wrapper header files and cpp files in the project.
C++ Custom Action:
void MD5ToDB(MSIHANDLE hInstall)
{
TCHAR* szValueBuf=NULL;
DWORD cchValueBuf = 0;
UINT uiStat = MsiGetProperty(hInstall, TEXT("OriginalDatabase"), TEXT(""), &cchValueBuf);
//cchValueBuf now contains the size of the property's string, without null termination
if (ERROR_MORE_DATA == uiStat)
{
++cchValueBuf; // add 1 for null termination
szValueBuf = new TCHAR[cchValueBuf];
if (szValueBuf)
{
uiStat = MsiGetProperty(hInstall, TEXT("OriginalDatabase"), szValueBuf, &cchValueBuf);
}
}
if (ERROR_SUCCESS != uiStat)
{
if (szValueBuf != NULL)
delete[] szValueBuf;
return;
}
//Convert tcahr to char
char* charWindowsPropertyValue = NULL;
charWindowsPropertyValue = new CHAR[cchValueBuf];
wcstombs(charWindowsPropertyValue, szValueBuf, wcslen(szValueBuf) + 1);//Converting tchar to char
std::string filePath(charWindowsPropertyValue);//converting char array to string
md5wrapper md5;
std::string hash = md5.getHashFromFile(filePath);//claculating md5 of the file path as string.
FILE* fp=NULL;
fopen_s(&fp,"C:\\Windows\\Temp\\Path.txt", "w+");//creating and writing to Path.txt
if (NULL != fp) {
fwrite(hash.c_str(), hash.length(), 1, fp);
fclose(fp);
}
}
UINT __stdcall ExtractingProperty(
MSIHANDLE hInstall
)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "ExtractingProperty");
ExitOnFailure(hr, "Failed to initialize");
WcaLog(LOGMSG_STANDARD, "Initialized.");
MD5ToDB(hInstall);
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
//Code was mostly taken from MSDN and the main attempt was to get the MD5 of the path after obtaining the path.
WIX File
<Custom Action="MD5ToDB" After"<Some Action>"/>
<CustomAction Id="MD5ToDB"
BinaryKey="AuthBinary"
DllEntry="ExtractingProperty"
Execute="immediate"
Return="check" />
Don't forget to add the function name in WIX .def file.

How to prevent Wix from overwriting directories on install

I have a Windows app which has 2 versions, kind of, but a different name for each version. I use the same .wxs to build because I have no reasons to create a second wix project. I do not want to argue wether it is a good idea in here.
The thing is: each version must be installed in
path \ mainDir \ versionDir
Where path \ mainDir is the same for each version.
My problem is that mainDir is being entirely overwritten on install by each version.
Let's say I install version1, I'll have
path \ mainDir \ version1Dir
And then if I install version2, instead of having
path \ mainDir \ version1Dir
path \ mainDir \ version2Dir
I'll have
path \ mainDir \ version2Dir
I've been trying to get this around using Wix - how to prevent overwrite entire directory?, but it only applies to files since Conditions cannot be assigned to directories (or maybe I have haven't found how to do so, I don't know).
My goal is to have each versions able to install in its own directory, creating mainDir if it doesn't exist, but only removing it if it's empty.
Here's the code, any leads would be very much appreciated.
<!-- This is in Product, I'm just pasting it here -->
<Property Id="ALREADYINSTALLED">
<RegistrySearch Id="InstallPath" Key="Software\$(var.MainDir)" Name="MainFolder" Root="HKCU" Type="directory" />
</Property>
<Property Id="SECONDALREADYINSTALLED">
<RegistrySearch Id="SecondInstallPath" Key="Software\$(var.MainDir)\$(var.SecondDir)" Name="SecondFolder" Root="HKCU" Type="directory" />
</Property>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="WINDOWSVOLUME">
<Directory Id="INSTALLFOLDER" Name=".">
<Directory Id="MAINFOLDER" Name="$(var.MainDir)">
<!-- Allows the removal of this directory on uninstall -->
<Component Id="mainFolderRemoval">
<RegistryValue Root="HKCU" Key="Software\$(var.MainDir)" Name="MainFolder" Type="string" KeyPath="yes" />
<RemoveFolder Id="removal" On="uninstall" Property="ALREADYINSTALLED"/>
</Component>
<Directory Id="SECONDFOLDER" Name="$(var.SecondDir)">
<Component Id="secondFolderRemoval">
<RegistryValue Root="HKCU" Key="Software\$(var.MainDir)\$(var.SecondDir)" Name="SecondFolder" Type="string" KeyPath="yes" />
<util:RemoveFolderEx On="uninstall" Property="SECONDALREADYINSTALLED"/>
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
<SetDirectory Id="WINDOWSVOLUME" Value="[WindowsVolume]" />
<!-- Overwrites the main folder if already installed -->
<SetDirectory Id="MAINFOLDER" Value="[ALREADYINSTALLED]"> <![CDATA[ALREADYINSTALLED]]> </SetDirectory>
</Fragment>
So I ended up finding why it wouldn't work.
You have to make sure that your product upgrade code is different for each version, which was quite obvious actually.
One could argue that I should delete this post, but I believe it could happen to anyone and this is too much of a stupid issue to let people loose time with this. I'll leave this question as an example, hopefully it might be useful to somebody in the future.

PHPUnit doesn't load some of the tests

I have a tests folder in one of my modules, with 3 subfolders:
Model
Helper
Integration
running
phpunit --group My_Module
executes only the tests in Model and Helper directories, with the Integration folder left untouched. Test files reside directly in the folder in all of the cases, so I'm not sure what's causing the problem.
Here's my phpunit.xml:
<?xml version="1.0"?>
<!-- initial phpunit configuration file, that you can modify for your project needs -->
<phpunit cacheTokens="true"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
strict="false"
verbose="false"
bootstrap="app/code/community/EcomDev/PHPUnit/bootstrap.php">
<listeners>
<listener file="app/code/community/EcomDev/PHPUnit/Test/Listener.php" class="EcomDev_PHPUnit_Test_Listener" />
</listeners>
<testsuites>
<testsuite name="MyApp">
<directory suffix=".php">app/code/local/*/*/Test/*/</directory>
</testsuite>
<testsuite name="Magento Test Suite">
<file>app/code/community/EcomDev/PHPUnit/Test/Suite.php</file>
</testsuite>
</testsuites>
<filter>
<blacklist>
<!-- Exclude Magento Core files from code coverage -->
<directory suffix=".php">app/code/core</directory>
<!-- Exclude EcomDev_PHPUnit classes from code coverage -->
<directory suffix=".php">app/code/community/EcomDev/PHPUnit</directory>
<directory suffix=".php">lib/EcomDev/Utils</directory>
<directory suffix=".php">lib/EcomDev/PHPUnit</directory>
<directory suffix=".php">lib/Spyc</directory>
<directory suffix=".php">lib/vfsStream</directory>
<!-- Exclude Mage.php file from code coverage -->
<file>app/Mage.php</file>
<!-- Exclude template files -->
<directory suffix=".phtml">app/design</directory>
<!-- Exclude Varien & Zend libraries -->
<directory suffix=".php">lib/Varien</directory>
<directory suffix=".php">lib/Zend</directory>
</blacklist>
</filter>
<logging>
<!-- Uncomment this line if you want to have coverage report generated every test run
<log type="coverage-html" target="var/phpunit/coverage" charset="UTF-8"
yui="true" highlight="false"
lowUpperBound="35" highLowerBound="70"/>
<log type="coverage-clover" target="var/phpunit/coverage.xml"/>
-->
<log type="junit" target="../build/logs/junit.xml" logIncompleteSkipped="false"/>
</logging>
</phpunit>
The version of PHPunit I'm running on ubuntu VM is 3.7.28. How can I get all of the tests to run? Help would be much appreciated.
Try to call
phpunit --list-groups
This show you list of available test groups. Do you see My_Module in list?
1) Yes - issue in tests folders/structure.
2) No - issue in xml configuration.

Count using xslt when looking at sub-strings

I would like to count the number of items that match the following conditions:
/results/errors/error/[#severity='warning']
and
test="not(string-length(substring-before(/results/errors/error/location/#file,'cow'))=0)"
So, while my first condition can easily be counted by using the built in count function
count(//results/errors/error[#severity='warning'])
I do not know how to use the count function for the second condition, as it is not in Xpath format.
Any help you can provide to me would be greatly appreciated. (My XML is below)
The desired answer would be: 2
Thank you,
Belinda
My xml looks like this:
<results version="2">
<errors>
<error id="redundantAssignment" severity="performance" msg="blah, blah.">
<location file="/farm/user/cow/src/IO/file1.cpp"/>
<location file="/farm/user/horse//src/IO/file5.cpp"/>
</error>
<error id="redundantAssignment" severity="warning" msg="blah, blah.">
<location file="/farm/user/sheep/src/IO/file1.cpp"/>
<location file="/farm/user/cow/src/IO/file2.cpp"/>
</error>
<error id="redundantAssignment" severity="error" msg="blah, blah.">
<location file="/farm/user/horse/src/IO/file1.cpp"/>
<location file="/farm/user/cow//src/IO/file3.cpp"/>
</error>
<error id="redundantAssignment" severity="warning" msg="blah, blah.">
<location file="/farm/user/sheep/src/IO/file1.cpp"/>
<location file="/farm/user/cow/src/IO/file3.cpp"/>
</error>
<error id="redundantAssignment" severity="warning" msg="blah, blah.">
<location file="/farm/user/sheep/src/IO/file1.cpp"/>
<location file="/farm/user/horse/src/IO/file3.cpp"/>
</error>
<error id="redundantAssignment" severity="style" msg="blah, blah.">
<location file="/farm/user/sheep/src/IO/file1.cpp"/>
<location file="/farm/user/horse/src/IO/file4.cpp"/>
</error>
<error id="redundantAssignment" severity="style" msg="blah, blah.">
<location file="/farm/user/sheep/src/IO/file1.cpp"/>
<location file="/farm/user/horse/src/IO/file2.cpp"/>
</error>
</errors>
</results>
You should be able to combine both into a single count() by adding the second test to the first predicate:
count(/results/errors/error[#severity='warning' and location[not(string-length(substring-before(#file,'cow'))=0)]])
Using your sample XML input, this produces 2.
Simplification using contains() suggested by Tim C...
count(/results/errors/error[#severity='warning' and location[contains(#file,'cow')]])

WiX Make a Sub-Dir a Component Group when running heat

Hello all my fellow WiX'ers,
I was wondering if it possible, and if so where I can go to learn how to do it, to run heat on a directory and have each directory inside that one be it's own Component Group.
Example:
Root Directory
Sub Dir 1
Sub Sub Dir 1
Sub Sub Dir 2
Sub Sub Dir 3
Sub Dir 2
Sub Sub Dir 1
Sub Sub Dir 2
Sub Sub Dir 3
Sub Dir 3
Sub Sub Dir 1
Sub Sub Dir 2
Sub Sub Dir 3
Then run a heat command in the Build Event of the VS2010 project (example below):
heat dir "Root Directory" -gg -sfrag -srd -dr INSTALLFOLDER -out MyWXS.wxs
and then have that WXS file structured like so:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirecotryRef Id="INSTALLFOLDER">
<Directory Id="dir84156489" Name="Sub Dir 1"> ... </Directory>
<Directory Id="dir84156489" Name="Sub Dir 2"> ... </Directory>
<Directory Id="dir84156489" Name="Sub Dir 3"> ... </Directory>
</DirectoryRed>
</Fragment>
<Fragment>
<ComponentGroup Id="Sub Dir 1"> ... </ComponentGroup>
<ComponentGroup Id="Sub Dir 2"> ... </ComponentGroup>
<ComponentGroup Id="Sub Dir 3"> ... </ComponentGroup>
</Fragment>
</wix>
If there is any confusion in my question or if anyone has any additional questions for me please let me know. Thank you and I look forward to hearing from you.
EDIT
Using the following xslt file I am getting the WXS structure that follows after:
**XLST File**
<?xml version="1.0" encoding="utf-8"?>
**WXS File Result**
<Wix>
<Fragment>
<DirectoryRef Id="INSTALLFOLDER">
<Directory Id="dir846546" Name="SubDir1"> ... </Directory>
<Directory Id="dir846546" Name="SubDir2"> ... </Directory>
<Directory Id="dir846546" Name="SubDir3"> ... </Directory>
</DirectoryRef>
</Fragment>
<wix:Fragment xmlns:wix="http://schemas.microsoft.com/wix/2006/wi">
<wix:ComponentGroup Id="SubDur1"> ... </wix:ComponentGroup>
</wix:Fragment>
<wix:Fragment xmlns:wix="http://schemas.microsoft.com/wix/2006/wi">
<wix:ComponentGroup Id="SubDur2"> ... </wix:ComponentGroup>
</wix:Fragment>
<wix:Fragment xmlns:wix="http://schemas.microsoft.com/wix/2006/wi">
<wix:ComponentGroup Id="SubDur3"> ... </wix:ComponentGroup>
</wix:Fragment>
</Wix>
No matter what I do I cannot get the Directories to be created as component groups...
Heat can do an XSL transform before emitting its output. Just add the -t transform.xsl argument.
All that's needed is to append a few component groups to the output. Generate a component group by matching on a first-level directory and then referencing all of the descendent components.
See the XSL for my answer to a similar question.