private static void ExplodeAndVerifyCab(string cabinetFilename, int fileCount) { Console.WriteLine("Files in " + cabinetFilename + ":"); bool errors = false; string outDir = OutDir(cabinetFilename); try { Assert.True(File.Exists(cabinetFilename)); using (var cab = new MSCabinet(cabinetFilename)) { ExplodeCab(cab, outDir); } } catch (Exception e) { errors = true; Console.WriteLine("ERROR! " + e.Message); } Assert.False(errors); Assert.True(Directory.Exists(outDir)); var outputFiles = Directory.GetFiles(outDir, "*", SearchOption.AllDirectories); Assert.Equal(fileCount, outputFiles.Count()); Directory.Delete(outDir, true); Console.WriteLine(""); Console.WriteLine(""); }
private static void ExplodeCab(MSCabinet cab, string outDir) { Directory.CreateDirectory(outDir); foreach (var file in cab.GetFiles()) { var extractPath = Path.Combine(outDir, file.Filename); Console.WriteLine(" Extracting {0} to {1}.", file.Filename, extractPath); file.ExtractTo(extractPath); } }
/// <summary> /// Allocates a decompressor for each cab and merges any cabs that need merged. /// </summary> /// <param name="cabinets"></param> /// <returns></returns> private static IEnumerable <MSCabinet> MergeCabs(IList <CabInfo> cabInfos) { /* Sometimes cab files are part of a set. We must merge those into their set before we leave here. * Otherwise extracting a file that extends beyond the bounds of one cab in the set will fail. This happens in VBRuntime.msi * * It can be determined if a cabinet has further parts to load by examining the mscabd_cabinet::flags field: * if (flags & MSCAB_HDR_PREVCAB) is non-zero, there is a predecessor cabinet to open() and prepend(). Its MS-DOS case-insensitive filename is mscabd_cabinet::prevname * if (flags & MSCAB_HDR_NEXTCAB) is non-zero, there is a successor cabinet to open() and append(). Its MS-DOS case-insensitive filename is mscabd_cabinet::nextname */ var decompressors = new List <MSCabinet>(); for (int i = 0; i < cabInfos.Count; i++) { CabInfo cab = cabInfos[i]; MSCabinet msCab = null; try { msCab = new MSCabinet(cab.LocalCabFile); // NOTE: Deliberately not disposing. Caller must cleanup. } catch (Exception) { // As seen in https://github.com/activescott/lessmsi/issues/104, sometimes bogus cabs are inside of a msi but they're not needed to extract any files from. So we should attempt to ignore this failure here: Debug.Fail( string.Format("Cab name \"{0}\" could not be read by cab reader. Will attempt to ignore...", cab.CabSourceName) ); continue; } if ((msCab.Flags & MSCabinetFlags.MSCAB_HDR_NEXTCAB) != 0) { Debug.Assert(!string.IsNullOrEmpty(msCab.NextName), "Header indcates next cab but new cab not found."); // load the cab found in NextName: // Append it to msCab Debug.Print("Found cabinet set. Nextname: " + msCab.NextName); var nextCab = FindCabAndRemoveFromList(cabInfos, msCab.NextName); var msCabNext = new MSCabinet(nextCab.LocalCabFile); msCab.Append(msCabNext); decompressors.Add(msCab); } else if ((msCab.Flags & MSCabinetFlags.MSCAB_HDR_PREVCAB) != 0) { Debug.Assert(!string.IsNullOrEmpty(msCab.PrevName), "Header indcates prev cab but new cab not found."); Debug.Print("Found cabinet set. PrevName: " + msCab.PrevName); var prevCabInfo = FindCabAndRemoveFromList(cabInfos, msCab.PrevName); var msCabPrev = new MSCabinet(prevCabInfo.LocalCabFile); msCabPrev.Append(msCab); decompressors.Add(msCabPrev); } else { // just a simple standalone cab decompressors.Add(msCab); } } return(decompressors); }
public void CabinetSet() { //NO WORKY! var root = Path.Combine(AppPath, "cabinetset"); var cabFilename1 = Path.Combine(root, "Disk1.CAB"); var cabFilename2 = Path.Combine(root, "Disk2.CAB"); using (var cab1 = new MSCabinet(cabFilename1)) using (var cab2 = new MSCabinet(cabFilename2)) { cab1.Append(cab2); ExplodeCab(cab1, OutDir(cabFilename1)); } Console.ReadLine(); }
public void CabinetSet() { string root = Path.Combine(AppContext.BaseDirectory, "test_files", "cabinetset"); string cabFilename1 = Path.Combine(root, "Disk1.CAB"); string cabFilename2 = Path.Combine(root, "Disk2.CAB"); Assert.True(File.Exists(cabFilename1)); Assert.True(File.Exists(cabFilename2)); using (var cab1 = new MSCabinet(cabFilename1)) using (var cab2 = new MSCabinet(cabFilename2)) { cab1.Append(cab2); ExplodeCab(cab1, OutDir(cabFilename1)); } }
private static void ExplodeCab(string cabinetFilename) { Console.WriteLine("Files in " + cabinetFilename + ":"); try { using (var cab = new MSCabinet(cabinetFilename)) { var outDir = OutDir(cabinetFilename); ExplodeCab(cab, outDir); } } catch (Exception e) { Console.WriteLine("ERROR! " + e.Message); } Console.WriteLine(""); Console.WriteLine(""); }
/// <summary> /// Allocates a decompressor for each cab and merges any cabs that need merged. /// </summary> /// <param name="cabinets"></param> /// <returns></returns> private static IEnumerable <MSCabinet> MergeCabs(IList <CabInfo> cabInfos) { /* Sometimes cab files are part of a set. We must merge those into their set before we leave here. * Otherwise extracting a file that extends beyond the bounds of one cab in the set will fail. This happens in VBRuntime.msi * * It can be determined if a cabinet has further parts to load by examining the mscabd_cabinet::flags field: * if (flags & MSCAB_HDR_PREVCAB) is non-zero, there is a predecessor cabinet to open() and prepend(). Its MS-DOS case-insensitive filename is mscabd_cabinet::prevname * if (flags & MSCAB_HDR_NEXTCAB) is non-zero, there is a successor cabinet to open() and append(). Its MS-DOS case-insensitive filename is mscabd_cabinet::nextname */ var decompressors = new List <MSCabinet>(); for (int i = 0; i < cabInfos.Count; i++) { CabInfo cab = cabInfos[i]; var msCab = new MSCabinet(cab.LocalCabFile); //NOTE: Deliberately not disposing. Caller must cleanup. if ((msCab.Flags & MSCabinetFlags.MSCAB_HDR_NEXTCAB) != 0) { Debug.Assert(!string.IsNullOrEmpty(msCab.NextName), "Header indcates next cab but new cab not found."); // load the cab found in NextName: // Append it to msCab Debug.Print("Found cabinet set. Nextname: " + msCab.NextName); var nextCab = FindCabAndRemoveFromList(cabInfos, msCab.NextName); var msCabNext = new MSCabinet(nextCab.LocalCabFile); msCab.Append(msCabNext); decompressors.Add(msCab); } else if ((msCab.Flags & MSCabinetFlags.MSCAB_HDR_PREVCAB) != 0) { Debug.Assert(!string.IsNullOrEmpty(msCab.PrevName), "Header indcates prev cab but new cab not found."); Debug.Print("Found cabinet set. PrevName: " + msCab.PrevName); var prevCabInfo = FindCabAndRemoveFromList(cabInfos, msCab.PrevName); var msCabPrev = new MSCabinet(prevCabInfo.LocalCabFile); msCabPrev.Append(msCab); decompressors.Add(msCabPrev); } else { // just a simple standalone cab decompressors.Add(msCab); } } return(decompressors); }
// TODO: Add stream opening support /// <inheritdoc/> public ConcurrentDictionary <string, ConcurrentQueue <string> > Scan(Scanner scanner, Stream stream, string file) { // If the cab file itself fails try { string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempPath); using (MSCabinet cabfile = new MSCabinet(file)) { foreach (var sub in cabfile.GetFiles()) { // If an individual entry fails try { // The trim here is for some very odd and stubborn files string tempFile = Path.Combine(tempPath, sub.Filename.TrimEnd('.')); sub.ExtractTo(tempFile); } catch { } } } // Collect and format all found protections var protections = scanner.GetProtections(tempPath); // If temp directory cleanup fails try { Directory.Delete(tempPath, true); } catch { } // Remove temporary path references Utilities.StripFromKeys(protections, tempPath); return(protections); } catch { } return(null); }
static void Main() { IEnumerable <string> files = Directory.GetFiles(AppPath, "*.cab"); foreach (var f in files) { ExplodeCab(f); } //NO WORKY! var root = Path.Combine(AppPath, "cabinetset"); var cabFilename1 = Path.Combine(root, "Disk1.CAB"); var cabFilename2 = Path.Combine(root, "Disk2.CAB"); using (var cab1 = new MSCabinet(cabFilename1)) using (var cab2 = new MSCabinet(cabFilename2)) { cab1.Append(cab2); ExplodeCab(cab1, OutDir(cabFilename1)); } Console.ReadLine(); }
/// <summary> /// Allocates a decompressor for each cab and merges any cabs that need merged. /// </summary> /// <param name="cabinets"></param> /// <returns></returns> private static IEnumerable<MSCabinet> MergeCabs(IList<CabInfo> cabInfos) { /* Sometimes cab files are part of a set. We must merge those into their set before we leave here. * Otherwise extracting a file that extends beyond the bounds of one cab in the set will fail. This happens in VBRuntime.msi * * It can be determined if a cabinet has further parts to load by examining the mscabd_cabinet::flags field: * if (flags & MSCAB_HDR_PREVCAB) is non-zero, there is a predecessor cabinet to open() and prepend(). Its MS-DOS case-insensitive filename is mscabd_cabinet::prevname * if (flags & MSCAB_HDR_NEXTCAB) is non-zero, there is a successor cabinet to open() and append(). Its MS-DOS case-insensitive filename is mscabd_cabinet::nextname */ var decompressors = new List<MSCabinet>(); for (int i=0; i < cabInfos.Count; i++) { CabInfo cab = cabInfos[i]; var msCab = new MSCabinet(cab.LocalCabFile);//NOTE: Deliberately not disposing. Caller must cleanup. if ((msCab.Flags & MSCabinetFlags.MSCAB_HDR_NEXTCAB) != 0) { Debug.Assert(!string.IsNullOrEmpty(msCab.NextName), "Header indcates next cab but new cab not found."); // load the cab found in NextName: // Append it to msCab Debug.Print("Found cabinet set. Nextname: " + msCab.NextName); var nextCab = FindCabAndRemoveFromList(cabInfos, msCab.NextName); var msCabNext = new MSCabinet(nextCab.LocalCabFile); msCab.Append(msCabNext); decompressors.Add(msCab); } else if ((msCab.Flags & MSCabinetFlags.MSCAB_HDR_PREVCAB) != 0) { Debug.Assert(!string.IsNullOrEmpty(msCab.PrevName), "Header indcates prev cab but new cab not found."); Debug.Print("Found cabinet set. PrevName: " + msCab.PrevName); var prevCabInfo = FindCabAndRemoveFromList(cabInfos, msCab.PrevName); var msCabPrev = new MSCabinet(prevCabInfo.LocalCabFile); msCabPrev.Append(msCab); decompressors.Add(msCabPrev); } else { // just a simple standalone cab decompressors.Add(msCab); } } return decompressors; }
/// <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(FileInfo msi, DirectoryInfo outputDir, MsiFile[] filesToExtract, AsyncCallback progressCallback) { if (msi == null) throw new ArgumentNullException("msi"); if (outputDir == null) throw new ArgumentNullException("outputDir"); int filesExtractedSoFar = 0; ExtractionProgress progress = null; Database msidb = new Database(msi.FullName, OpenDatabase.ReadOnly); try { if (filesToExtract == null || filesToExtract.Length < 1) filesToExtract = MsiFile.CreateMsiFilesFromMSI(msidb); progress = new ExtractionProgress(progressCallback, filesToExtract.Length); if (!msi.Exists) { Trace.WriteLine("File \'" + msi.FullName + "\' not found."); progress.ReportProgress(ExtractionActivity.Complete, "", filesExtractedSoFar); return; } progress.ReportProgress(ExtractionActivity.Initializing, "", filesExtractedSoFar); outputDir.Create(); //map short file names to the msi file entry var fileEntryMap = new Dictionary<string, MsiFile>(filesToExtract.Length, StringComparer.InvariantCulture); foreach (var fileEntry in filesToExtract) { MsiFile existingFile = null; if (fileEntryMap.TryGetValue(fileEntry.File, out existingFile)) { //NOTE: This used to be triggered when we ignored case of file, but now we don't ignore case so this is unlikely to occur. // Differing only by case is not compliant with the msi specification but some installers do it (e.g. python, see issue 28). Debug.Print("!!Found duplicate file using key {0}. The existing key was {1}", fileEntry.File, existingFile.File); } else { fileEntryMap.Add(fileEntry.File, fileEntry); } } Debug.Assert(fileEntryMap.Count == filesToExtract.Length, "Duplicate files must have caused some files to not be in the map."); var cabinets = CabsFromMsiToDisk(msidb, outputDir); foreach (CabInfo cabinfo in cabinets) { using (var cabDecompressor = new MSCabinet(cabinfo.LocalCabFile)) { foreach (var compressedFile in cabDecompressor.GetFiles()) { var entry = fileEntryMap[compressedFile.Filename]; progress.ReportProgress(ExtractionActivity.ExtractingFile, entry.LongFileName, filesExtractedSoFar); DirectoryInfo targetDirectoryForFile = GetTargetDirectory(outputDir, entry.Directory); string destName = Path.Combine(targetDirectoryForFile.FullName, entry.LongFileName); if (File.Exists(destName)) { Debug.Fail("output file already exists. We'll make it unique, but this is probably a strange msi or a bug in this program."); //make unique // ReSharper disable HeuristicUnreachableCode Trace.WriteLine(string.Concat("Duplicate file found \'", destName, "\'")); int duplicateCount = 0; string uniqueName; do { uniqueName = string.Concat(destName, ".", "duplicate", ++duplicateCount); } while (File.Exists(uniqueName)); destName = uniqueName; // ReSharper restore HeuristicUnreachableCode } Trace.WriteLine(string.Concat("Extracting File \'", compressedFile.Filename, "\' to \'", destName, "\'")); compressedFile.ExtractTo(destName); filesExtractedSoFar++; } } //del local cab: File.Delete(cabinfo.LocalCabFile); } } finally { if (msidb != null) msidb.Close(); if (progress != null) progress.ReportProgress(ExtractionActivity.Complete, "", filesExtractedSoFar); } }