Exemplo n.º 1
0
        /// <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="msi">The MSI file to extract files from.</param>
        /// <param name="outputDir">The directory to extract the files into.</param>
        /// <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)
        {
            if (msi.IsEmpty)
            {
                throw new ArgumentNullException("msi");
            }
            if (string.IsNullOrEmpty(outputDir))
            {
                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, DatabaseOpenMode.ReadOnly);

            try
            {
                if (filesToExtract == null || filesToExtract.Length < 1)
                {
                    filesToExtract = MsiFile.CreateMsiFilesFromMSI(msidb);
                }

                progress = new ExtractionProgress(progressCallback, filesToExtract.Length);

                if (!FileSystem.Exists(msi))
                {
                    Trace.WriteLine("File \'" + msi + "\' not found.");
                    progress.ReportProgress(ExtractionActivity.Complete, string.Empty, filesExtractedSoFar);
                    return;
                }

                progress.ReportProgress(ExtractionActivity.Initializing, string.Empty, filesExtractedSoFar);
                FileSystem.CreateDirectory(new Path(outputDir));

                //map short file names to the msi file entry
                var fileEntryMap = new Dictionary <string, MsiFile>(filesToExtract.Length, StringComparer.InvariantCulture);
                foreach (MsiFile 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.");

                progress.ReportProgress(ExtractionActivity.Uncompressing, string.Empty, filesExtractedSoFar);
                List <CabInfo> cabInfos = CabsFromMsiToDisk(msi, msidb, outputDir);

                try
                {
                    foreach (CabInfo cab in cabInfos)
                    {
                        foreach (CabFileInfo cabFile in cab.GetFiles())
                        {
                            var sourcePath = Path.Combine(cabFile.Path, cabFile.Name);
                            if (fileEntryMap.TryGetValue(sourcePath.PathString, out MsiFile msiFile))
                            {
                                Path destPath         = Path.Combine(GetTargetDirectory(outputDir, msiFile.Directory.FullPath), msiFile.LongFileName);
                                Path relativeDestPath = Path.Combine(msiFile.Directory.FullPath, msiFile.LongFileName);
                                progress.ReportProgress(ExtractionActivity.ExtractingFile, relativeDestPath.PathString, ++filesExtractedSoFar);
                                cab.UnpackFile(sourcePath.PathString, destPath.PathString);
                            }
                        }
                    }
                }
                finally
                {
                    foreach (CabInfo cab in cabInfos)
                    {
                        DeleteFileForcefully(new Path(cab.FullName));
                    }
                }
            }
            finally
            {
                if (msidb != null)
                {
                    msidb.Close();
                }
                if (progress != null)
                {
                    progress.ReportProgress(ExtractionActivity.Complete, "", filesExtractedSoFar);
                }
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Executes the specified command. Assume working directory is TestFiles\MsiInput\ dir.
        /// </summary>
        /// <param name="commandLineArguments">The command line arguments (everything after the exe name).</param>
        /// <param name="testName">The name of hte test (used to formulate the expectedEntries output dir).</param>
        /// <param name="actualEntriesOutputDir">The output directory where the actual extraction is expected to occur.</param>
        private void TestExtraction(string commandLineArguments, string testName, string actualEntriesOutputDir, bool useInProcessForDebugging)
        {
			string consoleOutput;
            var actualOutDir = new LessIO.Path(actualEntriesOutputDir).FullPath;
            if (actualOutDir.Exists)
                FileSystem.RemoveDirectory(actualOutDir, true);
	        int exitCode;

			if (useInProcessForDebugging)
				exitCode = base.RunCommandLineInProccess(commandLineArguments);
			else
				exitCode = base.RunCommandLine(commandLineArguments, out consoleOutput);
            Assert.Equal(0, exitCode);
			var actualEntries = FileEntryGraph.GetActualEntries(actualOutDir.FullPathString, "Actual Entries");
	        var actualEntriesFile = GetActualOutputFile(testName);
	        actualEntries.Save(actualEntriesFile);
			//Console.WriteLine("Actual entries saved to " + actualEntriesFile.FullName);
			var expectedEntries = GetExpectedEntriesForMsi(testName);
            AssertAreEqual(expectedEntries, actualEntries);
        }
Exemplo n.º 3
0
        public static void ExtractFiles(Path msi, string outputDir, string[] fileNamesToExtract, AsyncCallback progressCallback)
        {
            var msiFiles = GetMsiFileFromFileNames(msi, fileNamesToExtract);

            ExtractFiles(msi, outputDir, msiFiles, progressCallback);
        }
Exemplo n.º 4
0
        public static void ExtractFiles(Path msi, string outputDir, string[] fileNamesToExtract)
        {
            var msiFiles = GetMsiFileFromFileNames(msi, fileNamesToExtract);

            ExtractFiles(msi, outputDir, msiFiles, null);
        }
Exemplo n.º 5
0
 public static void ExtractFiles(Path msi, string outputDir)
 {
     ExtractFiles(msi, outputDir, new string[0], null);
 }
Exemplo n.º 6
0
 /// <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);
 }
Exemplo n.º 7
0
        /// <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)
        {
            if (msi.IsEmpty)
            {
                throw new ArgumentNullException("msi");
            }
            if (string.IsNullOrEmpty(outputDir))
            {
                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
            {
                if (filesToExtract == null || filesToExtract.Length < 1)
                {
                    filesToExtract = MsiFile.CreateMsiFilesFromMSI(msidb);
                }

                progress = new ExtractionProgress(progressCallback, filesToExtract.Length);

                if (!FileSystem.Exists(msi))
                {
                    Trace.WriteLine("File \'" + msi + "\' not found.");
                    progress.ReportProgress(ExtractionActivity.Complete, "", filesExtractedSoFar);
                    return;
                }

                progress.ReportProgress(ExtractionActivity.Initializing, "", filesExtractedSoFar);
                FileSystem.CreateDirectory(new Path(outputDir));

                //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 cabInfos         = CabsFromMsiToDisk(msi, msidb, outputDir);
                var cabDecompressors = MergeCabs(cabInfos);
                try
                {
                    foreach (MSCabinet decompressor in cabDecompressors)
                    {
                        foreach (var compressedFile in decompressor.GetFiles())
                        {
                            // if the user didn't select this in the UI for extraction, skip it.
                            if (!fileEntryMap.ContainsKey(compressedFile.Filename))
                            {
                                continue;
                            }
                            var entry = fileEntryMap[compressedFile.Filename];
                            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))
                            {
                                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;
                                Path uniqueName;
                                do
                                {
                                    uniqueName = new Path(destName + "." + "duplicate" + ++duplicateCount);
                                } while (FileSystem.Exists(uniqueName));
                                destName = uniqueName;
                                // ReSharper restore HeuristicUnreachableCode
                            }
                            Trace.WriteLine(string.Concat("Extracting File \'", compressedFile.Filename, "\' to \'", destName, "\'"));
                            compressedFile.ExtractTo(destName.PathString);
                            filesExtractedSoFar++;
                        }
                    }
                }
                finally
                {               //cleanup the decompressors allocated in MergeCabs
                    foreach (var decomp in cabDecompressors)
                    {
                        decomp.Close(false);
                        DeleteFileForcefully(new Path(decomp.LocalFilePath));
                    }
                }
            }
            finally
            {
                if (msidb != null)
                {
                    msidb.Close();
                }
                if (progress != null)
                {
                    progress.ReportProgress(ExtractionActivity.Complete, "", filesExtractedSoFar);
                }
            }
        }