Skip to content

Commit

Permalink
Merge pull request #161 from learn-more/fix_filetime
Browse files Browse the repository at this point in the history
fix: Properly display FILETIME in the summary view
  • Loading branch information
activescott authored Jan 14, 2021
2 parents c911de9 + 658bf76 commit d535045
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 56 deletions.
12 changes: 12 additions & 0 deletions src/LessMsi.Gui/Extensions/MsiNativeMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace LessMsi.Gui.Extensions
{
internal static class MsiNativeMethods
{
[DllImport("msi.dll", CharSet = CharSet.Unicode, EntryPoint = "MsiSummaryInfoGetPropertyW", ExactSpelling = true)]
internal static extern uint MsiSummaryInfoGetProperty(IntPtr summaryInfo, int property, out uint dataType, out int integerValue, ref System.Runtime.InteropServices.ComTypes.FILETIME fileTimeValue, StringBuilder stringValueBuf, ref int stringValueBufSize);
}
}
39 changes: 39 additions & 0 deletions src/LessMsi.Gui/Extensions/SummaryInformationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.Tools.WindowsInstallerXml.Msi;
using System;
using System.Text;

namespace LessMsi.Gui.Extensions
{
public static class SummaryInformationExtensions
{
/// <summary>
/// There is a bug in Wix where it does not correctly return data with the FILETIME type.
/// This extension method manually retrieves the value, and converts it to a DateTime.
/// </summary>
public static object GetPropertyFileTime(this SummaryInformation summaryInfo, int index)
{
uint iDataType;
int integerValue, stringValueBufSize = 0;
System.Runtime.InteropServices.ComTypes.FILETIME fileTimeValue = new System.Runtime.InteropServices.ComTypes.FILETIME();
StringBuilder stringValueBuf = new StringBuilder();

uint result = MsiNativeMethods.MsiSummaryInfoGetProperty(summaryInfo.InternalHandle, index,
out iDataType, out integerValue, ref fileTimeValue, stringValueBuf, ref stringValueBufSize);

if (result != 0)
{
throw new ArgumentNullException();
}

switch ((Model.VT)iDataType)
{
case Model.VT.EMPTY:
return string.Empty;
case Model.VT.FILETIME:
return DateTime.FromFileTime((((long)fileTimeValue.dwHighDateTime) << 32) | ((uint)fileTimeValue.dwLowDateTime));
default:
throw new ArgumentNullException();
}
}
}
}
2 changes: 2 additions & 0 deletions src/LessMsi.Gui/LessMsi.Gui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
<Compile Include="AboutBox.Designer.cs">
<DependentUpon>AboutBox.cs</DependentUpon>
</Compile>
<Compile Include="Extensions\SummaryInformationExtensions.cs" />
<Compile Include="ExtractionProgressDialog.cs">
<SubType>Form</SubType>
</Compile>
Expand All @@ -74,6 +75,7 @@
</Compile>
<Compile Include="MainFormPresenter.cs" />
<Compile Include="Model\MsiFileItemView.cs" />
<Compile Include="Extensions\MsiNativeMethods.cs" />
<Compile Include="Model\MsiPropertyInfo.cs" />
<Compile Include="Model\CabContainedFileView.cs" />
<Compile Include="Model\StreamInfoView.cs" />
Expand Down
105 changes: 49 additions & 56 deletions src/LessMsi.Gui/Model/MsiPropertyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@
// Authors:
// Scott Willeke (scott@willeke.com)
//
using System;
using System.Collections;
using LessMsi.Gui.Extensions;
using Microsoft.Tools.WindowsInstallerXml.Msi;
using System;
using System.Collections.Generic;

namespace LessMsi.Gui.Model
{
internal class MsiPropertyInfo
{
private readonly VT _propertyType;

#region msdn doc

//http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/summary_information_stream_property_set.asp
Expand All @@ -56,72 +55,78 @@ internal class MsiPropertyInfo
#endregion

private static readonly MsiPropertyInfo[] DefaultMsiPropertySet = new MsiPropertyInfo[]
{
new MsiPropertyInfo("Codepage", 1, VT.I2, "The ANSI code page used for any strings that are stored in the summary information. Note that this is not the same code page for strings in the installation database. The Codepage Summary property is used to translate the strings in the summary information into Unicode when calling the Unicode API functions."),
new MsiPropertyInfo("Title", 2, VT.LPSTR, "Breifly describes the installer."),
new MsiPropertyInfo("Subject", 3, VT.LPSTR, "Describes what can be installed using the installer."),
new MsiPropertyInfo("Author", 4, VT.LPSTR, "The manufacturer of the installer"),
new MsiPropertyInfo("Keywords", 5, VT.LPSTR, "Keywords that permit the database file to be found in a keyword search"),
new MsiPropertyInfo("Comments", 6, VT.LPSTR, "Used to describe the general purpose of the installer."),
new MsiPropertyInfo("Template", 7, VT.LPSTR, "The platform and languages supported by the installer (syntax:[platform property][,platform property][,...];[language id][,language id][,...].)."),
new MsiPropertyInfo("Last Saved By ", 8, VT.LPSTR, "The installer sets the Last Saved by Summary Property to the value of the LogonUser property during an administrative installation."),
new MsiPropertyInfo("Revision Number ", 9, VT.LPSTR, "Unique identifier of the installer package. In patch packages authored for Windows Installer version 2.0 this can be followed by a list of patch code GUIDs for obsolete patches that are removed when this patch is applied. The patch codes are concatenated with no delimiters separating GUIDs in the list. Windows Installer version 3.0 can install these earlier package versions and remove the obsolete patches. Windows Installer version 3.0 ignores the list of obsolete patches in the Revision Number Summary property if there is sequencing information present in the MsiPatchSequence table."),
new MsiPropertyInfo("Last Printed", 11, VT.FILETIME, "The date and time during an administrative installation to record when the administrative image was created. For non-administrative installations this property is the same as the Create Time/Date Summary property."),
new MsiPropertyInfo("Create Time/Date", 12, VT.FILETIME, "When the installer database was created."),
new MsiPropertyInfo("Last Save Time/Date", 13, VT.FILETIME, "When the last time the installer database was modified. Each time a user changes an installation the value for this summary property is updated to the current system time/date at the time the installer database was saved. Initially the value for this summary property is set to null to indicate that no changes have yet been made."),
new MsiPropertyInfo("Page Count", 14, VT.I4, "The minimum installer version required. \r\nFor Windows Installer version 1.0, this property must be set to the integer 100. For 64-bit Windows Installer Packages, this property must be set to the integer 200. For a transform package, the Page Count Summary property contains minimum installer version required to process the transform. Set to the greater of the two Page Count Summary property values belonging to the databases used to generate the transform. Set to Null in patch packages.", "0x{0:x}"),
new MsiPropertyInfo("Word Count", 15, VT.I4, "Indicates the type of source file image.", "0x{0:x}"),
new MsiPropertyInfo("Character Count", 16, VT.I4, "Two 16-bit words. The upper word contains the \"transform validation flags\" (used to verify that a transform can be applied to the database). The lower word contains the \"transform error condition flags\" (used to flag the error conditions of a transform)."),
new MsiPropertyInfo("Creating Application", 18, VT.LPSTR, "The software used to author the installation."),
new MsiPropertyInfo("Security", 19, VT.I4, "Indicates if the package should be opened as read-only:\r\n 0: No restriction \r\n2:Read-only recommended\r\n 4: Read-only enforced")
};
{
new MsiPropertyInfo("Codepage", 1, VT.I2, "The ANSI code page used for any strings that are stored in the summary information. Note that this is not the same code page for strings in the installation database. The Codepage Summary property is used to translate the strings in the summary information into Unicode when calling the Unicode API functions."),
new MsiPropertyInfo("Title", 2, VT.LPSTR, "Breifly describes the installer."),
new MsiPropertyInfo("Subject", 3, VT.LPSTR, "Describes what can be installed using the installer."),
new MsiPropertyInfo("Author", 4, VT.LPSTR, "The manufacturer of the installer"),
new MsiPropertyInfo("Keywords", 5, VT.LPSTR, "Keywords that permit the database file to be found in a keyword search"),
new MsiPropertyInfo("Comments", 6, VT.LPSTR, "Used to describe the general purpose of the installer."),
new MsiPropertyInfo("Template", 7, VT.LPSTR, "The platform and languages supported by the installer (syntax:[platform property][,platform property][,...];[language id][,language id][,...].)."),
new MsiPropertyInfo("Last Saved By ", 8, VT.LPSTR, "The installer sets the Last Saved by Summary Property to the value of the LogonUser property during an administrative installation."),
new MsiPropertyInfo("Revision Number ", 9, VT.LPSTR, "Unique identifier of the installer package. In patch packages authored for Windows Installer version 2.0 this can be followed by a list of patch code GUIDs for obsolete patches that are removed when this patch is applied. The patch codes are concatenated with no delimiters separating GUIDs in the list. Windows Installer version 3.0 can install these earlier package versions and remove the obsolete patches. Windows Installer version 3.0 ignores the list of obsolete patches in the Revision Number Summary property if there is sequencing information present in the MsiPatchSequence table."),
new MsiPropertyInfo("Last Printed", 11, VT.FILETIME, "The date and time during an administrative installation to record when the administrative image was created. For non-administrative installations this property is the same as the Create Time/Date Summary property."),
new MsiPropertyInfo("Create Time/Date", 12, VT.FILETIME, "When the installer database was created."),
new MsiPropertyInfo("Last Save Time/Date", 13, VT.FILETIME, "When the last time the installer database was modified. Each time a user changes an installation the value for this summary property is updated to the current system time/date at the time the installer database was saved. Initially the value for this summary property is set to null to indicate that no changes have yet been made."),
new MsiPropertyInfo("Page Count", 14, VT.I4, "The minimum installer version required. \r\nFor Windows Installer version 1.0, this property must be set to the integer 100. For 64-bit Windows Installer Packages, this property must be set to the integer 200. For a transform package, the Page Count Summary property contains minimum installer version required to process the transform. Set to the greater of the two Page Count Summary property values belonging to the databases used to generate the transform. Set to Null in patch packages.", "0x{0:x}"),
new MsiPropertyInfo("Word Count", 15, VT.I4, "Indicates the type of source file image.", "0x{0:x}"),
new MsiPropertyInfo("Character Count", 16, VT.I4, "Two 16-bit words. The upper word contains the \"transform validation flags\" (used to verify that a transform can be applied to the database). The lower word contains the \"transform error condition flags\" (used to flag the error conditions of a transform)."),
new MsiPropertyInfo("Creating Application", 18, VT.LPSTR, "The software used to author the installation."),
new MsiPropertyInfo("Security", 19, VT.I4, "Indicates if the package should be opened as read-only:\r\n 0: No restriction \r\n2:Read-only recommended\r\n 4: Read-only enforced")
};

private readonly string _valueFormatString = "{0}";

internal static MsiPropertyInfo[] GetPropertiesFromDatabase(Database msidb)
{
int[] standardIDs = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 19};

ArrayList properties = new ArrayList();
List<MsiPropertyInfo> properties = new List<MsiPropertyInfo>();
using (SummaryInformation summaryInfo = new SummaryInformation(msidb))
{
foreach (int propID in standardIDs)
foreach (MsiPropertyInfo prototype in DefaultMsiPropertySet)
{
bool failed = false;
object propValue = null;
try
{
propValue = summaryInfo.GetProperty(propID);
// Wix has a bug when translating a FILETIME, so we do it manually
if (prototype.PropertyType == VT.FILETIME)
{
propValue = summaryInfo.GetPropertyFileTime(prototype.ID);
}
else
{
propValue = summaryInfo.GetProperty(prototype.ID);
}
}
catch
{
failed = true;
}
if (!failed)
properties.Add(new MsiPropertyInfo(propID, propValue));
if (!failed)
{
properties.Add(new MsiPropertyInfo(prototype, propValue));
}
}
}
return (MsiPropertyInfo[]) properties.ToArray(typeof (MsiPropertyInfo));
return properties.ToArray();
}

private MsiPropertyInfo(int id, object value)
private MsiPropertyInfo(MsiPropertyInfo prototype, object value)
{
ID = id;
ID = prototype.ID;
Value = value;

MsiPropertyInfo prototype = GetPropertyInfoByID(id);
if (prototype != null)
{
Name = prototype.Name;
_propertyType = prototype._propertyType;
PropertyType = prototype.PropertyType;
Description = prototype.Description;
_valueFormatString = prototype._valueFormatString;
switch(_propertyType)
switch(PropertyType)
{
case VT.FILETIME:
//everything is coming from wix as a string, need to submit patch to wix:
// _value = DateTime.FromFileTime((long)_value);
break;
// Nothing to do here, we already converted it
break;
case VT.I2:
case VT.I4:
if (Value is string && Value != null && ((string)Value).Length > 0)
Expand All @@ -135,12 +140,11 @@ private MsiPropertyInfo(int id, object value)
}
break;
}

}
else
{
Name = "non-standard";
_propertyType = VT.EMPTY;
PropertyType = VT.EMPTY;
Description = "Unknown.";
}
}
Expand All @@ -149,29 +153,18 @@ private MsiPropertyInfo(string name, int pid, VT propertyType, string descriptio
{
Name = name;
ID = pid;
_propertyType = propertyType;
PropertyType = propertyType;
Description = description;
Value = null;
_valueFormatString = valueFormatString;
}

/// <summary>
/// Returns a <see cref="MsiPropertyInfo"/> with the specified <see cref="MsiPropertyInfo.ID"/> or null if the ID is unknown.
/// </summary>
public static MsiPropertyInfo GetPropertyInfoByID(int id)
{
foreach (MsiPropertyInfo info in DefaultMsiPropertySet)
{
if (info.ID == id)
return info;
}
return null;
}

public string Name { get; private set; }

public int ID { get; private set; }

public VT PropertyType { get; private set; }

public string Description { get; private set; }

public object Value { get; private set; }
Expand Down Expand Up @@ -200,4 +193,4 @@ internal enum VT : uint
LPSTR = 30,
FILETIME = 0x40,
}
}
}

0 comments on commit d535045

Please sign in to comment.