Skip to content

Commit

Permalink
Merge branch '78_no_streams_table'
Browse files Browse the repository at this point in the history
  • Loading branch information
activescott committed Mar 13, 2017
2 parents d3d8106 + f32ca21 commit 035e247
Show file tree
Hide file tree
Showing 18 changed files with 751 additions and 58 deletions.
3 changes: 3 additions & 0 deletions src/LessMsi.Cli/LessMsi.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
Expand Down Expand Up @@ -71,6 +73,7 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
Expand Down
9 changes: 9 additions & 0 deletions src/LessMsi.Core/LessMsi.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
Expand Down Expand Up @@ -43,6 +45,8 @@
<HintPath>..\packages\libmspack4n.0.8.0\lib\net40\libmspackn.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
<Reference Include="wix">
<HintPath>..\..\lib\wix.dll</HintPath>
</Reference>
Expand All @@ -56,14 +60,18 @@
<Compile Include="Msi\TableWrapper.cs" />
<Compile Include="Msi\ViewWrapper.cs" />
<Compile Include="Msi\Wixtracts.cs" />
<Compile Include="OleStorage\NativeMethods.cs" />
<Compile Include="OleStorage\OleStorageFile.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\CommonAssemblyInfo.cs">
<Link>Properties\CommonAssemblyInfo.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="OleStorage\README.md" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Target Name="BeforeBuild">
<!-- Copy over the dlls that lessmsi.exe is depdent upon -->
Expand All @@ -74,4 +82,5 @@
</Target>
<Target Name="AfterBuild">
</Target>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</Project>
9 changes: 8 additions & 1 deletion src/LessMsi.Core/Msi/MsiDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
// Authors:
// Scott Willeke ([email protected])
//

using System;
using System.Collections;
using System.IO;
using Microsoft.Tools.WindowsInstallerXml.Msi;
Expand Down Expand Up @@ -196,7 +198,12 @@ The directory names in this column may be formatted as short filename | long fil
ArrayList rootDirectoriesList = new ArrayList();
foreach (MsiDirectory dir in directoriesByDirID.Values)
{
if (dir.DirectoryParent == null || dir.DirectoryParent.Length == 0)
// If the value of the Directory_Parent column is null...
var isRoot = string.IsNullOrEmpty(dir.DirectoryParent);
// ...or is equal to the Directory column, the DefaultDir column specifies the name of a root source directory. - https://msdn.microsoft.com/en-us/library/windows/desktop/aa368295(v=vs.85).aspx
if (!isRoot && string.Equals(dir.Directory, dir.DirectoryParent, StringComparison.InvariantCulture))
isRoot = true;
if (isRoot)
{
rootDirectoriesList.Add(dir);
continue;
Expand Down
117 changes: 94 additions & 23 deletions src/LessMsi.Core/Msi/Wixtracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Threading;
using LessMsi.OleStorage;
using LibMSPackN;
using Microsoft.Tools.WindowsInstallerXml.Msi;
using LessIO;
using Path = LessIO.Path;

namespace LessMsi.Msi
{
Expand All @@ -44,9 +49,9 @@ public class ExtractionProgress : IAsyncResult
{
private string _currentFileName;
private ExtractionActivity _activity;
private ManualResetEvent _waitSignal;
private AsyncCallback _callback;
private int _totalFileCount;
private readonly ManualResetEvent _waitSignal;
private readonly AsyncCallback _callback;
private readonly int _totalFileCount;
private int _filesExtracted;

public ExtractionProgress(AsyncCallback progressCallback, int totalFileCount)
Expand Down Expand Up @@ -259,7 +264,7 @@ public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExt
throw new ArgumentNullException("outputDir");

int filesExtractedSoFar = 0;

//Refrence on Embedding files: https://msdn.microsoft.com/en-us/library/aa369279.aspx
ExtractionProgress progress = null;
Database msidb = new Database(msi.PathString, OpenDatabase.ReadOnly);
try
Expand Down Expand Up @@ -467,7 +472,7 @@ private static List<CabInfo> CabsFromMsiToDisk(Path msi, Database msidb, string
if (extract)
{
// extract cabinet, then explode all of the files to a temp directory
ExtractCabFromPackage(localCabFile, cabSourceName, msidb);
ExtractCabFromPackage(localCabFile, cabSourceName, msidb, msi);
}
else
{
Expand Down Expand Up @@ -504,32 +509,98 @@ public CabInfo(string localCabFile, string cabSourceName)
}
}

/// <summary>
/// Write the Cab to disk.
/// </summary>
/// <param name="filePath">Specifies the path to the file to contain the stream.</param>
/// <param name="cabName">Specifies the name of the file in the stream.</param>
public static void ExtractCabFromPackage(Path filePath, string cabName, Database inputDatabase)
public static void ExtractCabFromPackage(Path destCabPath, string cabName, Database inputDatabase, LessIO.Path msiPath)
{
//NOTE: checking inputDatabase.TableExists("_Streams") here is not accurate. It reports that it doesn't exist at times when it is perfectly queryable. So we actually try it and look for a specific exception:
//NOTE: we do want to tryStreams. It is more reliable when available and AFAICT it always /should/ be there according to the docs but isn't.
const bool tryStreams = true;
if (tryStreams)
{
try
{
ExtractCabFromPackageTraditionalWay(destCabPath, cabName, inputDatabase);
// as long as TraditionalWay didn't throw, we'll leave it at that...
return;
}
catch (Exception e)
{
Debug.WriteLine("ExtractCabFromPackageTraditionalWay Exception: {0}", e);
// According to issue #78 (https://github.com/activescott/lessmsi/issues/78), WIX installers sometimes (always?)
// don't have _Streams table yet they still install. Since it appears that msi files generally (BUT NOT ALWAYS - see X86 Debuggers And Tools-x86_en-us.msi) will have only one cab file, we'll try to just find it in the sterams and use it instead:
Trace.WriteLine("MSI File has no _Streams table. Attempting alternate cab file extraction process...");
}
}

using (var stg = new OleStorageFile(msiPath))
{
// MSIs do exist with >1. If we use the ExtractCabFromPackageTraditionalWay (via _Streams table) then it handles that. If we are using this fallback approach, multiple cabs is a bad sign!
Debug.Assert(CountCabs(stg) == 1, string.Format("Expected 1 cab, but found {0}.", CountCabs(stg)));
foreach (var strm in stg.GetStreams())
{
using (var bits = strm.GetStream(FileMode.Open, FileAccess.Read))
{
if (OleStorageFile.IsCabStream(bits))
{
Trace.WriteLine(String.Format("Found CAB bits in stream. Assuming it is for cab {0}.", destCabPath));
Func<byte[], int> streamReader = destBuffer => bits.Read(destBuffer, 0, destBuffer.Length);
CopyStreamToFile(streamReader, destCabPath);
}
}
}
}
}

private static int CountCabs(OleStorageFile stg)
{
return stg.GetStreams().Count(strm => OleStorageFile.IsCabStream((StreamInfo) strm));
}

/// <summary>
/// Write the Cab to disk.
/// </summary>
/// <param name="destCabPath">Specifies the path to the file to contain the stream.</param>
/// <param name="cabName">Specifies the name of the file in the stream.</param>
/// <param name="inputDatabase">The MSI database to get cabs from.</param>
public static void ExtractCabFromPackageTraditionalWay(Path destCabPath, string cabName, Database inputDatabase)
{
using (View view = inputDatabase.OpenExecuteView(String.Concat("SELECT * FROM `_Streams` WHERE `Name` = '", cabName, "'")))
{
Record record;
if (view.Fetch(out record))
{
using (var writer = new System.IO.BinaryWriter(FileSystem.CreateFile(filePath)))
{
var buf = new byte[1024*1024];
int count;
do
{
const int MsiInterop_Storages_Data = 2; //From wiX:Index to column name Data into Record for row in Msi Table Storages
count = record.GetStream(MsiInterop_Storages_Data, buf, buf.Length);
if (count > 0)
writer.Write(buf, 0, count);
} while (count > 0);
}
Func<byte[], int> streamReader = destBuffer =>
{
const int msiInteropStoragesData = 2; //From wiX:Index to column name Data into Record for row in Msi Table Storages
var bytesWritten = record.GetStream(msiInteropStoragesData, destBuffer, destBuffer.Length);
return bytesWritten;
};
CopyStreamToFile(streamReader, destCabPath);
}
}
}

/// <summary>
/// Copies the Stream of bytes from the specified streamReader to the specified destination path.
/// </summary>
/// <param name="streamReader">
/// A callback like this:
/// int StreamReader(byte[] destBuffer)
/// The function should put bytes into the destBuffer and return the number of bytes written to the buffer.
/// </param>
/// <param name="destFile">The file to write the sreamReader's bits to.</param>
private static void CopyStreamToFile(Func<byte[], int> streamReader, Path destFile)
{
using (var writer = new BinaryWriter(FileSystem.CreateFile(destFile)))
{
var buf = new byte[1024 * 1024];
int bytesWritten;
do
{
bytesWritten = streamReader(buf);
if (bytesWritten > 0)
writer.Write(buf, 0, bytesWritten);
} while (bytesWritten > 0);
}
}
}
}
35 changes: 35 additions & 0 deletions src/LessMsi.Core/OleStorage/NativeMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2017 Scott Willeke (http://scott.willeke.com)
//
// Authors:
// Scott Willeke ([email protected])
//
using System.Runtime.InteropServices;

namespace LessMsi.OleStorage
{
internal static class NativeMethods
{
[DllImport("ole32.dll")]
internal static extern int StgIsStorageFile([MarshalAs(UnmanagedType.LPWStr)]string pwcsName);

}
}
Loading

0 comments on commit 035e247

Please sign in to comment.