ConfigurationUnitInformation.cs (12228B)
1 // ----------------------------------------------------------------------------- 2 // <copyright file="ConfigurationUnitInformation.cs" company="Microsoft Corporation"> 3 // Copyright (c) Microsoft Corporation. Licensed under the MIT License. 4 // </copyright> 5 // ----------------------------------------------------------------------------- 6 7 namespace Microsoft.WinGet.Configuration.Engine.Helpers 8 { 9 using System; 10 using System.Collections.Generic; 11 using System.Linq; 12 using System.Management.Automation; 13 using System.Text; 14 using Microsoft.Management.Configuration; 15 using Microsoft.WinGet.Configuration.Engine.Extensions; 16 using Microsoft.WinGet.Resources; 17 using Windows.Foundation.Collections; 18 19 /// <summary> 20 /// Helper class to construct the information messages for a unit. 21 /// This must match or be as close as possible to winget's OutputConfigurationUnitInformation. 22 /// </summary> 23 internal class ConfigurationUnitInformation 24 { 25 private const string Description = "description"; 26 private const string Module = "module"; 27 private const string TreatAsArray = "treatAsArray"; 28 29 private readonly string header; 30 private readonly string information; 31 32 /// <summary> 33 /// Initializes a new instance of the <see cref="ConfigurationUnitInformation"/> class. 34 /// </summary> 35 /// <param name="unit">Configuration unit.</param> 36 public ConfigurationUnitInformation(ConfigurationUnit unit) 37 { 38 this.header = this.CreateHeader(unit, unit.Details != null ? unit.Details.UnitType : unit.Type); 39 this.information = this.CreateInformation(unit); 40 } 41 42 /// <summary> 43 /// Gets the header information message. 44 /// </summary> 45 /// <returns>Header information message.</returns> 46 public HostInformationMessage GetHeader() 47 { 48 return Utilities.CreateInformationMessage(this.header, foregroundColor: ConsoleColor.Cyan); 49 } 50 51 /// <summary> 52 /// Gets the information message. 53 /// </summary> 54 /// <returns>Information message.</returns> 55 public HostInformationMessage GetInformation() 56 { 57 return Utilities.CreateInformationMessage(this.information); 58 } 59 60 private string CreateHeader(ConfigurationUnit unit, string name) 61 { 62 var sb = new StringBuilder(); 63 sb.Append($"{this.IntentToString(unit.Intent)} :: {name}"); 64 65 string identifier = unit.Identifier; 66 if (!string.IsNullOrEmpty(identifier)) 67 { 68 sb.Append($" [{identifier}]"); 69 } 70 71 return sb.ToString(); 72 } 73 74 private string CreateInformation(ConfigurationUnit unit) 75 { 76 IConfigurationUnitProcessorDetails details = unit.Details; 77 ValueSet metadata = unit.Metadata; 78 79 var sb = new StringBuilder(); 80 if (details != null) 81 { 82 this.CreateInformationWithDetails(ref sb, details, metadata); 83 } 84 else 85 { 86 this.CreateInformationWithoutDetails(ref sb, metadata); 87 } 88 89 // -- Sample output footer -- 90 // Dependencies: dep1, dep2, ... 91 // Settings: 92 // <... settings splat> 93 var dependencies = unit.Dependencies; 94 if (dependencies.Count > 0) 95 { 96 var dependencySb = new StringBuilder(); 97 foreach (var dependency in dependencies) 98 { 99 dependencySb.Append($" {dependency}"); 100 } 101 102 sb.AppendLine($" {string.Format(Resources.ConfigurationDependencies, dependencySb.ToString())}"); 103 } 104 105 var settings = unit.Settings; 106 if (settings.Count > 0) 107 { 108 sb.AppendLine($" {Resources.ConfigurationSettings}"); 109 this.AppendValueSet(ref sb, settings, 4); 110 } 111 112 return sb.ToString(); 113 } 114 115 // -- Sample output when IConfigurationUnitProcessorDetails present -- 116 // Intent :: UnitName <from details> [Identifier] 117 // UnitDocumentationUri <if present> 118 // Description <from details first, directives second> 119 // "Module": ModuleName "by" Author / Publisher (IsLocal / ModuleSource) 120 // "Signed by": SigningCertificateChain (leaf subject CN) 121 // PublishedModuleUri / ModuleDocumentationUri <if present> 122 // ModuleDescription 123 private void CreateInformationWithDetails(ref StringBuilder sb, IConfigurationUnitProcessorDetails details, ValueSet directives) 124 { 125 var unitDocumentationUri = details.UnitDocumentationUri; 126 if (unitDocumentationUri != null) 127 { 128 sb.AppendLine($" {unitDocumentationUri.AbsoluteUri}"); 129 } 130 131 var unitDescriptionFromDetails = details.UnitDescription; 132 if (!string.IsNullOrEmpty(unitDescriptionFromDetails)) 133 { 134 sb.AppendLine($" {unitDescriptionFromDetails}"); 135 } 136 else 137 { 138 var unitDescriptionFromDirectives = directives.TryGetStringValue(Description); 139 if (!string.IsNullOrEmpty(unitDescriptionFromDirectives)) 140 { 141 sb.AppendLine($" {unitDescriptionFromDirectives}"); 142 } 143 } 144 145 var author = details.Author; 146 if (string.IsNullOrEmpty(author)) 147 { 148 author = details.Publisher; 149 } 150 151 if (details.IsLocal) 152 { 153 sb.AppendLine($" {string.Format(Resources.ConfigurationModuleWithDetails, details.ModuleName, author, Resources.ConfigurationLocal)}"); 154 } 155 else 156 { 157 sb.AppendLine($" {string.Format(Resources.ConfigurationModuleWithDetails, details.ModuleName, author, details.ModuleSource)}"); 158 } 159 160 // TODO: see signature information in ConfigurationFlow.cpp 161 var moduleUri = details.PublishedModuleUri; 162 if (moduleUri == null) 163 { 164 moduleUri = details.ModuleDocumentationUri; 165 } 166 167 if (moduleUri != null) 168 { 169 sb.AppendLine($" {moduleUri.AbsoluteUri}"); 170 } 171 172 var moduleDescription = details.ModuleDescription; 173 if (!string.IsNullOrEmpty(moduleDescription)) 174 { 175 sb.AppendLine($" {moduleDescription}"); 176 } 177 } 178 179 // -- Sample output when no IConfigurationUnitProcessorDetails present -- 180 // Intent :: UnitName <from unit> [identifier] 181 // Description (from directives) 182 // "Module": module <directive> 183 private void CreateInformationWithoutDetails(ref StringBuilder sb, ValueSet directives) 184 { 185 var unitDescriptionFromDirectives = directives.TryGetStringValue(Description); 186 if (!string.IsNullOrEmpty(unitDescriptionFromDirectives)) 187 { 188 sb.AppendLine($" {unitDescriptionFromDirectives}"); 189 } 190 191 var unitModuleFromDirectives = directives.TryGetStringValue(Module); 192 if (!string.IsNullOrEmpty(unitModuleFromDirectives)) 193 { 194 sb.AppendLine($" {string.Format(Resources.ConfigurationModuleNameOnly, unitModuleFromDirectives)}"); 195 } 196 } 197 198 private void AppendValueSet(ref StringBuilder sb, ValueSet valueSet, int indent) 199 { 200 var indentString = new string(' ', indent); 201 202 foreach (var value in valueSet) 203 { 204 sb.Append($"{indentString}{value.Key}:"); 205 206 // Can't use IPropertyValue here... 207 var obj = value.Value; 208 var innerValueSet = obj as ValueSet; 209 if (innerValueSet != null) 210 { 211 sb.AppendLine(); 212 if (innerValueSet.ContainsKey(TreatAsArray)) 213 { 214 this.AppendValueSetAsArray(ref sb, innerValueSet, indent + 2); 215 } 216 else 217 { 218 this.AppendValueSet(ref sb, innerValueSet, indent + 2); 219 } 220 } 221 else 222 { 223 this.AppendPropertyValue(ref sb, obj); 224 } 225 } 226 } 227 228 private void AppendValueSetAsArray(ref StringBuilder sb, ValueSet valueSet, int indent) 229 { 230 var indentString = new string(' ', indent); 231 232 var sortedList = new SortedList<int, object>(); 233 234 foreach (var keyValuePair in valueSet) 235 { 236 if (keyValuePair.Key != TreatAsArray) 237 { 238 if (int.TryParse(keyValuePair.Key, out int key)) 239 { 240 sortedList.Add(key, keyValuePair.Value); 241 } 242 else 243 { 244 throw new InvalidOperationException(keyValuePair.Key); 245 } 246 } 247 } 248 249 foreach (var arrayValue in sortedList) 250 { 251 sb.Append($"{indentString}-"); 252 var obj = arrayValue.Value; 253 254 var innerValueSet = obj as ValueSet; 255 if (innerValueSet == null) 256 { 257 this.AppendPropertyValue(ref sb, obj); 258 } 259 else 260 { 261 var size = innerValueSet.Count; 262 if (size > 0) 263 { 264 // First one is special. 265 var first = innerValueSet.First(); 266 sb.Append($" {first.Key}:"); 267 268 var firstValueSet = first.Value as ValueSet; 269 if (firstValueSet == null) 270 { 271 this.AppendPropertyValue(ref sb, first.Value); 272 } 273 else 274 { 275 sb.AppendLine(); 276 this.AppendValueSet(ref sb, firstValueSet, indent + 4); 277 } 278 279 if (size > 1) 280 { 281 innerValueSet.Remove(first.Key); 282 this.AppendValueSet(ref sb, innerValueSet, indent + 2); 283 innerValueSet.Add(first); 284 } 285 } 286 } 287 } 288 } 289 290 private void AppendPropertyValue(ref StringBuilder sb, object value) 291 { 292 Type type = value.GetType(); 293 if (type == typeof(string)) 294 { 295 sb.AppendLine($" {(string)value}"); 296 } 297 else if (type == typeof(bool)) 298 { 299 string message = (bool)value ? "true" : "false"; 300 sb.AppendLine($" {message}"); 301 } 302 else if (type == typeof(long)) 303 { 304 sb.AppendLine($" {(long)value}"); 305 } 306 else 307 { 308 sb.AppendLine($" [Debug:PropertyType={type}]"); 309 } 310 } 311 312 private string IntentToString(ConfigurationUnitIntent intent) 313 { 314 return intent switch 315 { 316 ConfigurationUnitIntent.Assert => Resources.ConfigurationAssert, 317 ConfigurationUnitIntent.Inform => Resources.ConfigurationInform, 318 ConfigurationUnitIntent.Apply => Resources.ConfigurationApply, 319 _ => string.Empty, 320 }; 321 } 322 } 323 }