Skip to content

Commit d535045

Browse files
authored
Merge pull request #161 from learn-more/fix_filetime
fix: Properly display FILETIME in the summary view
2 parents c911de9 + 658bf76 commit d535045

File tree

4 files changed

+102
-56
lines changed

4 files changed

+102
-56
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using System.Text;
4+
5+
namespace LessMsi.Gui.Extensions
6+
{
7+
internal static class MsiNativeMethods
8+
{
9+
[DllImport("msi.dll", CharSet = CharSet.Unicode, EntryPoint = "MsiSummaryInfoGetPropertyW", ExactSpelling = true)]
10+
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);
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Microsoft.Tools.WindowsInstallerXml.Msi;
2+
using System;
3+
using System.Text;
4+
5+
namespace LessMsi.Gui.Extensions
6+
{
7+
public static class SummaryInformationExtensions
8+
{
9+
/// <summary>
10+
/// There is a bug in Wix where it does not correctly return data with the FILETIME type.
11+
/// This extension method manually retrieves the value, and converts it to a DateTime.
12+
/// </summary>
13+
public static object GetPropertyFileTime(this SummaryInformation summaryInfo, int index)
14+
{
15+
uint iDataType;
16+
int integerValue, stringValueBufSize = 0;
17+
System.Runtime.InteropServices.ComTypes.FILETIME fileTimeValue = new System.Runtime.InteropServices.ComTypes.FILETIME();
18+
StringBuilder stringValueBuf = new StringBuilder();
19+
20+
uint result = MsiNativeMethods.MsiSummaryInfoGetProperty(summaryInfo.InternalHandle, index,
21+
out iDataType, out integerValue, ref fileTimeValue, stringValueBuf, ref stringValueBufSize);
22+
23+
if (result != 0)
24+
{
25+
throw new ArgumentNullException();
26+
}
27+
28+
switch ((Model.VT)iDataType)
29+
{
30+
case Model.VT.EMPTY:
31+
return string.Empty;
32+
case Model.VT.FILETIME:
33+
return DateTime.FromFileTime((((long)fileTimeValue.dwHighDateTime) << 32) | ((uint)fileTimeValue.dwLowDateTime));
34+
default:
35+
throw new ArgumentNullException();
36+
}
37+
}
38+
}
39+
}

src/LessMsi.Gui/LessMsi.Gui.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
<Compile Include="AboutBox.Designer.cs">
6666
<DependentUpon>AboutBox.cs</DependentUpon>
6767
</Compile>
68+
<Compile Include="Extensions\SummaryInformationExtensions.cs" />
6869
<Compile Include="ExtractionProgressDialog.cs">
6970
<SubType>Form</SubType>
7071
</Compile>
@@ -74,6 +75,7 @@
7475
</Compile>
7576
<Compile Include="MainFormPresenter.cs" />
7677
<Compile Include="Model\MsiFileItemView.cs" />
78+
<Compile Include="Extensions\MsiNativeMethods.cs" />
7779
<Compile Include="Model\MsiPropertyInfo.cs" />
7880
<Compile Include="Model\CabContainedFileView.cs" />
7981
<Compile Include="Model\StreamInfoView.cs" />

src/LessMsi.Gui/Model/MsiPropertyInfo.cs

+49-56
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,15 @@
2222
// Authors:
2323
// Scott Willeke ([email protected])
2424
//
25-
using System;
26-
using System.Collections;
25+
using LessMsi.Gui.Extensions;
2726
using Microsoft.Tools.WindowsInstallerXml.Msi;
27+
using System;
28+
using System.Collections.Generic;
2829

2930
namespace LessMsi.Gui.Model
3031
{
3132
internal class MsiPropertyInfo
3233
{
33-
private readonly VT _propertyType;
34-
3534
#region msdn doc
3635

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

5857
private static readonly MsiPropertyInfo[] DefaultMsiPropertySet = new MsiPropertyInfo[]
59-
{
60-
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."),
61-
new MsiPropertyInfo("Title", 2, VT.LPSTR, "Breifly describes the installer."),
62-
new MsiPropertyInfo("Subject", 3, VT.LPSTR, "Describes what can be installed using the installer."),
63-
new MsiPropertyInfo("Author", 4, VT.LPSTR, "The manufacturer of the installer"),
64-
new MsiPropertyInfo("Keywords", 5, VT.LPSTR, "Keywords that permit the database file to be found in a keyword search"),
65-
new MsiPropertyInfo("Comments", 6, VT.LPSTR, "Used to describe the general purpose of the installer."),
66-
new MsiPropertyInfo("Template", 7, VT.LPSTR, "The platform and languages supported by the installer (syntax:[platform property][,platform property][,...];[language id][,language id][,...].)."),
67-
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."),
68-
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."),
69-
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."),
70-
new MsiPropertyInfo("Create Time/Date", 12, VT.FILETIME, "When the installer database was created."),
71-
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."),
72-
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}"),
73-
new MsiPropertyInfo("Word Count", 15, VT.I4, "Indicates the type of source file image.", "0x{0:x}"),
74-
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)."),
75-
new MsiPropertyInfo("Creating Application", 18, VT.LPSTR, "The software used to author the installation."),
76-
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")
77-
};
58+
{
59+
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."),
60+
new MsiPropertyInfo("Title", 2, VT.LPSTR, "Breifly describes the installer."),
61+
new MsiPropertyInfo("Subject", 3, VT.LPSTR, "Describes what can be installed using the installer."),
62+
new MsiPropertyInfo("Author", 4, VT.LPSTR, "The manufacturer of the installer"),
63+
new MsiPropertyInfo("Keywords", 5, VT.LPSTR, "Keywords that permit the database file to be found in a keyword search"),
64+
new MsiPropertyInfo("Comments", 6, VT.LPSTR, "Used to describe the general purpose of the installer."),
65+
new MsiPropertyInfo("Template", 7, VT.LPSTR, "The platform and languages supported by the installer (syntax:[platform property][,platform property][,...];[language id][,language id][,...].)."),
66+
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."),
67+
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."),
68+
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."),
69+
new MsiPropertyInfo("Create Time/Date", 12, VT.FILETIME, "When the installer database was created."),
70+
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."),
71+
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}"),
72+
new MsiPropertyInfo("Word Count", 15, VT.I4, "Indicates the type of source file image.", "0x{0:x}"),
73+
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)."),
74+
new MsiPropertyInfo("Creating Application", 18, VT.LPSTR, "The software used to author the installation."),
75+
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")
76+
};
7877

7978
private readonly string _valueFormatString = "{0}";
8079

8180
internal static MsiPropertyInfo[] GetPropertiesFromDatabase(Database msidb)
8281
{
83-
int[] standardIDs = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 19};
84-
85-
ArrayList properties = new ArrayList();
82+
List<MsiPropertyInfo> properties = new List<MsiPropertyInfo>();
8683
using (SummaryInformation summaryInfo = new SummaryInformation(msidb))
8784
{
88-
foreach (int propID in standardIDs)
85+
foreach (MsiPropertyInfo prototype in DefaultMsiPropertySet)
8986
{
9087
bool failed = false;
9188
object propValue = null;
9289
try
9390
{
94-
propValue = summaryInfo.GetProperty(propID);
91+
// Wix has a bug when translating a FILETIME, so we do it manually
92+
if (prototype.PropertyType == VT.FILETIME)
93+
{
94+
propValue = summaryInfo.GetPropertyFileTime(prototype.ID);
95+
}
96+
else
97+
{
98+
propValue = summaryInfo.GetProperty(prototype.ID);
99+
}
95100
}
96101
catch
97102
{
98103
failed = true;
99104
}
100-
if (!failed)
101-
properties.Add(new MsiPropertyInfo(propID, propValue));
105+
if (!failed)
106+
{
107+
properties.Add(new MsiPropertyInfo(prototype, propValue));
108+
}
102109
}
103110
}
104-
return (MsiPropertyInfo[]) properties.ToArray(typeof (MsiPropertyInfo));
111+
return properties.ToArray();
105112
}
106113

107-
private MsiPropertyInfo(int id, object value)
114+
private MsiPropertyInfo(MsiPropertyInfo prototype, object value)
108115
{
109-
ID = id;
116+
ID = prototype.ID;
110117
Value = value;
111118

112-
MsiPropertyInfo prototype = GetPropertyInfoByID(id);
113119
if (prototype != null)
114120
{
115121
Name = prototype.Name;
116-
_propertyType = prototype._propertyType;
122+
PropertyType = prototype.PropertyType;
117123
Description = prototype.Description;
118124
_valueFormatString = prototype._valueFormatString;
119-
switch(_propertyType)
125+
switch(PropertyType)
120126
{
121127
case VT.FILETIME:
122-
//everything is coming from wix as a string, need to submit patch to wix:
123-
// _value = DateTime.FromFileTime((long)_value);
124-
break;
128+
// Nothing to do here, we already converted it
129+
break;
125130
case VT.I2:
126131
case VT.I4:
127132
if (Value is string && Value != null && ((string)Value).Length > 0)
@@ -135,12 +140,11 @@ private MsiPropertyInfo(int id, object value)
135140
}
136141
break;
137142
}
138-
139143
}
140144
else
141145
{
142146
Name = "non-standard";
143-
_propertyType = VT.EMPTY;
147+
PropertyType = VT.EMPTY;
144148
Description = "Unknown.";
145149
}
146150
}
@@ -149,29 +153,18 @@ private MsiPropertyInfo(string name, int pid, VT propertyType, string descriptio
149153
{
150154
Name = name;
151155
ID = pid;
152-
_propertyType = propertyType;
156+
PropertyType = propertyType;
153157
Description = description;
154158
Value = null;
155159
_valueFormatString = valueFormatString;
156160
}
157161

158-
/// <summary>
159-
/// Returns a <see cref="MsiPropertyInfo"/> with the specified <see cref="MsiPropertyInfo.ID"/> or null if the ID is unknown.
160-
/// </summary>
161-
public static MsiPropertyInfo GetPropertyInfoByID(int id)
162-
{
163-
foreach (MsiPropertyInfo info in DefaultMsiPropertySet)
164-
{
165-
if (info.ID == id)
166-
return info;
167-
}
168-
return null;
169-
}
170-
171162
public string Name { get; private set; }
172163

173164
public int ID { get; private set; }
174165

166+
public VT PropertyType { get; private set; }
167+
175168
public string Description { get; private set; }
176169

177170
public object Value { get; private set; }
@@ -200,4 +193,4 @@ internal enum VT : uint
200193
LPSTR = 30,
201194
FILETIME = 0x40,
202195
}
203-
}
196+
}

0 commit comments

Comments
 (0)