Skip to content

Commit a93adbc

Browse files
committed
feat: Add basic support for .msi files appended to an executable
1 parent c5b475b commit a93adbc

File tree

6 files changed

+125
-7
lines changed

6 files changed

+125
-7
lines changed

src/LessMsi.Core/Msi/MsiDatabase.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
// Scott Willeke ([email protected])
2424
//
2525
using Microsoft.Tools.WindowsInstallerXml.Msi;
26+
using System;
27+
using System.IO;
28+
using System.IO.MemoryMappedFiles;
2629

2730
namespace LessMsi.Msi
2831
{
@@ -53,5 +56,82 @@ public static Database Create(LessIO.Path msiDatabaseFilePath)
5356
return new Database(msiDatabaseFilePath.PathString, OpenDatabase.ReadOnly | (OpenDatabase)MSIDBOPEN_PATCHFILE);
5457
}
5558
}
59+
60+
61+
static readonly Byte[] STORAGE_magic = new byte[]{ 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 };
62+
63+
public static bool TryDetectMsiHeader(LessIO.Path filePath, out long offset)
64+
{
65+
using (var mapped = MemoryMappedFile.CreateFromFile(filePath.PathString, System.IO.FileMode.Open, null, 0L, MemoryMappedFileAccess.ReadWrite))
66+
{
67+
using (var accessor = mapped.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read))
68+
{
69+
byte[] buffer = new byte[4096];
70+
long readOffset = 0;
71+
while (true)
72+
{
73+
var totalRemaining = accessor.Capacity - readOffset;
74+
if (totalRemaining <= 0)
75+
break;
76+
int remaining = accessor.ReadArray(readOffset, buffer, 0, (int)Math.Min(totalRemaining, buffer.Length));
77+
if (remaining <= 0)
78+
break;
79+
80+
bool moveLess = false;
81+
for (int n = 0; n < remaining; n++)
82+
{
83+
if (buffer[n] == STORAGE_magic[0])
84+
{
85+
if (buffer.Length - n >= STORAGE_magic.Length)
86+
{
87+
bool match = true;
88+
for (int m = 1; m < STORAGE_magic.Length; m++)
89+
{
90+
if (buffer[n + m] != STORAGE_magic[m])
91+
{
92+
match = false;
93+
break;
94+
}
95+
}
96+
if (match)
97+
{
98+
offset = readOffset + n;
99+
return true;
100+
}
101+
}
102+
else
103+
{
104+
moveLess = true;
105+
break;
106+
}
107+
}
108+
}
109+
110+
if (moveLess && remaining > STORAGE_magic.Length && readOffset > STORAGE_magic.Length)
111+
{
112+
readOffset -= STORAGE_magic.Length;
113+
}
114+
readOffset += remaining;
115+
}
116+
117+
offset = -1;
118+
return false;
119+
}
120+
}
121+
}
122+
123+
public static void ExtractMsiFromExe(LessIO.Path filePath, LessIO.Path outputFile, long offset)
124+
{
125+
using (var mapped = MemoryMappedFile.CreateFromFile(filePath.PathString, FileMode.Open, null, 0L, MemoryMappedFileAccess.ReadWrite))
126+
{
127+
using (var reader = mapped.CreateViewStream(offset, 0, MemoryMappedFileAccess.Read))
128+
{
129+
using (var output = File.Create(outputFile.PathString))
130+
{
131+
reader.CopyTo(output);
132+
}
133+
}
134+
}
135+
}
56136
}
57137
}

src/LessMsi.Gui/IMainFormView.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ internal interface IMainFormView
7777
/// Shows an error to the user.
7878
/// </summary>
7979
void ShowUserError(string formatStr, params object[] args);
80+
81+
bool ShowUserMessageQuestionYesNo(string message);
8082
/// <summary>
8183
/// Adds a column to the MSI table grid.
8284
/// </summary>

src/LessMsi.Gui/MainForm.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ internal class MainForm : Form, IMainFormView
5454
private Panel pnlStreamsBottom;
5555
private Button btnExtractStreamFiles;
5656
private ToolStripMenuItem searchFileToolStripMenuItem;
57-
readonly static string[] AllowedDragDropExtensions = new[] { ".msi", ".msp" };
57+
readonly static string[] AllowedDragDropExtensions = new[] { ".msi", ".msp", ".exe" };
5858

5959
public MainForm(string defaultInputFile)
6060
{
@@ -169,6 +169,12 @@ public void ShowUserError(string formatStr, params object[] args)
169169
ShowUserMessageBox(string.Format(CultureInfo.CurrentUICulture, formatStr, args));
170170
}
171171

172+
public bool ShowUserMessageQuestionYesNo(string message)
173+
{
174+
return MessageBox.Show(this, message, "LessMSI", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes;
175+
}
176+
177+
172178
#region MSI Table Grid Stuff
173179

174180
public void AddTableViewGridColumn(string headerText)

src/LessMsi.Gui/MainFormPresenter.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,20 @@
2222
// Authors:
2323
// Scott Willeke ([email protected])
2424
//
25-
using System;
26-
using System.Collections.Generic;
27-
using System.ComponentModel;
28-
using System.Diagnostics;
29-
using System.IO;
30-
using System.Linq;
3125
using LessIO;
3226
using LessMsi.Gui.Model;
3327
using LessMsi.Gui.Resources.Languages;
3428
using LessMsi.Gui.Windows.Forms;
3529
using LessMsi.Msi;
3630
using LessMsi.OleStorage;
3731
using Microsoft.Tools.WindowsInstallerXml.Msi;
32+
using System;
33+
using System.Collections.Generic;
34+
using System.ComponentModel;
35+
using System.Diagnostics;
36+
using System.IO;
37+
using System.IO.MemoryMappedFiles;
38+
using System.Linq;
3839

3940
namespace LessMsi.Gui
4041
{
@@ -545,6 +546,16 @@ public void LoadCurrentFile()
545546
}
546547
catch (Exception eCatchAll)
547548
{
549+
if (eCatchAll is IOException && this.SelectedMsiFile.Extension.ToLower() == ".exe" && MsiDatabase.TryDetectMsiHeader(new LessIO.Path(this.SelectedMsiFile.FullName), out long offset))
550+
{
551+
if (View.ShowUserMessageQuestionYesNo("It looks like this file contains a .msi file, do you want to extract it?"))
552+
{
553+
var tempFile = System.IO.Path.GetTempFileName();
554+
MsiDatabase.ExtractMsiFromExe(new LessIO.Path(this.SelectedMsiFile.FullName), new LessIO.Path(tempFile), offset);
555+
LoadFile(tempFile);
556+
return;
557+
}
558+
}
548559
isBadFile = true;
549560
Error(Strings.OpenFileError, eCatchAll);
550561
}

src/Lessmsi.Tests/MiscTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,25 @@ public void MissingStreamsTable()
139139
public void MissingParentDirectoryEntry()
140140
{
141141
ExtractAndCompareToMaster("IviNetSharedComponents32_Fx20_1.3.0.msi");
142+
}
143+
144+
/// <summary>
145+
/// From https://github.com/activescott/lessmsi/pull/237
146+
/// </summary>
147+
[Fact]
148+
public void TryDetectEmbeddedMsi()
149+
{
150+
bool containsMsi = Msi.MsiDatabase.TryDetectMsiHeader(GetMsiTestFile("vmware_tools_setup.exe"), out long offset);
151+
Assert.True(containsMsi);
152+
Assert.Equal(0x315E3C, offset);
153+
154+
containsMsi = Msi.MsiDatabase.TryDetectMsiHeader(GetMsiTestFile("IviNetSharedComponents32_Fx20_1.3.0.msi"), out offset);
155+
Assert.True(containsMsi);
156+
Assert.Equal(0, offset);
157+
158+
containsMsi = Msi.MsiDatabase.TryDetectMsiHeader(GetMsiTestFile("msi_with_external_cab.cab"), out offset);
159+
Assert.False(containsMsi);
160+
Assert.Equal(-1, offset);
142161
}
143162
}
144163
}
30.7 MB
Binary file not shown.

0 commit comments

Comments
 (0)