Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overwrite Extraction with Original Folder Structure #212

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
38 changes: 34 additions & 4 deletions src/LessMsi.Cli/ExtractCommand.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using LessMsi.Msi;
using NDesk.Options;

namespace LessMsi.Cli
Expand Down Expand Up @@ -38,16 +39,45 @@ private ExtractionMode getExtractionMode(string commandArgument)
commandArgument = commandArgument.ToLowerInvariant();
ExtractionMode extractionMode = ExtractionMode.PreserveDirectoriesExtraction;

if (commandArgument[commandArgument.Length - 1] == 'o')
if (isFlatExtractionRequired(commandArgument))
{
extractionMode = ExtractionMode.OverwriteFlatExtraction;
if (commandArgument[commandArgument.Length - 1] == 'o')
{
extractionMode = ExtractionMode.OverwriteFlatExtraction;
}
else if (commandArgument[commandArgument.Length - 1] == 'r')
{
extractionMode = ExtractionMode.RenameFlatExtraction;
}
}
else if (commandArgument[commandArgument.Length - 1] == 'r')
else
{
extractionMode = ExtractionMode.RenameFlatExtraction;
if (isRegularExtracionWithOverwriteRequired(commandArgument))
{
extractionMode = ExtractionMode.OverwriteExtraction;
}
}

return extractionMode;
}

private bool isFlatExtractionRequired(string commandArgument)
{
bool flatExtractionRequiredFlag = false;

if (commandArgument.Length > 1)
{
flatExtractionRequiredFlag = commandArgument[1] == 'f';
}

return flatExtractionRequiredFlag;
}

private bool isRegularExtracionWithOverwriteRequired(string commandArgument)
{
bool regularExtracionWithOverwriteFlag = commandArgument[commandArgument.Length - 1] == 'o';

return regularExtracionWithOverwriteFlag;
}
Comment on lines +42 to +81
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see mega5800#1 for a simplification here.

}
}
1 change: 0 additions & 1 deletion src/LessMsi.Cli/LessMsi.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
<Compile Include="..\CommonAssemblyInfo.cs">
<Link>Properties\CommonAssemblyInfo.cs</Link>
</Compile>
<Compile Include="ExtractionMode.cs" />
<Compile Include="ExtractCommand.cs" />
<Compile Include="LessMsiCommand.cs" />
<Compile Include="ListTableCommand.cs" />
Expand Down
9 changes: 4 additions & 5 deletions src/LessMsi.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@
// Scott Willeke ([email protected])
//
#region Using directives

using System;
using System.Collections.Generic;
using System.IO;
using LessMsi.Msi;

#endregion

namespace LessMsi.Cli
Expand Down Expand Up @@ -63,6 +61,7 @@ public static int Main(string[] args)
var subcommands = new Dictionary<string, LessMsiCommand> {
{"o", new OpenGuiCommand()},
{"x", extractCommand},
{"xo", extractCommand},
{"xfo", extractCommand},
{"xfr", extractCommand},
{"/x", extractCommand},
Expand Down Expand Up @@ -107,7 +106,7 @@ public static int Main(string[] args)
/// <param name="msiFileName">The path of the specified MSI file.</param>
/// <param name="outDirName">The directory to extract to. If empty it will use the current directory.</param>
/// <param name="filesToExtract">The files to be extracted from the msi. If empty all files will be extracted.</param>
/// /// <param name="extractionMode">Enum value for files extraction without folder structure</param>
/// <param name="extractionMode">Enum value for files extraction without folder structure</param>
public static void DoExtraction(string msiFileName, string outDirName, List<string> filesToExtract, ExtractionMode extractionMode)
{
msiFileName = EnsureAbsolutePath(msiFileName);
Expand All @@ -127,7 +126,7 @@ public static void DoExtraction(string msiFileName, string outDirName, List<stri
if (isExtractionModeFlat(extractionMode))
{
string tempOutDirName = $"{outDirName}{TempFolderSuffix}";
Wixtracts.ExtractFiles(msiFile, tempOutDirName, filesToExtract.ToArray(), PrintProgress);
Wixtracts.ExtractFiles(msiFile, tempOutDirName, filesToExtract.ToArray(), PrintProgress, extractionMode);

var fileNameCountingDict = new Dictionary<string, int>();

Expand All @@ -138,7 +137,7 @@ public static void DoExtraction(string msiFileName, string outDirName, List<stri
}
else
{
Wixtracts.ExtractFiles(msiFile, outDirName, filesToExtract.ToArray(), PrintProgress);
Wixtracts.ExtractFiles(msiFile, outDirName, filesToExtract.ToArray(), PrintProgress, extractionMode);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/LessMsi.Core/LessMsi.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<ItemGroup>
<Compile Include="Msi\ColumnInfo.cs" />
<Compile Include="Msi\ExternalCabNotFoundException.cs" />
<Compile Include="Msi\ExtractionMode.cs" />
<Compile Include="Msi\MsiDatabase.cs" />
<Compile Include="Msi\MsiDirectory.cs" />
<Compile Include="Msi\MsiFile.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
๏ปฟnamespace LessMsi.Cli
๏ปฟnamespace LessMsi.Msi
{
public enum ExtractionMode
{
/// <summary>
/// Default value indicating that no extraction should be performed.
/// Default value indicating that a regular extraction should be performed.
/// </summary>
None,
Default,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: Changing this would be a breaking change to anyone using the library. I don't think that's a problem since this is the LessMsi.Cli library and to my knowledge only people depend on the Lessmsi.Core library. I am okay making this change, just want to point it out.

/// <summary>
/// Value indicating that a file extraction preserving directories should be performed.
/// </summary>
Expand All @@ -17,6 +17,11 @@ public enum ExtractionMode
/// <summary>
/// Value indicating that a file extraction overwriting identical files should be performed.
/// </summary>
OverwriteFlatExtraction
OverwriteFlatExtraction,
/// <summary>
/// Value indicating that a file extraction overwriting identical files should be performed.
/// While preserving the directories structures
/// </summary>
OverwriteExtraction
}
}
43 changes: 31 additions & 12 deletions src/LessMsi.Core/Msi/Wixtracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,19 +205,19 @@ public enum ExtractionActivity

public static void ExtractFiles(Path msi, string outputDir)
{
ExtractFiles(msi, outputDir, new string[0], null);
ExtractFiles(msi, outputDir, new string[0], null, ExtractionMode.Default);
}

public static void ExtractFiles(Path msi, string outputDir, string[] fileNamesToExtract)
{
var msiFiles = GetMsiFileFromFileNames(msi, fileNamesToExtract);
ExtractFiles(msi, outputDir, msiFiles, null);
ExtractFiles(msi, outputDir, msiFiles, null, ExtractionMode.Default);
}

public static void ExtractFiles(Path msi, string outputDir, string[] fileNamesToExtract, AsyncCallback progressCallback)
public static void ExtractFiles(Path msi, string outputDir, string[] fileNamesToExtract, AsyncCallback progressCallback, ExtractionMode extractionMode)
{
var msiFiles = GetMsiFileFromFileNames(msi, fileNamesToExtract);
ExtractFiles(msi, outputDir, msiFiles, progressCallback);
ExtractFiles(msi, outputDir, msiFiles, progressCallback, extractionMode);
}

private static MsiFile[] GetMsiFileFromFileNames(Path msi, string[] fileNamesToExtract)
Expand Down Expand Up @@ -256,13 +256,19 @@ int IComparer.Compare(object x, object y)
}
}

/// <summary>
/// <summary>
/// Extracts the compressed files from the specified MSI file to the specified output directory.
/// If specified, the list of <paramref name="filesToExtract"/> objects are the only files extracted.
/// </summary>
/// <param name="filesToExtract">The files to extract or null or empty to extract all files.</param>
/// <param name="progressCallback">Will be called during during the operation with progress information, and upon completion. The argument will be of type <see cref="ExtractionProgress"/>.</param>
public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExtract, AsyncCallback progressCallback)
/// <param name="extractionMode">Enum value for files extraction without folder structure</param>
public static void ExtractFiles(
Path msi,
string outputDir,
MsiFile[] filesToExtract,
AsyncCallback progressCallback,
ExtractionMode extractionMode)
{
if (msi.IsEmpty)
throw new ArgumentNullException("msi");
Expand All @@ -289,7 +295,8 @@ public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExt

progress.ReportProgress(ExtractionActivity.Initializing, "", filesExtractedSoFar);
var outputDirPath = new Path(outputDir);
if (!FileSystem.Exists(outputDirPath)) {
if (!FileSystem.Exists(outputDirPath))
{
FileSystem.CreateDirectory(outputDirPath);
}

Expand Down Expand Up @@ -326,9 +333,10 @@ public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExt
progress.ReportProgress(ExtractionActivity.ExtractingFile, entry.LongFileName, filesExtractedSoFar);
string targetDirectoryForFile = GetTargetDirectory(outputDir, entry.Directory);
LessIO.Path destName = LessIO.Path.Combine(targetDirectoryForFile, entry.LongFileName);
if (FileSystem.Exists(destName))
if (IsExtractionModeAllowsDuplicates(extractionMode) && FileSystem.Exists(destName))
{
Debug.Fail(string.Format("output file '{0}' already exists. We'll make it unique, but this is probably a strange msi or a bug in this program.", destName));

//make unique
// ReSharper disable HeuristicUnreachableCode
Trace.WriteLine(string.Concat("Duplicate file found \'", destName, "\'"));
Expand Down Expand Up @@ -371,11 +379,22 @@ public static void ExtractFiles(Path msi, string outputDir, MsiFile[] filesToExt
progress.ReportProgress(ExtractionActivity.Complete, "", filesExtractedSoFar);
}
}
/// <summary>
/// Checks if given extraction mode value allows duplicate files during extraction
/// </summary>
/// <param name="extractionMode"></param>
/// <returns>Boolean flag for using duplicate files</returns>
private static bool IsExtractionModeAllowsDuplicates(ExtractionMode extractionMode)
{
bool duplicateFilesFlag = extractionMode != ExtractionMode.OverwriteExtraction;

/// <summary>
/// Deletes a file even if it is readonly.
/// </summary>
private static void DeleteFileForcefully(Path localFilePath)
return duplicateFilesFlag;
}

/// <summary>
/// Deletes a file even if it is readonly.
/// </summary>
private static void DeleteFileForcefully(Path localFilePath)
{
// In github issue #4 found that the cab files in the Win7SDK have the readonly attribute set and File.Delete fails to delete them. Explicitly unsetting that bit before deleting works okay...
FileSystem.RemoveFile(localFilePath, true);
Expand Down
3 changes: 2 additions & 1 deletion src/LessMsi.Gui/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,8 @@ private void btnExtract_Click(object sender, EventArgs e)

var filesToExtract = selectedFiles.ToArray();
Wixtracts.ExtractFiles(msiFile, outputDir, filesToExtract,
new AsyncCallback(progressDialog.UpdateProgress));
new AsyncCallback(progressDialog.UpdateProgress),
ExtractionMode.Default);
}
catch (Exception err)
{
Expand Down
22 changes: 22 additions & 0 deletions src/Lessmsi.Tests/CommandLineExtractTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ public void FlatRenameExtract1Arg()
TestExtraction(commandLine, GetTestName(), "NUnit-2.5.2.9222", false, flatExtractionFlag: true);
}

[Fact]
public void OverwriteExtract1Arg()
{
var commandLine = "xo TestFiles\\MsiInput\\AppleMobileDeviceSupport64.msi";
// setting "AppleMobileDeviceSupport64.msi" as actualEntriesOutputDir value, since no other output dir specified in command line text
TestExtraction(commandLine, GetTestName(), "AppleMobileDeviceSupport64", false);
}

[Fact]
public void Extract2Args()
{
Expand Down Expand Up @@ -68,6 +76,13 @@ public void FlatRenameExtract2Args()
TestExtraction(commandLine, GetTestName(), "FlatRenameExtract2Args", false, flatExtractionFlag: true);
}

[Fact]
public void OverwriteExtract2Args()
{
var commandLine = "xo TestFiles\\MsiInput\\AppleMobileDeviceSupport64.msi OverwriteExtract2Args\\";
TestExtraction(commandLine, GetTestName(), "OverwriteExtract2Args", false);
}

[Fact]
public void Extract3Args()
{
Expand Down Expand Up @@ -96,6 +111,13 @@ public void FlatRenameExtract3Args()
TestExtraction(commandLine, GetTestName(), "FlatRenameExtract3Args", false, flatExtractionFlag: true);
}

[Fact]
public void OverwriteExtract3Args()
{
var commandLine = "xo TestFiles\\MsiInput\\AppleMobileDeviceSupport64.msi OverwriteExtract3Args\\ \"api-ms-win-core-file-l1-1-0.dll\" \"api-ms-win-core-file-l1-1-0.dll.duplicate1\"";
TestExtraction(commandLine, GetTestName(), "OverwriteExtract3Args", false);
}

[Fact]
public void ExtractCompatibility1Arg()
{
Expand Down
Loading