public async Task <UpdateImpactResults> Analyze( Stream appxBlockMap1, Stream appxBlockMap2, CancellationToken cancellationToken = default, IProgress <ProgressData> progressData = default) { var progressReadFiles1 = new RangeProgress(progressData, 0, 30); var progressReadFiles2 = new RangeProgress(progressData, 30, 60); var progressCalculatingStatus = new RangeProgress(progressData, 60, 75); var progressDuplicates = new RangeProgress(progressData, 75, 90); var progressRemainingCalculation = new RangeProgress(progressData, 90, 100); var filesInOldPackage = new SortedDictionary <string, AppxFile>(); var filesInNewPackage = new SortedDictionary <string, AppxFile>(); var blocksInPackage1 = new Dictionary <string, AppxBlock>(); var blocksInPackage2 = new Dictionary <string, AppxBlock>(); progressReadFiles1.Report(new ProgressData(0, "Reading the first package...")); var files1 = await this.GetFiles(appxBlockMap1, cancellationToken, progressReadFiles1).ConfigureAwait(false); foreach (var file1 in files1) { foreach (var block in file1.Blocks) { blocksInPackage1[block.Hash] = block; } filesInOldPackage.Add(file1.Name, file1); } progressReadFiles2.Report(new ProgressData(0, "Reading the second package...")); var files2 = await this.GetFiles(appxBlockMap2, cancellationToken, progressReadFiles2).ConfigureAwait(false); foreach (var file2 in files2) { foreach (var block in file2.Blocks) { blocksInPackage2[block.Hash] = block; } filesInNewPackage.Add(file2.Name, file2); } progressCalculatingStatus.Report(new ProgressData(0, "Calculating file status (1/2)...")); foreach (var file in filesInOldPackage) { cancellationToken.ThrowIfCancellationRequested(); if (!filesInNewPackage.TryGetValue(file.Key, out var fileFromPackage2)) { file.Value.Status = ComparisonStatus.Old; file.Value.UpdateImpact = 0; // file removed = no update on impact file.Value.SizeDifference = -file.Value.UncompressedSize; continue; } if (file.Value.UncompressedSize == fileFromPackage2.UncompressedSize && file.Value.Blocks.Select(b => b.Hash).SequenceEqual(fileFromPackage2.Blocks.Select(b => b.Hash))) { file.Value.Status = ComparisonStatus.Unchanged; file.Value.UpdateImpact = 0; // file unchanged = no update on impact file.Value.SizeDifference = 0; fileFromPackage2.Status = ComparisonStatus.Unchanged; fileFromPackage2.UpdateImpact = 0; // file unchanged = no update on impact fileFromPackage2.SizeDifference = 0; } else { file.Value.Status = ComparisonStatus.Changed; file.Value.UpdateImpact = 0; // file changed = show no update impact. The impact should be shown in the other package. file.Value.SizeDifference = file.Value.UncompressedSize - fileFromPackage2.UncompressedSize; var blocksOnlyInPackage2 = fileFromPackage2.Blocks.Select(b => b.Hash).Except(file.Value.Blocks.Select(b => b.Hash)); fileFromPackage2.Status = ComparisonStatus.Changed; fileFromPackage2.UpdateImpact = blocksOnlyInPackage2.Select(b => blocksInPackage2[b]).Sum(b => b.CompressedSize); fileFromPackage2.SizeDifference = fileFromPackage2.UncompressedSize - file.Value.UncompressedSize; } } foreach (var block1KeyValuePair in blocksInPackage1) { cancellationToken.ThrowIfCancellationRequested(); var block1 = block1KeyValuePair.Value; if (!blocksInPackage2.TryGetValue(block1KeyValuePair.Key, out var block2)) { block1.Status = ComparisonStatus.Old; block1.UpdateImpact = 0; // block removed, no update impact } else { block1.Status = ComparisonStatus.Unchanged; block1.UpdateImpact = 0; block2.Status = ComparisonStatus.Unchanged; block2.UpdateImpact = 0; } } progressCalculatingStatus.Report(new ProgressData(50, "Calculating file status (2/2)...")); foreach (var file in filesInNewPackage) { cancellationToken.ThrowIfCancellationRequested(); if (!filesInOldPackage.TryGetValue(file.Key, out _)) { file.Value.Status = ComparisonStatus.New; file.Value.UpdateImpact = file.Value.Blocks.Sum(b => b.CompressedSize); // sum of all blocks file.Value.SizeDifference = file.Value.UncompressedSize; } } foreach (var block2KeyValuePair in blocksInPackage2) { cancellationToken.ThrowIfCancellationRequested(); var block2 = block2KeyValuePair.Value; if (!blocksInPackage1.ContainsKey(block2KeyValuePair.Key)) { block2.Status = ComparisonStatus.New; block2.UpdateImpact = block2.CompressedSize; } } var duplicates1 = new Dictionary <string, IList <AppxFile> >(); var duplicates2 = new Dictionary <string, IList <AppxFile> >(); progressDuplicates.Report(new ProgressData(0, "Finding duplicates...")); using (var md5 = MD5.Create()) { foreach (var file in filesInOldPackage.Values) { cancellationToken.ThrowIfCancellationRequested(); var hash = string.Join(Environment.NewLine, file.Blocks.Select(b => b.Hash)); var allBlocksHash = System.Text.Encoding.ASCII.GetString(md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(hash))); if (!duplicates1.TryGetValue(allBlocksHash, out var list)) { list = new List <AppxFile>(); duplicates1[allBlocksHash] = list; } list.Add(file); } foreach (var file in filesInNewPackage.Values) { cancellationToken.ThrowIfCancellationRequested(); var hash = string.Join(System.Environment.NewLine, file.Blocks.Select(b => b.Hash)); var allBlocksHash = System.Text.Encoding.ASCII.GetString(md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(hash))); if (!duplicates2.TryGetValue(allBlocksHash, out var list)) { list = new List <AppxFile>(); duplicates2[allBlocksHash] = list; } list.Add(file); } } var duplicatedFiles1 = new AppxDuplication { Duplicates = new List <ComparedDuplicate>(), FileCount = duplicates1.Count(d => d.Value.Count > 1) * 2, FileSize = duplicates1.Where(d => d.Value.Count > 1).Sum(d => d.Value[0].UncompressedSize) }; var duplicatedFiles2 = new AppxDuplication { Duplicates = new List <ComparedDuplicate>(), FileCount = duplicates2.Count(d => d.Value.Count > 1) * 2, FileSize = duplicates2.Where(d => d.Value.Count > 1).Sum(d => d.Value[0].UncompressedSize) }; progressDuplicates.Report(new ProgressData(55, "Analyzing duplication impact (1/3)...")); foreach (var file in duplicates1.Where(d => d.Value.Count > 1)) { cancellationToken.ThrowIfCancellationRequested(); var duplicate = new ComparedDuplicate { Files = new List <ComparedDuplicateFile>() }; foreach (var df in file.Value) { var mdf = new ComparedDuplicateFile { Name = df.Name, PossibleSizeReduction = df.UncompressedSize, PossibleImpactReduction = df.Blocks.Sum(d => blocksInPackage1[d.Hash].CompressedSize) }; duplicate.Files.Add(mdf); } duplicate.PossibleSizeReduction = duplicate.Files[0].PossibleSizeReduction * (duplicate.Files.Count - 1); duplicate.PossibleImpactReduction = duplicate.Files[0].PossibleImpactReduction * (duplicate.Files.Count - 1); duplicatedFiles1.Duplicates.Add(duplicate); } progressDuplicates.Report(new ProgressData(70, "Analyzing duplication impact (2/3)...")); foreach (var file in duplicates2.Where(d => d.Value.Count > 1)) { cancellationToken.ThrowIfCancellationRequested(); var duplicate = new ComparedDuplicate { Files = new List <ComparedDuplicateFile>() }; foreach (var df in file.Value) { var mdf = new ComparedDuplicateFile { Name = df.Name, PossibleSizeReduction = df.UncompressedSize, PossibleImpactReduction = df.Blocks.Sum(d => blocksInPackage2[d.Hash].CompressedSize) }; duplicate.Files.Add(mdf); } duplicate.PossibleSizeReduction = duplicate.Files[0].PossibleSizeReduction * (duplicate.Files.Count - 1); duplicate.PossibleImpactReduction = duplicate.Files[0].PossibleImpactReduction * (duplicate.Files.Count - 1); duplicatedFiles2.Duplicates.Add(duplicate); } progressDuplicates.Report(new ProgressData(85, "Analyzing duplication impact (3/3)...")); duplicatedFiles1.PossibleImpactReduction = duplicatedFiles1.Duplicates.Sum(d => d.PossibleImpactReduction); duplicatedFiles1.PossibleSizeReduction = duplicatedFiles1.Duplicates.Sum(d => d.PossibleSizeReduction); duplicatedFiles2.PossibleImpactReduction = duplicatedFiles2.Duplicates.Sum(d => d.PossibleImpactReduction); duplicatedFiles2.PossibleSizeReduction = duplicatedFiles2.Duplicates.Sum(d => d.PossibleSizeReduction); cancellationToken.ThrowIfCancellationRequested(); progressRemainingCalculation.Report(new ProgressData(0, "Analyzing changed files...")); var changedFiles = new ChangedFiles { // Shared parameters UpdateImpact = filesInNewPackage.Values.Where(b => b.Status == ComparisonStatus.Changed).SelectMany(b => b.Blocks).Sum(b => b.UpdateImpact), ActualUpdateImpact = blocksInPackage2.Values.Where(b => b.Status == ComparisonStatus.Changed).Sum(b => b.UpdateImpact), SizeDifference = filesInNewPackage.Values.Where(b => b.Status == ComparisonStatus.Changed).Sum(b => b.SizeDifference), FileCount = filesInOldPackage.Values.Count(b => b.Status == ComparisonStatus.Changed), // Old package OldPackageFiles = filesInOldPackage.Values.Where(f => f.Status == ComparisonStatus.Changed).ToList(), OldPackageFileSize = filesInOldPackage.Values.Where(b => b.Status == ComparisonStatus.Changed).Sum(b => b.UncompressedSize), OldPackageBlockCount = filesInOldPackage.Values.Where(b => b.Status == ComparisonStatus.Changed).Sum(b => b.Blocks.Count), OldPackageBlockSize = filesInOldPackage.Values.Where(b => b.Status == ComparisonStatus.Changed).SelectMany(b => b.Blocks).Sum(b => b.CompressedSize), // New package NewPackageFiles = filesInNewPackage.Values.Where(f => f.Status == ComparisonStatus.Changed).ToList(), NewPackageFileSize = filesInNewPackage.Values.Where(b => b.Status == ComparisonStatus.Changed).Sum(b => b.UncompressedSize), NewPackageBlockCount = filesInNewPackage.Values.Where(b => b.Status == ComparisonStatus.Changed).Sum(b => b.Blocks.Count), NewPackageBlockSize = filesInNewPackage.Values.Where(b => b.Status == ComparisonStatus.Changed).SelectMany(b => b.Blocks).Sum(b => b.CompressedSize) }; cancellationToken.ThrowIfCancellationRequested(); progressRemainingCalculation.Report(new ProgressData(25, "Analyzing added files...")); var addedFiles = new AddedFiles { UpdateImpact = filesInNewPackage.Values.Where(b => b.Status == ComparisonStatus.New).SelectMany(b => b.Blocks).Sum(b => b.UpdateImpact), ActualUpdateImpact = blocksInPackage2.Values.Where(b => b.Status == ComparisonStatus.New).Sum(b => b.UpdateImpact), BlockCount = blocksInPackage2.Values.Count(b => b.Status == ComparisonStatus.New), BlockSize = blocksInPackage2.Values.Where(b => b.Status == ComparisonStatus.New).Sum(b => b.CompressedSize), FileCount = filesInNewPackage.Values.Count(s => s.Status == ComparisonStatus.New), SizeDifference = filesInNewPackage.Values.Where(s => s.Status == ComparisonStatus.New).Sum(f => f.SizeDifference), FileSize = filesInNewPackage.Values.Where(s => s.Status == ComparisonStatus.New).Sum(f => f.UncompressedSize), Files = filesInNewPackage.Values.Where(f => f.Status == ComparisonStatus.New).ToList() }; cancellationToken.ThrowIfCancellationRequested(); progressRemainingCalculation.Report(new ProgressData(50, "Analyzing deleted files...")); var deletedFiles = new DeletedFiles { FileSize = filesInOldPackage.Values.Where(s => s.Status == ComparisonStatus.Old).Sum(f => f.UncompressedSize), SizeDifference = filesInOldPackage.Values.Where(s => s.Status == ComparisonStatus.Old).Sum(f => f.SizeDifference), FileCount = filesInOldPackage.Values.Count(s => s.Status == ComparisonStatus.Old), BlockSize = blocksInPackage1.Values.Where(b => b.Status == ComparisonStatus.Old).Sum(b => b.CompressedSize), BlockCount = blocksInPackage1.Values.Count(b => b.Status == ComparisonStatus.Old), ActualUpdateImpact = blocksInPackage1.Values.Where(s => s.Status == ComparisonStatus.Old).Sum(f => f.UpdateImpact), Files = filesInOldPackage.Values.Where(f => f.Status == ComparisonStatus.Old).ToList() }; deletedFiles.UpdateImpact = deletedFiles.ActualUpdateImpact; cancellationToken.ThrowIfCancellationRequested(); progressRemainingCalculation.Report(new ProgressData(75, "Analyzing unchanged files...")); var unchangedFiles = new UnchangedFiles { FileCount = filesInNewPackage.Values.Count(b => b.Status == ComparisonStatus.Unchanged), BlockCount = blocksInPackage2.Values.Count(b => b.Status == ComparisonStatus.Unchanged), BlockSize = blocksInPackage2.Values.Where(b => b.Status == ComparisonStatus.Unchanged).Sum(b => b.CompressedSize), FileSize = filesInNewPackage.Values.Where(b => b.Status == ComparisonStatus.Unchanged).Sum(b => b.UncompressedSize), Files = filesInOldPackage.Values.Where(f => f.Status == ComparisonStatus.Unchanged).ToList() }; cancellationToken.ThrowIfCancellationRequested(); progressRemainingCalculation.Report(new ProgressData(90, "Please wait...")); var comparisonResult = new UpdateImpactResults { OldPackageLayout = new AppxLayout { FileSize = filesInOldPackage.Sum(f => f.Value.UncompressedSize), FileCount = filesInOldPackage.Count, BlockSize = blocksInPackage1.Sum(f => f.Value.CompressedSize), BlockCount = blocksInPackage1.Count, Layout = new PackageLayout { Blocks = this.GetChartForBlocks(filesInOldPackage), Files = this.GetChartForFiles(filesInOldPackage) } }, NewPackageLayout = new AppxLayout { FileSize = filesInNewPackage.Sum(f => f.Value.UncompressedSize), FileCount = filesInNewPackage.Count, BlockSize = blocksInPackage2.Sum(f => f.Value.CompressedSize), BlockCount = blocksInPackage2.Count, Layout = new PackageLayout { Blocks = this.GetChartForBlocks(filesInNewPackage), Files = this.GetChartForFiles(filesInNewPackage) } }, OldPackageDuplication = duplicatedFiles1, NewPackageDuplication = duplicatedFiles2, UpdateImpact = blocksInPackage2.Where(b => b.Value.Status == ComparisonStatus.New).Sum(b => b.Value.UpdateImpact) + blocksInPackage1.Sum(b => b.Value.UpdateImpact), ChangedFiles = changedFiles, DeletedFiles = deletedFiles, AddedFiles = addedFiles, UnchangedFiles = unchangedFiles }; comparisonResult.OldPackageLayout.Size = comparisonResult.OldPackageLayout.BlockSize + filesInOldPackage.Values.Sum(f => f.HeaderSize); comparisonResult.NewPackageLayout.Size = comparisonResult.NewPackageLayout.BlockSize + filesInNewPackage.Values.Sum(f => f.HeaderSize); comparisonResult.SizeDifference = comparisonResult.AddedFiles.SizeDifference + comparisonResult.ChangedFiles.SizeDifference + comparisonResult.DeletedFiles.SizeDifference; comparisonResult.ActualUpdateImpact = comparisonResult.UpdateImpact; return(comparisonResult); }
public async Task CreateVolumes( IReadOnlyCollection <string> packagePaths, string volumeDirectory, AppAttachVolumeType type = AppAttachVolumeType.Vhd, bool extractCertificate = false, bool generateScripts = true, CancellationToken cancellationToken = default, IProgress <ProgressData> progressReporter = null) { if (volumeDirectory == null) { throw new ArgumentNullException(nameof(volumeDirectory)); } var diskExtension = type.ToString("G").ToLowerInvariant(); if (!Directory.Exists(volumeDirectory)) { Logger.Info("Creating directory {0}...", volumeDirectory); Directory.CreateDirectory(volumeDirectory); } var allPackagesCount = packagePaths.Count; var currentPackage = 0; var volumeCreationStrategy = await this.GetVolumeCreationStrategy(type).ConfigureAwait(false); IAppAttachVolumeCreationStrategyInitialization initialization = null; try { initialization = await volumeCreationStrategy.Initialize(cancellationToken).ConfigureAwait(false); foreach (var packagePath in packagePaths) { var currentVolumeDirectory = type == AppAttachVolumeType.Cim ? Path.Combine(volumeDirectory, Path.GetFileNameWithoutExtension(packagePath)) : volumeDirectory; var packageProgressReporter = new RangeProgress(progressReporter, (int)(currentPackage * 80.0 / allPackagesCount), (int)((currentPackage + 1) * 80.0 / allPackagesCount)); currentPackage++; var packageFileInfo = new FileInfo(packagePath); if (!packageFileInfo.Exists) { throw new FileNotFoundException($"File {packagePath} does not exist.", packagePath); } var volumeFileInfo = new FileInfo(Path.Combine(currentVolumeDirectory, Path.GetFileNameWithoutExtension(packagePath) + "." + diskExtension)); if (volumeFileInfo.Exists) { volumeFileInfo.Delete(); } else if (volumeFileInfo.Directory?.Exists == false) { volumeFileInfo.Directory.Create(); } packageProgressReporter.Report(new ProgressData(0, $"Analyzing {Path.GetFileName(packagePath)}...")); await volumeCreationStrategy.CreateVolume(packagePath, volumeFileInfo.FullName, null, cancellationToken, packageProgressReporter).ConfigureAwait(false); if (extractCertificate) { packageProgressReporter.Report(new ProgressData(100, $"Extracting certificate from {Path.GetFileName(packagePath)}...")); var actualSigningManager = this.signingManager ?? await this.managerFactory.GetProxyFor(SelfElevationLevel.AsInvoker, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); // ReSharper disable once AssignNullToNotNullAttribute await actualSigningManager.ExtractCertificateFromMsix(packagePath, Path.Combine(volumeFileInfo.DirectoryName, Path.GetFileNameWithoutExtension(volumeFileInfo.FullName)) + ".cer", cancellationToken).ConfigureAwait(false); } } if (type == AppAttachVolumeType.Cim) { // Currently JSON and PS1 are only supported for VHD(X)... } else { var jsonData = new List <JsonData>(); if (generateScripts) { progressReporter?.Report(new ProgressData(90, "Creating scripts...")); await CreateScripts(volumeDirectory, cancellationToken).ConfigureAwait(false); } progressReporter?.Report(new ProgressData(95, "Creating JSON file...")); foreach (var volumePath in packagePaths.Select(p => Path.Combine(volumeDirectory, Path.GetFileNameWithoutExtension(p) + "." + diskExtension))) { var volumeData = await this.GetExpandedPackageData(volumePath, cancellationToken).ConfigureAwait(false); var volumeGuid = volumeData.Item1; var volumeMsixFolderName = volumeData.Item2; jsonData.Add(new JsonData(Path.GetFileName(volumePath), Path.GetFileNameWithoutExtension(volumePath), volumeGuid, volumeMsixFolderName)); } var jsonPath = Path.Combine(volumeDirectory, "app-attach.json"); await CreateJson(jsonPath, jsonData, cancellationToken).ConfigureAwait(false); } } finally { await volumeCreationStrategy.Finish(initialization, cancellationToken).ConfigureAwait(false); } }
private async Task <IList <AppxPackage> > GetConsideredPackages(AppxPackage startPackage, CancellationToken cancellationToken = default, IProgress <ProgressData> progress = default) { var progressForGettingPackages = new RangeProgress(progress, 0, 70); var progressForGettingAddOns = new RangeProgress(progress, 70, 90); var progressForCalculation = new RangeProgress(progress, 90, 100); var manager = await this.packageManager.GetProxyFor(SelfElevationLevel.HighestAvailable, cancellationToken).ConfigureAwait(false); var allPackages = await manager.GetInstalledPackages(PackageFindMode.Auto, cancellationToken, progressForGettingPackages).ConfigureAwait(false); var consideredPackages = new List <AppxPackage> { startPackage }; var addOnPackages = new List <AppxPackage>(); var manifestReader = new AppxManifestReader(); progressForGettingAddOns.Report(new ProgressData(0, "Reading optional packages...")); foreach (var addOnPackage in allPackages.Where(installedPackage => installedPackage.IsOptional)) { using var fileReader = FileReaderFactory.CreateFileReader(addOnPackage.ManifestLocation); addOnPackages.Add(await manifestReader.Read(fileReader, false, cancellationToken).ConfigureAwait(false)); } progressForCalculation.Report(new ProgressData(0, "Reading relations...")); for (var i = 0; i < consideredPackages.Count; i++) { var currentPkg = consideredPackages[i]; var matchingAddOns = addOnPackages.Where(addOnPackage => addOnPackage.MainPackages.Any(dep => dep.Name == currentPkg.Name)); foreach (var matchingAddOn in matchingAddOns) { if (consideredPackages.Any(existing => existing.Publisher == matchingAddOn.Publisher && existing.Name == matchingAddOn.Name)) { // we have already processes this package continue; } consideredPackages.Add(matchingAddOn); } foreach (var dependency in currentPkg.PackageDependencies) { if (consideredPackages.Any(existing => existing.Publisher == dependency.Publisher && existing.Name == dependency.Name)) { // we have already processes this package continue; } var candidate = allPackages.FirstOrDefault(installedPackage => installedPackage.Name == dependency.Name && installedPackage.Publisher == dependency.Publisher && installedPackage.Version >= Version.Parse(dependency.Version)); if (candidate != null) { using var fileReader = FileReaderFactory.CreateFileReader(candidate.ManifestLocation); consideredPackages.Add(await manifestReader.Read(fileReader, false, cancellationToken).ConfigureAwait(false)); } } foreach (var dependency in currentPkg.MainPackages) { if (consideredPackages.Any(existing => existing.Name == dependency.Name)) { // we have already processes this package continue; } var candidate = allPackages.FirstOrDefault(installedPackage => installedPackage.Name == dependency.Name); if (candidate != null) { using var fileReader = FileReaderFactory.CreateFileReader(candidate.ManifestLocation); consideredPackages.Add(await manifestReader.Read(fileReader, false, cancellationToken).ConfigureAwait(false)); } } } return(consideredPackages); }