using System.Diagnostics; using Microsoft.Extensions.Logging; using PatchProbe.Shared.Contracts; using PatchProbe.Shared.Models; namespace PatchProbe.Cli.Collectors; public sealed class CbsDismCollector(ILogger logger) : ICollector> { public async Task> CollectAsync(CancellationToken cancellationToken = default) { logger.LogInformation("Collecting CBS/DISM package state"); var packages = new List(); try { var output = await RunDismAsync(cancellationToken); packages = ParseDismOutput(output); logger.LogInformation("Collected {Count} CBS packages", packages.Count); } catch (Exception ex) { logger.LogWarning(ex, "CBS/DISM collection failed"); } return packages; } private static async Task RunDismAsync(CancellationToken cancellationToken) { var psi = new ProcessStartInfo { FileName = "dism.exe", Arguments = "/Online /Get-Packages /Format:Table", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }; using var process = Process.Start(psi) ?? throw new InvalidOperationException("Failed to start dism.exe"); var output = await process.StandardOutput.ReadToEndAsync(cancellationToken); await process.WaitForExitAsync(cancellationToken); return output; } private static List ParseDismOutput(string output) { var packages = new List(); var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); // Skip header lines; data rows contain "Package Identity" columns separated by spaces/pipes bool inDataSection = false; foreach (var line in lines) { var trimmed = line.Trim(); if (trimmed.StartsWith("Package Identity", StringComparison.OrdinalIgnoreCase)) { inDataSection = true; continue; } if (!inDataSection || string.IsNullOrWhiteSpace(trimmed) || trimmed.StartsWith('-')) continue; // DISM table output uses multiple spaces as column separator var parts = System.Text.RegularExpressions.Regex.Split(trimmed, @"\s{2,}"); if (parts.Length >= 2) { packages.Add(new CbsPackage { PackageIdentity = parts[0].Trim(), State = parts.Length > 1 ? parts[1].Trim() : null, ReleaseType = parts.Length > 2 ? parts[2].Trim() : null, InstallTime = parts.Length > 3 ? parts[3].Trim() : null, }); } } return packages; } }