ConfigurationUnitInternal.cs (10143B)
1 // ----------------------------------------------------------------------------- 2 // <copyright file="ConfigurationUnitInternal.cs" company="Microsoft Corporation"> 3 // Copyright (c) Microsoft Corporation. Licensed under the MIT License. 4 // </copyright> 5 // ----------------------------------------------------------------------------- 6 7 namespace Microsoft.Management.Configuration.Processor.Helpers 8 { 9 using System; 10 using System.Collections.Generic; 11 using System.Diagnostics.CodeAnalysis; 12 using System.IO; 13 using Microsoft.Management.Configuration.Processor.Constants; 14 using Microsoft.Management.Configuration.Processor.Exceptions; 15 using Windows.Foundation.Collections; 16 17 /// <summary> 18 /// Wrapper around Configuration units and its directives. 19 /// Creates a normalized directives map for consumption. 20 /// </summary> 21 internal class ConfigurationUnitInternal 22 { 23 private const string ConfigRootVar = "${WinGetConfigRoot}"; 24 25 private readonly string? configurationFileRootPath = null; 26 private readonly Dictionary<string, object> normalizedDirectives = new (); 27 28 /// <summary> 29 /// Initializes a new instance of the <see cref="ConfigurationUnitInternal"/> class. 30 /// </summary> 31 /// <param name="unit">Configuration unit.</param> 32 /// <param name="configurationFilePath">The configuration file path.</param> 33 public ConfigurationUnitInternal( 34 ConfigurationUnit unit, 35 string? configurationFilePath) 36 { 37 this.Unit = unit; 38 this.InitializeDirectives(); 39 this.InitializeNames(); 40 41 if (!string.IsNullOrEmpty(configurationFilePath)) 42 { 43 if (!File.Exists(configurationFilePath)) 44 { 45 throw new FileNotFoundException(configurationFilePath); 46 } 47 48 this.configurationFileRootPath = Path.GetDirectoryName(configurationFilePath); 49 } 50 } 51 52 /// <summary> 53 /// Gets the configuration unit. 54 /// </summary> 55 public ConfigurationUnit Unit { get; } 56 57 /// <summary> 58 /// Gets a value indicating whether the unit type should be treated as the resource name. 59 /// </summary> 60 public bool UnitTypeIsResourceName { get; init; } = false; 61 62 /// <summary> 63 /// Gets the resource name *only*. For example, "Resource". 64 /// </summary> 65 public string ResourceName { get; private set; } 66 67 /// <summary> 68 /// Gets the qualified name, which includes the module. For example, "Module/Resource". 69 /// </summary> 70 public string QualifiedName { get; private set; } 71 72 /// <summary> 73 /// Gets the directive value from the unit taking into account the directives overlay. 74 /// </summary> 75 /// <param name="directiveName">Directive name.</param> 76 /// <returns>Value of directive, null if not found.</returns> 77 /// <typeparam name="TType">Directive type value.</typeparam> 78 public TType? GetDirective<TType>(string directiveName) 79 where TType : class 80 { 81 var normalizedDirectiveName = StringHelpers.Normalize(directiveName); 82 if (this.normalizedDirectives.TryGetValue(normalizedDirectiveName, out object? value)) 83 { 84 return value as TType; 85 } 86 87 return null; 88 } 89 90 /// <summary> 91 /// Gets the bool value of a directive from the unit taking into account the directives overlay. 92 /// </summary> 93 /// <param name="directiveName">Directive name.</param> 94 /// <returns>Value of directive, false if not found.</returns> 95 public bool? GetDirective(string directiveName) 96 { 97 var normalizedDirectiveName = StringHelpers.Normalize(directiveName); 98 if (this.normalizedDirectives.TryGetValue(normalizedDirectiveName, out object? value)) 99 { 100 return value as bool?; 101 } 102 103 return null; 104 } 105 106 /// <summary> 107 /// Gets the semantic version, if any. 108 /// </summary> 109 /// <returns>SemanticVersion, null if not specified.</returns> 110 public SemanticVersion? GetSemanticVersion() 111 { 112 string? semanticVersion = this.GetDirective<string>(DirectiveConstants.Version); 113 if (!string.IsNullOrWhiteSpace(semanticVersion)) 114 { 115 return new SemanticVersion(semanticVersion); 116 } 117 118 return null; 119 } 120 121 /// <summary> 122 /// Gets the semantic min version, if any. 123 /// </summary> 124 /// <returns>SemanticVersion, null if not specified.</returns> 125 public SemanticVersion? GetSemanticMinVersion() 126 { 127 string? semanticVersion = this.GetDirective<string>(DirectiveConstants.MinVersion); 128 if (!string.IsNullOrWhiteSpace(semanticVersion)) 129 { 130 return new SemanticVersion(semanticVersion); 131 } 132 133 return null; 134 } 135 136 /// <summary> 137 /// Gets the semantic max version, if any. 138 /// </summary> 139 /// <returns>SemanticVersion, null if not specified.</returns> 140 public SemanticVersion? GetSemanticMaxVersion() 141 { 142 string? semanticVersion = this.GetDirective<string>(DirectiveConstants.MaxVersion); 143 if (!string.IsNullOrWhiteSpace(semanticVersion)) 144 { 145 return new SemanticVersion(semanticVersion); 146 } 147 148 return null; 149 } 150 151 /// <summary> 152 /// TODO: Implement for more variables. 153 /// I am so sad because rs.SessionStateProxy.InvokeCommand.ExpandString doesn't work as I wanted. 154 /// PowerShell assumes all code passed to ExpandString is trusted and we cannot assume that. 155 /// </summary> 156 /// <returns>ValueSet with settings.</returns> 157 public ValueSet GetExpandedSettings() 158 { 159 var valueSet = new ValueSet(); 160 foreach (var value in this.Unit.Settings) 161 { 162 if (value.Value is string) 163 { 164 // For now, we just expand config root. 165 valueSet.Add(value.Key, this.ExpandConfigRoot(value.Value as string, value.Key)); 166 } 167 else 168 { 169 valueSet.Add(value); 170 } 171 } 172 173 return valueSet; 174 } 175 176 private string? ExpandConfigRoot(string? value, string settingName) 177 { 178 if (!string.IsNullOrEmpty(value)) 179 { 180 // TODO: since we only support one variable, this only finds and replace 181 // ${WingetConfigRoot} if found in the string when the work of expanding 182 // string is done it should take into account other operators like the subexpression operator $() 183 if (value.Contains(ConfigRootVar, StringComparison.OrdinalIgnoreCase)) 184 { 185 if (string.IsNullOrEmpty(this.configurationFileRootPath)) 186 { 187 throw new UnitSettingConfigRootException(this.QualifiedName, settingName); 188 } 189 190 return value.Replace(ConfigRootVar, this.configurationFileRootPath, StringComparison.OrdinalIgnoreCase); 191 } 192 } 193 194 return value; 195 } 196 197 private void InitializeDirectives() 198 { 199 foreach (var directive in this.Unit.Metadata) 200 { 201 var normalizedKey = StringHelpers.Normalize(directive.Key); 202 this.normalizedDirectives.Add(normalizedKey, directive.Value); 203 } 204 } 205 206 private string ConstructQualifiedName(string? moduleName) 207 { 208 return $"{(moduleName == null ? string.Empty : $"{moduleName}/")}{this.ResourceName}"; 209 } 210 211 [MemberNotNull(nameof(ResourceName), nameof(QualifiedName))] 212 private void InitializeNames() 213 { 214 // Determine ResourceName, QualifiedName, and the module directive 215 string unitType = this.Unit.Type; 216 string? moduleDirective = this.GetDirective<string>(DirectiveConstants.Module); 217 218 if (this.UnitTypeIsResourceName) 219 { 220 this.ResourceName = unitType; 221 this.QualifiedName = this.ConstructQualifiedName(moduleDirective); 222 return; 223 } 224 225 int unitTypeDividerPosition = unitType.IndexOf('/'); 226 227 if (unitTypeDividerPosition == unitType.Length - 1) 228 { 229 throw new ArgumentException($"Invalid unit Type: {unitType}"); 230 } 231 232 string? moduleName; 233 234 if (unitTypeDividerPosition == -1) 235 { 236 moduleName = moduleDirective; 237 this.ResourceName = unitType; 238 this.QualifiedName = this.ConstructQualifiedName(moduleDirective); 239 } 240 else 241 { 242 moduleName = unitType.Substring(0, unitTypeDividerPosition); 243 this.ResourceName = unitType.Substring(unitTypeDividerPosition + 1); 244 this.QualifiedName = unitType; 245 } 246 247 if (moduleName != null) 248 { 249 if (moduleDirective != null) 250 { 251 if (moduleName != moduleDirective) 252 { 253 throw new ArgumentException($"Mismatched module specifiers: {moduleName} != {moduleDirective}"); 254 } 255 } 256 else 257 { 258 this.normalizedDirectives.Add(DirectiveConstants.Module, moduleName); 259 } 260 } 261 } 262 } 263 }