Exemplo n.º 1
0
        /// <summary>
        /// Cleans up and removes stale workload packs.
        /// </summary>
        public void GarbageCollectInstalledWorkloadPacks()
        {
            try
            {
                Log?.LogMessage("Starting garbage collection.");
                IEnumerable <SdkFeatureBand> installedFeatureBands = GetInstalledFeatureBands();
                IEnumerable <WorkloadId>     installedWorkloads    = RecordRepository.GetInstalledWorkloads(_sdkFeatureBand);
                IEnumerable <PackInfo>       expectedWorkloadPacks = installedWorkloads
                                                                     .SelectMany(workload => _workloadResolver.GetPacksInWorkload(workload))
                                                                     .Select(pack => _workloadResolver.TryGetPackInfo(pack))
                                                                     .Where(pack => pack != null);
                IEnumerable <WorkloadPackId> expectedPackIds = expectedWorkloadPacks.Select(p => p.Id);

                foreach (PackInfo expectedPack in expectedWorkloadPacks)
                {
                    Log?.LogMessage($"Expected workload pack, ID: {expectedPack.ResolvedPackageId}, version: {expectedPack.Version}.");
                }

                foreach (SdkFeatureBand installedFeatureBand in installedFeatureBands)
                {
                    Log?.LogMessage($"Installed feature band: {installedFeatureBand}");
                }

                IEnumerable <WorkloadPackRecord> installedWorkloadPacks = WorkloadPackRecords.Values.SelectMany(r => r);

                List <WorkloadPackRecord> packsToRemove = new List <WorkloadPackRecord>();

                // We first need to clean up the dependents and then do a pass at removing them. Querying the installed packs
                // is effectively a table scan of the registry to make sure we have accurate information and there's a
                // potential perf hit for both memory and speed when enumerating large sets of registry entries.
                foreach (WorkloadPackRecord packRecord in installedWorkloadPacks)
                {
                    DependencyProvider depProvider = new DependencyProvider(packRecord.ProviderKeyName);

                    // Find all the dependents that look like they belong to SDKs. We only care
                    // about dependents that match the SDK host we're running under. For example, an x86 SDK should not be
                    // modifying the x64 MSI dependents.
                    IEnumerable <string> sdkDependents = depProvider.Dependents
                                                         .Where(d => d.StartsWith($"{DependentPrefix}"))
                                                         .Where(d => d.EndsWith($",{HostArchitecture}"));

                    foreach (string dependent in sdkDependents)
                    {
                        Log?.LogMessage($"Evaluating dependent for workload pack, dependent: {dependent}, pack ID: {packRecord.PackId}, pack version: {packRecord.PackVersion}");

                        // Dependents created by the SDK should have 3 parts, for example, "Microsoft.NET.Sdk,6.0.100,x86".
                        string[] dependentParts = dependent.Split(',');

                        if (dependentParts.Length != 3)
                        {
                            Log?.LogMessage($"Skipping dependent: {dependent}");
                            continue;
                        }

                        try
                        {
                            SdkFeatureBand dependentFeatureBand = new SdkFeatureBand(dependentParts[1]);

                            if (!installedFeatureBands.Contains(dependentFeatureBand))
                            {
                                Log?.LogMessage($"Removing dependent '{dependent}' from provider key '{depProvider.ProviderKeyName}' because its SDK feature band does not match any installed feature bands.");
                                UpdateDependent(InstallRequestType.RemoveDependent, depProvider.ProviderKeyName, dependent);
                            }

                            if (dependentFeatureBand.Equals(_sdkFeatureBand))
                            {
                                // If the current SDK feature band is listed as a dependent, we can validate
                                // the workload pack against the expected pack IDs and versions to potentially remove it.
                                if (!expectedWorkloadPacks.Where(p => packRecord.PackId.Equals(p.ResolvedPackageId))
                                    .Where(p => p.Version.Equals(packRecord.PackVersion.ToString())).Any())
                                {
                                    Log?.LogMessage($"Removing dependent '{dependent}' because the pack record does not match any expected packs.");
                                    UpdateDependent(InstallRequestType.RemoveDependent, depProvider.ProviderKeyName, dependent);
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            Log?.LogMessage($"{e.Message}");
                            Log?.LogMessage($"{e.StackTrace}");
                            continue;
                        }
                    }

                    // Recheck the registry to see if there are any remaining dependents. If not, we can
                    // remove the workload pack. We'll add it to the list and remove the packs at the end.
                    IEnumerable <string> remainingDependents = depProvider.Dependents;

                    if (remainingDependents.Any())
                    {
                        Log?.LogMessage($"{packRecord.PackId} ({packRecord.PackVersion}) will not be removed because other dependents remain: {string.Join(", ", remainingDependents)}.");
                    }
                    else
                    {
                        packsToRemove.Add(packRecord);
                    }
                }

                foreach (WorkloadPackRecord record in packsToRemove)
                {
                    // We need to make sure the product is actually installed and that we're not dealing with an orphaned record, e.g.
                    // if a previous removal was interrupted. We can't safely clean up orphaned records because it's too expensive
                    // to query all installed components and determine the product codes associated with the component that
                    // created the record.
                    DetectState state = Detect(record.ProductCode, out string _);

                    if (state == DetectState.Present)
                    {
                        // We don't have package information and can't construct it accurately.
                        string id = $"{record.PackId}.Msi.{HostArchitecture}";

                        MsiPayload msiPayload = GetCachedMsiPayload(id, record.PackVersion.ToString());
                        VerifyPackage(msiPayload);
                        InstallAction plannedAction = GetPlannedAction(state, InstallAction.Uninstall);

                        string logFile = GetMsiLogName(record, plannedAction);

                        uint error = ExecuteWithProgress($"Removing {id} ", () => UninstallMsi(record.ProductCode, logFile));
                        ExitOnError(error, $"Failed to uninstall {msiPayload.MsiPath}.");
                    }
                }
            }
            catch (Exception e)
            {
                LogException(e);
                throw;
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Cleans up and removes stale workload packs.
        /// </summary>
        public void GarbageCollectInstalledWorkloadPacks(DirectoryPath?offlineCache = null)
        {
            try
            {
                ReportPendingReboot();
                Log?.LogMessage("Starting garbage collection.");
                IEnumerable <SdkFeatureBand> installedFeatureBands = GetInstalledFeatureBands();
                IEnumerable <WorkloadId>     installedWorkloads    = RecordRepository.GetInstalledWorkloads(_sdkFeatureBand);
                Dictionary <(WorkloadPackId id, string version), PackInfo> expectedWorkloadPacks = installedWorkloads
                                                                                                   .SelectMany(workload => _workloadResolver.GetPacksInWorkload(workload))
                                                                                                   .Distinct()
                                                                                                   .Select(pack => _workloadResolver.TryGetPackInfo(pack))
                                                                                                   .Where(pack => pack != null)
                                                                                                   .ToDictionary(p => (new WorkloadPackId(p.ResolvedPackageId), p.Version));

                foreach (PackInfo expectedPack in expectedWorkloadPacks.Values)
                {
                    Log?.LogMessage($"Expected workload pack, ID: {expectedPack.ResolvedPackageId}, version: {expectedPack.Version}.");
                }

                foreach (SdkFeatureBand installedFeatureBand in installedFeatureBands)
                {
                    Log?.LogMessage($"Installed feature band: {installedFeatureBand}");
                }

                IEnumerable <WorkloadPackRecord> installedWorkloadPacks = GetWorkloadPackRecords();

                List <WorkloadPackRecord> packsToRemove = new List <WorkloadPackRecord>();

                // We first need to clean up the dependents and then do a pass at removing them. Querying the installed packs
                // is effectively a table scan of the registry to make sure we have accurate information and there's a
                // potential perf hit for both memory and speed when enumerating large sets of registry entries.
                foreach (WorkloadPackRecord packRecord in installedWorkloadPacks)
                {
                    DependencyProvider depProvider = new DependencyProvider(packRecord.ProviderKeyName);

                    // Find all the dependents that look like they belong to SDKs. We only care
                    // about dependents that match the SDK host we're running under. For example, an x86 SDK should not be
                    // modifying the x64 MSI dependents.
                    IEnumerable <string> sdkDependents = depProvider.Dependents
                                                         .Where(d => d.StartsWith($"{DependentPrefix}"))
                                                         .Where(d => d.EndsWith($",{HostArchitecture}"));

                    foreach (string dependent in sdkDependents)
                    {
                        Log?.LogMessage($"Evaluating dependent for workload pack, dependent: {dependent}, MSI ID: {packRecord.MsiId}, MSI version: {packRecord.MsiNuGetVersion}");

                        // Dependents created by the SDK should have 3 parts, for example, "Microsoft.NET.Sdk,6.0.100,x86".
                        string[] dependentParts = dependent.Split(',');

                        if (dependentParts.Length != 3)
                        {
                            Log?.LogMessage($"Skipping dependent: {dependent}");
                            continue;
                        }

                        try
                        {
                            SdkFeatureBand dependentFeatureBand = new SdkFeatureBand(dependentParts[1]);

                            if (!installedFeatureBands.Contains(dependentFeatureBand))
                            {
                                Log?.LogMessage($"Removing dependent '{dependent}' from provider key '{depProvider.ProviderKeyName}' because its SDK feature band does not match any installed feature bands.");
                                UpdateDependent(InstallRequestType.RemoveDependent, depProvider.ProviderKeyName, dependent);
                            }

                            if (dependentFeatureBand.Equals(_sdkFeatureBand))
                            {
                                // If the current SDK feature band is listed as a dependent, we can validate
                                // the workload packs against the expected pack IDs and versions to potentially remove it.
                                if (packRecord.InstalledPacks.All(p => !expectedWorkloadPacks.ContainsKey((p.id, p.version.ToString()))))
                                {
                                    //  None of the packs installed by this MSI are necessary any longer for this feature band, so we can remove the reference count
                                    Log?.LogMessage($"Removing dependent '{dependent}' because the pack record(s) do not match any expected packs.");
                                    UpdateDependent(InstallRequestType.RemoveDependent, depProvider.ProviderKeyName, dependent);
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            Log?.LogMessage($"{e.Message}");
                            Log?.LogMessage($"{e.StackTrace}");
                            continue;
                        }
                    }

                    // Recheck the registry to see if there are any remaining dependents. If not, we can
                    // remove the workload pack. We'll add it to the list and remove the packs at the end.
                    IEnumerable <string> remainingDependents = depProvider.Dependents;

                    if (remainingDependents.Any())
                    {
                        Log?.LogMessage($"{packRecord.MsiId} ({packRecord.MsiNuGetVersion}) will not be removed because other dependents remain: {string.Join(", ", remainingDependents)}.");
                    }
                    else
                    {
                        packsToRemove.Add(packRecord);
                    }
                }

                foreach (WorkloadPackRecord record in packsToRemove)
                {
                    // We need to make sure the product is actually installed and that we're not dealing with an orphaned record, e.g.
                    // if a previous removal was interrupted. We can't safely clean up orphaned records because it's too expensive
                    // to query all installed components and determine the product codes associated with the component that
                    // created the record.
                    DetectState state = DetectPackage(record.ProductCode, out Version _);

                    if (state == DetectState.Present)
                    {
                        // Manually construct the MSI payload package details
                        string     id  = $"{record.MsiId}.Msi.{HostArchitecture}";
                        MsiPayload msi = GetCachedMsiPayload(id, record.MsiNuGetVersion.ToString(), offlineCache);

                        // Make sure the package we have in the cache matches with the record. If it doesn't, we'll do the uninstall
                        // the hard way
                        if (!string.Equals(record.ProductCode, msi.ProductCode, StringComparison.OrdinalIgnoreCase))
                        {
                            Log?.LogMessage($"ProductCode mismatch! Cached package: {msi.ProductCode}, pack record: {record.ProductCode}.");
                            string logFile = GetMsiLogName(record, InstallAction.Uninstall);
                            uint   error   = ExecuteWithProgress(String.Format(LocalizableStrings.MsiProgressUninstall, id), () => UninstallMsi(record.ProductCode, logFile));
                            ExitOnError(error, $"Failed to uninstall {msi.MsiPath}.");
                        }
                        else
                        {
                            // No need to plan. We know that there are no other dependents, the MSI is installed and we
                            // want to remove it.
                            VerifyPackage(msi);
                            ExecutePackage(msi, InstallAction.Uninstall);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                LogException(e);
                throw;
            }
        }