Skip to content

Commit

Permalink
Fix IsWindowsVersionOrGreater: Use Kernel32.dll product version rathe…
Browse files Browse the repository at this point in the history
…r than file version. It correctly ientifies Windows OS version number
  • Loading branch information
nirbar committed Nov 6, 2024
1 parent ad4104e commit ac6efc8
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 34 deletions.
68 changes: 51 additions & 17 deletions PanelSwCustomActions/IsWindowsVersionOrGreater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#include <WixString.h>
#include <pathutil.h>
#include <fileutil.h>
#include <memutil.h>

static HRESULT DAPI GetKernel32ProductVersion(ULARGE_INTEGER* pullKernelProductVersion);

extern "C" UINT __stdcall IsWindowsVersionOrGreater(MSIHANDLE hInstall)
{
Expand All @@ -10,9 +13,8 @@ extern "C" UINT __stdcall IsWindowsVersionOrGreater(MSIHANDLE hInstall)
BOOL bRes = TRUE;
PMSIHANDLE hView;
PMSIHANDLE hRecord;
HMODULE hKernel32 = NULL;
CWixString szKernel32Path, szKernel32Version;
ULARGE_INTEGER ullKernelVersion;
CWixString szKernel32Version;
ULARGE_INTEGER ullKernelVersion = {};

hr = WcaInitialize(hInstall, __FUNCTION__);
ExitOnFailure(hr, "Failed to initialize");
Expand All @@ -22,40 +24,42 @@ extern "C" UINT __stdcall IsWindowsVersionOrGreater(MSIHANDLE hInstall)
ExitOnFailure(hr, "Failed to check if table exists 'PSW_IsWindowsVersionOrGreater'");
ExitOnNull((hr == S_OK), hr, E_FAIL, "Table does not exist 'PSW_IsWindowsVersionOrGreater'. Have you authored 'PanelSw:IsWindowsVersionOrGreater' entries in WiX code?");

hKernel32 = ::GetModuleHandleW(L"kernel32");
ExitOnNullWithLastError(hKernel32, hr, "Failed to get module handle for kernel32.");

hr = PathForCurrentProcess((LPWSTR*)szKernel32Path, hKernel32);
ExitOnFailure(hr, "Failed to get full path of Kernel32.dll");

hr = FileVersion((LPCWSTR)szKernel32Path, &ullKernelVersion.HighPart, &ullKernelVersion.LowPart);
ExitOnFailure(hr, "Failed to get version of Kernel32.dll");
hr = GetKernel32ProductVersion(&ullKernelVersion);
ExitOnFailure(hr, "Failed to get product version of Kernel32.dll");

hr = FileVersionToStringEx(ullKernelVersion.QuadPart, (LPWSTR*)szKernel32Version);
ExitOnFailure(hr, "Failed to parse version of Kernel32.dll");
WcaLog(LOGMSG_STANDARD, "Detected version '%ls' of '%ls'", (LPCWSTR)szKernel32Version, (LPCWSTR)szKernel32Path);
ExitOnFailure(hr, "Failed to parse product version of Kernel32.dll");
WcaLog(LOGMSG_STANDARD, "Detected product version '%ls' of Kernel32.dll", (LPCWSTR)szKernel32Version);

// Execute view
hr = WcaOpenExecuteView(L"SELECT `Property_`, `Version` FROM `PSW_IsWindowsVersionOrGreater`", &hView);
hr = WcaOpenExecuteView(L"SELECT `Property_`, `MinVersion`, `MaxVersion` FROM `PSW_IsWindowsVersionOrGreater`", &hView);
ExitOnFailure(hr, "Failed to execute MSI SQL query");

// Loop
while ((hr = WcaFetchRecord(hView, &hRecord)) != E_NOMOREITEMS)
{
ExitOnFailure(hr, "Failed to fetch record.");

CWixString szProperty, szMinVersion;
ULARGE_INTEGER ullMinVersion;
CWixString szProperty, szMinVersion, szMaxVersion;
ULARGE_INTEGER ullMinVersion = {}, ullMaxVersion = {};

hr = WcaGetRecordString(hRecord, 1, (LPWSTR*)szProperty);
ExitOnFailure(hr, "Failed to get Property_.");
hr = WcaGetRecordString(hRecord, 2, (LPWSTR*)szMinVersion);
ExitOnFailure(hr, "Failed to get Version.");
hr = WcaGetRecordString(hRecord, 3, (LPWSTR*)szMaxVersion);
ExitOnFailure(hr, "Failed to get Version.");

hr = FileVersionFromString((LPCWSTR)szMinVersion, &ullMinVersion.HighPart, &ullMinVersion.LowPart);
ExitOnFailure(hr, "Failed to parse minimal version '%ls' for '%ls'.", (LPCWSTR)szMinVersion, (LPCWSTR)szProperty);

if (ullKernelVersion.QuadPart >= ullMinVersion.QuadPart)
if (!szMaxVersion.IsNullOrEmpty())
{
hr = FileVersionFromString((LPCWSTR)szMaxVersion, &ullMaxVersion.HighPart, &ullMaxVersion.LowPart);
ExitOnFailure(hr, "Failed to parse maximal version '%ls' for '%ls'.", (LPCWSTR)szMinVersion, (LPCWSTR)szProperty);
}

if ((ullKernelVersion.QuadPart >= ullMinVersion.QuadPart) && (szMaxVersion.IsNullOrEmpty() || (ullKernelVersion.QuadPart <= ullMaxVersion.QuadPart)))
{
hr = WcaSetIntProperty((LPCWSTR)szProperty, 1);
ExitOnFailure(hr, "Failed to set property.");
Expand All @@ -67,3 +71,33 @@ extern "C" UINT __stdcall IsWindowsVersionOrGreater(MSIHANDLE hInstall)
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}

static HRESULT DAPI GetKernel32ProductVersion(ULARGE_INTEGER* pullKernelProductVersion)
{
HRESULT hr = S_OK;
UINT cbVerBuffer = 0;
LPVOID pVerBuffer = nullptr;
VS_FIXEDFILEINFO* pvsFileInfo = nullptr;
UINT cbFileInfo = 0;
BOOL bRes = TRUE;

cbVerBuffer = ::GetFileVersionInfoSize(L"Kernel32", nullptr);
ExitOnNullWithLastError(cbVerBuffer, hr, "Failed to get Kernel32.dll version info size");

pVerBuffer = ::MemAlloc(cbVerBuffer, TRUE);
ExitOnNullWithLastError(pVerBuffer, hr, "Failed to allocate memory");

bRes = ::GetFileVersionInfo(L"Kernel32", 0, cbVerBuffer, pVerBuffer);
ExitOnNullWithLastError(bRes, hr, "Failed to get Kernel32.dll version info");

bRes = ::VerQueryValue(pVerBuffer, L"\\", (void**)&pvsFileInfo, &cbFileInfo);
ExitOnNullWithLastError(bRes, hr, "Failed to get Kernel32.dll version");

pullKernelProductVersion->HighPart = pvsFileInfo->dwProductVersionMS;
pullKernelProductVersion->LowPart = pvsFileInfo->dwProductVersionLS;

LExit:
ReleaseMem(pVerBuffer);

return hr;
}
3 changes: 2 additions & 1 deletion PanelSwWixExtension/Data/tables.xml
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,8 @@
<tableDefinition name="PSW_IsWindowsVersionOrGreater">
<columnDefinition name="Id" type="string" length="72" primaryKey="yes" modularize="column" category="identifier" />
<columnDefinition name="Property_" type="string" length="72" modularize="column" category="identifier" />
<columnDefinition name="Version" type="string" length="0" modularize="none" category="version" />
<columnDefinition name="MinVersion" type="string" length="0" modularize="none" category="version" />
<columnDefinition name="MaxVersion" type="string" length="0" modularize="none" category="version" nullable="yes" />
</tableDefinition>

<tableDefinition name="PSW_Payload">
Expand Down
34 changes: 27 additions & 7 deletions PanelSwWixExtension/PanelSwWixCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,8 @@ private void ParseIsWindowsVersionOrGreater(XmlNode node)
{
SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
string property = null;
string version = null;
string minVersion = null;
string maxVersion = null;

if (node.ParentNode.LocalName != "Property")
{
Expand All @@ -1418,7 +1419,23 @@ private void ParseIsWindowsVersionOrGreater(XmlNode node)
switch (attrib.LocalName)
{
case "Version":
version = Core.GetAttributeValue(sourceLineNumbers, attrib);
Core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.LocalName, attrib.LocalName, "MinVersion"));
if (minVersion != null)
{
Core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.LocalName, attrib.LocalName, "MinVersion"));
break;
}
minVersion = Core.GetAttributeVersionValue(sourceLineNumbers, attrib, true);
break;
case "MinVersion":
if (minVersion != null)
{
Core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.LocalName, attrib.LocalName, "Version"));
}
minVersion = Core.GetAttributeVersionValue(sourceLineNumbers, attrib, true);
break;
case "MaxVersion":
maxVersion = Core.GetAttributeVersionValue(sourceLineNumbers, attrib, true);
break;

default:
Expand All @@ -1432,13 +1449,15 @@ private void ParseIsWindowsVersionOrGreater(XmlNode node)
{
Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.ParentNode.LocalName, "Id"));
}
if (string.IsNullOrEmpty(version))
if (string.IsNullOrEmpty(minVersion))
{
Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.LocalName, "Version"));
Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.LocalName, "MinVersion"));
}
if (!Version.TryParse(version, out Version v))
if (!string.IsNullOrEmpty(maxVersion) && Version.TryParse(maxVersion, out Version v))
{
Core.OnMessage(WixErrors.IllegalVersionValue(sourceLineNumbers, node.LocalName, "Version", version));
int build = v.Build >= 0 ? v.Build : 0xFFFF;
int revision = v.Revision >= 0 ? v.Revision : 0xFFFF;
maxVersion = $"{v.Major}.{v.Minor}.{build}.{revision}";
}

// find unexpected child elements
Expand All @@ -1457,7 +1476,8 @@ private void ParseIsWindowsVersionOrGreater(XmlNode node)
Row row = Core.CreateRow(sourceLineNumbers, "PSW_IsWindowsVersionOrGreater");
row[0] = "wmv" + Guid.NewGuid().ToString("N");
row[1] = property;
row[2] = version;
row[2] = minVersion;
row[3] = maxVersion;
}
}

Expand Down
16 changes: 13 additions & 3 deletions PanelSwWixExtension/Xsd/PanelSwWixExtension.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,25 @@
<xse:parent namespace="http://schemas.microsoft.com/wix/2006/wi" ref="Property" />
</xs:appinfo>
<xs:documentation>
<![CDATA[Test whether Kernel32.dll's version is at least the given version. If yes, set the property to 1.]]>
<![CDATA[Test whether Kernel32.dll's product version is at least the given version. If yes, set the property to 1.]]>
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="Version" type="xs:string" use="required">
<xs:attribute name="Version" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>Minimal version to test. The version is compared against Kernel32.dll's version. The property is set to 1 if Kernel32.dll's is at least the minimal version. Otherwise, the property is unmodified.</xs:documentation>
<xs:documentation>Deperecated. Use MinVersion instead</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="MinVersion" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>Minimal version to test. The version is compared against Kernel32.dll's product version. The property is set to 1 if Kernel32.dll's is at least the minimal version. Otherwise, the property is unmodified.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="MaxVersion" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation>Maximal version to test. The version is compared against Kernel32.dll's product version. The property is set to 1 if Kernel32.dll's is not greater than the maximal version. Otherwise, the property is unmodified.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,5 +209,5 @@ After building a unit test project, you'll need to shutdown Visual Studio before
This is due to the unfortunate habit of Visual Studio to hold the extension file in use.
You may find it convenient to build unit test projects from a command prompt to workaround this limitation
~~~~~~~~~~~~
MSBuild UnitTests\WebsiteConfigUT\WebsiteConfigUT.wixproj /p:Configuration=Release /p:Platform=x86 /t:Rebuild "/p:SolutionDir=%CD%\\"
MSBuild UnitTests\IsWindowsVersionOrGreaterUT\IsWindowsVersionOrGreaterUT.wixproj /p:Configuration=Release /p:Platform=x86 /t:Rebuild "/p:SolutionDir=%CD%\\"
~~~~~~~~~~~~
2 changes: 1 addition & 1 deletion TidyBuild.custom.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<Import Project="$(MSBuildThisFileDirectory)TidyBuild.user.props" Condition="Exists('$(MSBuildThisFileDirectory)TidyBuild.user.props')"/>
<PropertyGroup>
<FullVersion>3.12.0</FullVersion>
<FullVersion>3.12.1</FullVersion>
<FullVersion Condition=" '$(GITHUB_RUN_NUMBER)'!='' ">$(FullVersion).$(GITHUB_RUN_NUMBER)</FullVersion>
<ProductName>PanelSwWixExtension</ProductName>
<Manufacturer>Panel::Software</Manufacturer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@
<Property Id="MSIFASTINSTALL" Value="1"></Property>

<Property Id="TEST_5_0">
<PanelSW:IsWindowsVersionOrGreater Version="5.0" />
<PanelSW:IsWindowsVersionOrGreater MinVersion="5.0" />
</Property>
<Property Id="TEST_10">
<PanelSW:IsWindowsVersionOrGreater Version="6.3.18362" />
<PanelSW:IsWindowsVersionOrGreater MinVersion="10.0.18362" MaxVersion="10.0.21999" />
</Property>
<Property Id="TEST_11">
<PanelSW:IsWindowsVersionOrGreater Version="6.3.22000" />
<PanelSW:IsWindowsVersionOrGreater MinVersion="10.0.22000" />
</Property>

<Condition Message="Surely we're no older than Windows 5.0"><![CDATA[Installed Or TEST_5_0]]></Condition>
<Condition Message="Surely we're no older than Windows 10.0"><![CDATA[Installed Or TEST_10]]></Condition>
<Condition Message="Surely we're no older than Windows 10.0"><![CDATA[Installed Or TEST_11 Or TEST_10]]></Condition>
<Condition Message="Surely we're no older than Windows 11.0"><![CDATA[Installed Or TEST_11]]></Condition>
<CustomActionRef Id="TerminateSuccessfully_Immediate"/>
</Product>
Expand Down

0 comments on commit ac6efc8

Please sign in to comment.