MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.IO;7using System.Management.Automation.Internal;8using System.Text;9using System.Threading;10 11using Newtonsoft.Json;12using Newtonsoft.Json.Linq;13 14namespace System.Management.Automation.Configuration15{16 /// <summary>17 /// The scope of the configuration file.18 /// </summary>19 public enum ConfigScope20 {21 /// <summary>22 /// AllUsers configuration applies to all users.23 /// </summary>24 AllUsers = 0,25 26 /// <summary>27 /// CurrentUser configuration applies to the current user.28 /// </summary>29 CurrentUser = 130 }31 32 /// <summary>33 /// Reads from and writes to the JSON configuration files.34 /// The config values were originally stored in the Windows registry.35 /// </summary>36 /// <remarks>37 /// The config file access APIs are designed to avoid hitting the disk as much as possible.38 /// - For the first read request targeting a config file, the config data is read from the file and then cached as a 'JObject' instance;39 /// * the first read request happens very early during the startup of 'pwsh'.40 /// - For the subsequent read requests targeting the same config file, they will then work with that 'JObject' instance;41 /// - For the write request targeting a config file, the cached config data corresponding to that config file will be refreshed after the write operation is successfully done.42 ///43 /// To summarize the expected behavior:44 /// Once a 'pwsh' process starts up -45 /// 1. any changes made to the config file from outside this 'pwsh' process is not guaranteed to be seen by it (most likely won't be seen).46 /// 2. any changes to the config file by this 'pwsh' process via the config file access APIs will be seen by it, if it chooses to read those changes afterwards.47 /// </remarks>48 internal sealed class PowerShellConfig49 {50 private const string ConfigFileName = "powershell.config.json";51 private const string ExecutionPolicyDefaultShellKey = "Microsoft.PowerShell:ExecutionPolicy";52 private const string DisableImplicitWinCompatKey = "DisableImplicitWinCompat";53 private const string WindowsPowerShellCompatibilityModuleDenyListKey = "WindowsPowerShellCompatibilityModuleDenyList";54 private const string WindowsPowerShellCompatibilityNoClobberModuleListKey = "WindowsPowerShellCompatibilityNoClobberModuleList";55 56 // Provide a singleton57 internal static readonly PowerShellConfig Instance = new PowerShellConfig();58 59 // The json file containing system-wide configuration settings.60 // When passed as a pwsh command-line option, overrides the system wide configuration file.61 private string systemWideConfigFile;62 private string systemWideConfigDirectory;63 64 // The json file containing the per-user configuration settings.65 private readonly string perUserConfigFile;66 private readonly string perUserConfigDirectory;67 68 // Note: JObject and JsonSerializer are thread safe.69 // Root Json objects corresponding to the configuration file for 'AllUsers' and 'CurrentUser' respectively.70 // They are used as a cache to avoid hitting the disk for every read operation.71 private readonly JObject[] configRoots;72 private readonly JObject emptyConfig;73 private readonly JsonSerializer serializer;74 75 /// <summary>76 /// Lock used to enable multiple concurrent readers and singular write locks within a single process.77 /// TODO: This solution only works for IO from a single process.78 /// A more robust solution is needed to enable ReaderWriterLockSlim behavior between processes.79 /// </summary>80 private readonly ReaderWriterLockSlim fileLock;81 82 private PowerShellConfig()83 {84 // Sets the system-wide configuration file.85 systemWideConfigDirectory = Utils.DefaultPowerShellAppBase;86 systemWideConfigFile = Path.Combine(systemWideConfigDirectory, ConfigFileName);87 88 // Sets the per-user configuration directory89 // Note: This directory may or may not exist depending upon the execution scenario.90 // Writes will attempt to create the directory if it does not already exist.91 perUserConfigDirectory = Platform.ConfigDirectory;92 if (!string.IsNullOrEmpty(perUserConfigDirectory))93 {94 perUserConfigFile = Path.Combine(perUserConfigDirectory, ConfigFileName);95 }96 97 emptyConfig = new JObject();98 configRoots = new JObject[2];99 serializer = JsonSerializer.Create(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, MaxDepth = 10 });100 101 fileLock = new ReaderWriterLockSlim();102 }103 104 private string GetConfigFilePath(ConfigScope scope)105 {106 return (scope == ConfigScope.CurrentUser) ? perUserConfigFile : systemWideConfigFile;107 }108 109 /// <summary>110 /// Sets the system wide configuration file path.111 /// </summary>112 /// <param name="value">A fully qualified path to the system wide configuration file.</param>113 /// <exception cref="FileNotFoundException"><paramref name="value"/> is a null reference or the associated file does not exist.</exception>114 /// <remarks>115 /// This method is for use when processing the -SettingsFile configuration setting and should not be used for any other purpose.116 /// </remarks>117 internal void SetSystemConfigFilePath(string value)118 {119 if (!string.IsNullOrEmpty(value) && !File.Exists(value))120 {121 throw new FileNotFoundException(value);122 }123 124 FileInfo info = new FileInfo(value);125 systemWideConfigFile = info.FullName;126 systemWideConfigDirectory = info.Directory.FullName;127 }128 129 /// <summary>130 /// Existing Key = HKLM:\System\CurrentControlSet\Control\Session Manager\Environment131 /// Proposed value = %ProgramFiles%\PowerShell\Modules by default132 /// Note: There is no setter because this value is immutable.133 /// </summary>134 /// <param name="scope">Whether this is a system-wide or per-user setting.</param>135 /// <returns>Value if found, null otherwise. The behavior matches ModuleIntrinsics.GetExpandedEnvironmentVariable().</returns>136 internal string GetModulePath(ConfigScope scope)137 {138 string modulePath = ReadValueFromFile<string>(scope, Constants.PSModulePathEnvVar);139 if (!string.IsNullOrEmpty(modulePath))140 {141 modulePath = Environment.ExpandEnvironmentVariables(modulePath);142 }143 144 return modulePath;145 }146 147 /// <summary>148 /// Existing Key = HKCU and HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell149 /// Proposed value = Existing default execution policy if not already specified150 ///151 /// Schema:152 /// {153 /// "shell-ID-string:ExecutionPolicy" : "execution policy string"154 /// }155 ///156 /// TODO: In a single config file, it might be better to nest this. It is unnecessary complexity until a need arises for more nested values.157 /// </summary>158 /// <param name="scope">Whether this is a system-wide or per-user setting.</param>159 /// <param name="shellId">The shell associated with this policy. Typically, it is "Microsoft.PowerShell".</param>160 /// <returns>The execution policy if found. Null otherwise.</returns>161 internal string GetExecutionPolicy(ConfigScope scope, string shellId)162 {163 string key = GetExecutionPolicySettingKey(shellId);164 string execPolicy = ReadValueFromFile<string>(scope, key);165 return string.IsNullOrEmpty(execPolicy) ? null : execPolicy;166 }167 168 internal void RemoveExecutionPolicy(ConfigScope scope, string shellId)169 {170 string key = GetExecutionPolicySettingKey(shellId);171 RemoveValueFromFile<string>(scope, key);172 }173 174 internal void SetExecutionPolicy(ConfigScope scope, string shellId, string executionPolicy)175 {176 string key = GetExecutionPolicySettingKey(shellId);177 WriteValueToFile<string>(scope, key, executionPolicy);178 }179 180 private static string GetExecutionPolicySettingKey(string shellId)181 {182 return string.Equals(shellId, Utils.DefaultPowerShellShellID, StringComparison.Ordinal)183 ? ExecutionPolicyDefaultShellKey184 : string.Concat(shellId, ":", "ExecutionPolicy");185 }186 187 /// <summary>188 /// Get the names of experimental features enabled in the config file.189 /// </summary>190 internal string[] GetExperimentalFeatures()191 {192 string[] features = ReadValueFromFile(ConfigScope.CurrentUser, "ExperimentalFeatures", Array.Empty<string>());193 194 if (features.Length == 0)195 {196 features = ReadValueFromFile(ConfigScope.AllUsers, "ExperimentalFeatures", Array.Empty<string>());197 }198 199 return features;200 }201 202 /// <summary>203 /// Set the enabled list of experimental features in the config file.204 /// </summary>205 /// <param name="scope">The ConfigScope of the configuration file to update.</param>206 /// <param name="featureName">The name of the experimental feature to change in the configuration.</param>207 /// <param name="setEnabled">If true, add to configuration; otherwise, remove from configuration.</param>208 internal void SetExperimentalFeatures(ConfigScope scope, string featureName, bool setEnabled)209 {210 var features = new List<string>(GetExperimentalFeatures());211 bool containsFeature = features.Contains(featureName);212 if (setEnabled && !containsFeature)213 {214 features.Add(featureName);215 WriteValueToFile<string[]>(scope, "ExperimentalFeatures", features.ToArray());216 }217 else if (!setEnabled && containsFeature)218 {219 features.Remove(featureName);220 WriteValueToFile<string[]>(scope, "ExperimentalFeatures", features.ToArray());221 }222 }223 224 internal bool IsImplicitWinCompatEnabled()225 {226 bool settingValue = ReadValueFromFile<bool?>(ConfigScope.CurrentUser, DisableImplicitWinCompatKey)227 ?? ReadValueFromFile<bool?>(ConfigScope.AllUsers, DisableImplicitWinCompatKey)228 ?? false;229 230 return !settingValue;231 }232 233 internal string[] GetWindowsPowerShellCompatibilityModuleDenyList()234 {235 return ReadValueFromFile<string[]>(ConfigScope.CurrentUser, WindowsPowerShellCompatibilityModuleDenyListKey)236 ?? ReadValueFromFile<string[]>(ConfigScope.AllUsers, WindowsPowerShellCompatibilityModuleDenyListKey);237 }238 239 internal string[] GetWindowsPowerShellCompatibilityNoClobberModuleList()240 {241 return ReadValueFromFile<string[]>(ConfigScope.CurrentUser, WindowsPowerShellCompatibilityNoClobberModuleListKey)242 ?? ReadValueFromFile<string[]>(ConfigScope.AllUsers, WindowsPowerShellCompatibilityNoClobberModuleListKey);243 }244 245 /// <summary>246 /// Corresponding settings of the original Group Policies.247 /// </summary>248 internal PowerShellPolicies GetPowerShellPolicies(ConfigScope scope)249 {250 return ReadValueFromFile<PowerShellPolicies>(scope, nameof(PowerShellPolicies));251 }252 253#if UNIX254 /// <summary>255 /// Gets the identity name to use for writing to syslog.256 /// </summary>257 /// <returns>258 /// The string identity to use for writing to syslog. The default value is 'powershell'.259 /// </returns>260 internal string GetSysLogIdentity()261 {262 string identity = ReadValueFromFile<string>(ConfigScope.AllUsers, "LogIdentity");263 264 if (string.IsNullOrEmpty(identity) ||265 identity.Equals(LogDefaultValue, StringComparison.OrdinalIgnoreCase))266 {267 identity = "powershell";268 }269 270 return identity;271 }272 273 /// <summary>274 /// Gets the log level filter.275 /// </summary>276 /// <returns>277 /// One of the PSLevel values indicating the level to log. The default value is PSLevel.Informational.278 /// </returns>279 internal PSLevel GetLogLevel()280 {281 string levelName = ReadValueFromFile<string>(ConfigScope.AllUsers, "LogLevel");282 PSLevel level;283 284 if (string.IsNullOrEmpty(levelName) ||285 levelName.Equals(LogDefaultValue, StringComparison.OrdinalIgnoreCase) ||286 !Enum.TryParse<PSLevel>(levelName, true, out level))287 {288 level = PSLevel.Informational;289 }290 291 return level;292 }293 294 /// <summary>295 /// The supported separator characters for listing channels and keywords in configuration.296 /// </summary>297 private static readonly char[] s_valueSeparators = new char[] {' ', ',', '|'};298 299 /// <summary>300 /// Provides a string name to indicate the default for a configuration setting.301 /// </summary>302 private const string LogDefaultValue = "default";303 304 /// <summary>305 /// Gets the bitmask of the PSChannel values to log.306 /// </summary>307 /// <returns>308 /// A bitmask of PSChannel.Operational and/or PSChannel.Analytic. The default value is PSChannel.Operational.309 /// </returns>310 internal PSChannel GetLogChannels()311 {312 string values = ReadValueFromFile<string>(ConfigScope.AllUsers, "LogChannels");313 314 PSChannel result = 0;315 if (!string.IsNullOrEmpty(values))316 {317 string[] names = values.Split(s_valueSeparators, StringSplitOptions.RemoveEmptyEntries);318 319 foreach (string name in names)320 {321 if (name.Equals(LogDefaultValue, StringComparison.OrdinalIgnoreCase))322 {323 result = 0;324 break;325 }326 327 PSChannel value;328 if (Enum.TryParse<PSChannel>(name, true, out value))329 {330 result |= value;331 }332 }333 }334 335 if (result == 0)336 {337 result = System.Management.Automation.Tracing.PSSysLogProvider.DefaultChannels;338 }339 340 return result;341 }342 343 /// <summary>344 /// Gets the bitmask of keywords to log.345 /// </summary>346 /// <returns>347 /// A bitmask of PSKeyword values. The default value is all keywords other than UseAlwaysAnalytic.348 /// </returns>349 internal PSKeyword GetLogKeywords()350 {351 string values = ReadValueFromFile<string>(ConfigScope.AllUsers, "LogKeywords");352 353 PSKeyword result = 0;354 if (!string.IsNullOrEmpty(values))355 {356 string[] names = values.Split(s_valueSeparators, StringSplitOptions.RemoveEmptyEntries);357 358 foreach (string name in names)359 {360 if (name.Equals(LogDefaultValue, StringComparison.OrdinalIgnoreCase))361 {362 result = 0;363 break;364 }365 366 PSKeyword value;367 if (Enum.TryParse<PSKeyword>(name, true, out value))368 {369 result |= value;370 }371 }372 }373 374 if (result == 0)375 {376 result = System.Management.Automation.Tracing.PSSysLogProvider.DefaultKeywords;377 }378 379 return result;380 }381#endif // UNIX382 383 /// <summary>384 /// Read a value from the configuration file.385 /// </summary>386 /// <typeparam name="T">The type of the value</typeparam>387 /// <param name="scope">The ConfigScope of the configuration file to update.</param>388 /// <param name="key">The string key of the value.</param>389 /// <param name="defaultValue">The default value to return if the key is not present.</param>390 private T ReadValueFromFile<T>(ConfigScope scope, string key, T defaultValue = default)391 {392 string fileName = GetConfigFilePath(scope);393 if (string.IsNullOrEmpty(fileName))394 {395 return defaultValue;396 }397 398 JObject configData = configRoots[(int)scope];399 400 if (configData == null)401 {402 if (File.Exists(fileName))403 {404 try405 {406 // Open file for reading, but allow multiple readers407 fileLock.EnterReadLock();408 409 using var stream = OpenFileStreamWithRetry(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);410 using var jsonReader = new JsonTextReader(new StreamReader(stream));411 412 configData = serializer.Deserialize<JObject>(jsonReader) ?? emptyConfig;413 }414 catch (Exception exc)415 {416 throw PSTraceSource.NewInvalidOperationException(exc, PSConfigurationStrings.CanNotConfigurationFile, args: fileName);417 }418 finally419 {420 fileLock.ExitReadLock();421 }422 }423 else424 {425 configData = emptyConfig;426 }427 428 // Set the configuration cache.429 JObject originalValue = Interlocked.CompareExchange(ref configRoots[(int)scope], configData, null);430 if (originalValue != null)431 {432 configData = originalValue;433 }434 }435 436 if (configData != emptyConfig && configData.TryGetValue(key, StringComparison.OrdinalIgnoreCase, out JToken jToken))437 {438 return jToken.ToObject<T>(serializer) ?? defaultValue;439 }440 441 return defaultValue;442 }443 444 private static FileStream OpenFileStreamWithRetry(string fullPath, FileMode mode, FileAccess access, FileShare share)445 {446 const int MaxTries = 5;447 for (int numTries = 0; numTries < MaxTries; numTries++)448 {449 try450 {451 return new FileStream(fullPath, mode, access, share);452 }453 catch (IOException)454 {455 if (numTries == (MaxTries - 1))456 {457 throw;458 }459 460 Thread.Sleep(50);461 }462 }463 464 throw new IOException(nameof(OpenFileStreamWithRetry));465 }466 467 /// <summary>468 /// Update a value in the configuration file.469 /// </summary>470 /// <typeparam name="T">The type of the value</typeparam>471 /// <param name="scope">The ConfigScope of the configuration file to update.</param>472 /// <param name="key">The string key of the value.</param>473 /// <param name="value">The value to set.</param>474 /// <param name="addValue">Whether the key-value pair should be added to or removed from the file.</param>475 private void UpdateValueInFile<T>(ConfigScope scope, string key, T value, bool addValue)476 {477 try478 {479 string fileName = GetConfigFilePath(scope);480 fileLock.EnterWriteLock();481 482 // Since multiple properties can be in a single file, replacement is required instead of overwrite if a file already exists.483 // Handling the read and write operations within a single FileStream prevents other processes from reading or writing the file while484 // the update is in progress. It also locks out readers during write operations.485 486 JObject jsonObject = null;487 using FileStream fs = OpenFileStreamWithRetry(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);488 489 // UTF8, BOM detection, and bufferSize are the same as the basic stream constructor.490 // The most important parameter here is the last one, which keeps underlying stream open after StreamReader is disposed491 // so that it can be reused for the subsequent write operation.492 using (StreamReader streamRdr = new StreamReader(fs, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true))493 using (JsonTextReader jsonReader = new JsonTextReader(streamRdr))494 {495 // Safely determines whether there is content to read from the file496 bool isReadSuccess = jsonReader.Read();497 if (isReadSuccess)498 {499 // Read the stream into a root JObject for manipulation500 jsonObject = serializer.Deserialize<JObject>(jsonReader);501 JProperty propertyToModify = jsonObject.Property(key);502 503 if (propertyToModify == null)504 {505 // The property doesn't exist, so add it506 if (addValue)507 {508 jsonObject.Add(new JProperty(key, value));509 }510 // else the property doesn't exist so there is nothing to remove511 }512 else513 {514 // The property exists515 if (addValue)516 {517 propertyToModify.Replace(new JProperty(key, value));518 }519 else520 {521 propertyToModify.Remove();522 }523 }524 }525 else526 {527 // The file doesn't already exist and we want to write to it or it exists with no content.528 // A new file will be created that contains only this value.529 // If the file doesn't exist and a we don't want to write to it, no action is needed.530 if (addValue)531 {532 jsonObject = new JObject(new JProperty(key, value));533 }534 else535 {536 return;537 }538 }539 }540 541 // Reset the stream position to the beginning so that the542 // changes to the file can be written to disk543 fs.Seek(0, SeekOrigin.Begin);544 545 // Update the file with new content546 using (StreamWriter streamWriter = new StreamWriter(fs))547 using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter))548 {549 // The entire document exists within the root JObject.550 // I just need to write that object to produce the document.551 jsonObject.WriteTo(jsonWriter);552 553 // This trims the file if the file shrank. If the file grew,554 // it is a no-op. The purpose is to trim extraneous characters555 // from the file stream when the resultant JObject is smaller556 // than the input JObject.557 fs.SetLength(fs.Position);558 }559 560 // Refresh the configuration cache.561 Interlocked.Exchange(ref configRoots[(int)scope], jsonObject);562 }563 finally564 {565 fileLock.ExitWriteLock();566 }567 }568 569 /// <summary>570 /// TODO: Should this return success, fail, or throw?571 /// </summary>572 /// <typeparam name="T">The type of value to write.</typeparam>573 /// <param name="scope">The ConfigScope of the file to update.</param>574 /// <param name="key">The string key of the value.</param>575 /// <param name="value">The value to write.</param>576 private void WriteValueToFile<T>(ConfigScope scope, string key, T value)577 {578 if (scope == ConfigScope.CurrentUser && !Directory.Exists(perUserConfigDirectory))579 {580 Directory.CreateDirectory(perUserConfigDirectory);581 }582 583 UpdateValueInFile<T>(scope, key, value, true);584 }585 586 /// <summary>587 /// TODO: Should this return success, fail, or throw?588 /// </summary>589 /// <typeparam name="T">The type of value to remove.</typeparam>590 /// <param name="scope">The ConfigScope of the file to update.</param>591 /// <param name="key">The string key of the value.</param>592 private void RemoveValueFromFile<T>(ConfigScope scope, string key)593 {594 string fileName = GetConfigFilePath(scope);595 // Optimization: If the file doesn't exist, there is nothing to remove596 if (File.Exists(fileName))597 {598 UpdateValueInFile<T>(scope, key, default(T), false);599 }600 }601 }602 603 #region GroupPolicy Configs604 605 /// <summary>606 /// The GroupPolicy related settings used in PowerShell are as follows in Registry:607 /// - Software\Policies\Microsoft\PowerShellCore -- { EnableScripts (0 or 1); ExecutionPolicy (string) }608 /// SubKeys Name-Value-Pairs609 /// - ScriptBlockLogging { EnableScriptBlockLogging (0 or 1); EnableScriptBlockInvocationLogging (0 or 1) }610 /// - ModuleLogging { EnableModuleLogging (0 or 1); ModuleNames (string[]) }611 /// - Transcription { EnableTranscripting (0 or 1); OutputDirectory (string); EnableInvocationHeader (0 or 1) }612 /// - UpdatableHelp { DefaultSourcePath (string) }613 /// - ConsoleSessionConfiguration { EnableConsoleSessionConfiguration (0 or 1); ConsoleSessionConfigurationName (string) }614 /// - Software\Policies\Microsoft\Windows\EventLog615 /// SubKeys Name-Value-Pairs616 /// - ProtectedEventLogging { EnableProtectedEventLogging (0 or 1); EncryptionCertificate (string[]) }617 ///618 /// The JSON representation is in sync with the 'PowerShellPolicies' type. Here is an example:619 /// {620 /// "PowerShellPolicies": {621 /// "ScriptExecution": {622 /// "ExecutionPolicy": "RemoteSigned"623 /// },624 /// "ScriptBlockLogging": {625 /// "EnableScriptBlockInvocationLogging": true,626 /// "EnableScriptBlockLogging": false627 /// },628 /// "ProtectedEventLogging": {629 /// "EnableProtectedEventLogging": false,630 /// "EncryptionCertificate": [631 /// "Joe"632 /// ]633 /// },634 /// "Transcription": {635 /// "EnableTranscripting": true,636 /// "EnableInvocationHeader": true,637 /// "OutputDirectory": "c:\\tmp"638 /// },639 /// "UpdatableHelp": {640 /// "DefaultSourcePath": "f:\\temp"641 /// },642 /// "ConsoleSessionConfiguration": {643 /// "EnableConsoleSessionConfiguration": true,644 /// "ConsoleSessionConfigurationName": "name"645 /// }646 /// }647 /// }648 /// </summary>649 internal sealed class PowerShellPolicies650 {651 public ScriptExecution ScriptExecution { get; set; }652 653 public ScriptBlockLogging ScriptBlockLogging { get; set; }654 655 public ModuleLogging ModuleLogging { get; set; }656 657 public ProtectedEventLogging ProtectedEventLogging { get; set; }658 659 public Transcription Transcription { get; set; }660 661 public UpdatableHelp UpdatableHelp { get; set; }662 663 public ConsoleSessionConfiguration ConsoleSessionConfiguration { get; set; }664 }665 666 internal abstract class PolicyBase { }667 668 /// <summary>669 /// Setting about ScriptExecution.670 /// </summary>671 internal sealed class ScriptExecution : PolicyBase672 {673 public string ExecutionPolicy { get; set; }674 675 public bool? EnableScripts { get; set; }676 }677 678 /// <summary>679 /// Setting about ScriptBlockLogging.680 /// </summary>681 internal sealed class ScriptBlockLogging : PolicyBase682 {683 public bool? EnableScriptBlockInvocationLogging { get; set; }684 685 public bool? EnableScriptBlockLogging { get; set; }686 }687 688 /// <summary>689 /// Setting about ModuleLogging.690 /// </summary>691 internal sealed class ModuleLogging : PolicyBase692 {693 public bool? EnableModuleLogging { get; set; }694 695 public string[] ModuleNames { get; set; }696 }697 698 /// <summary>699 /// Setting about Transcription.700 /// </summary>701 internal sealed class Transcription : PolicyBase702 {703 public bool? EnableTranscripting { get; set; }704 705 public bool? EnableInvocationHeader { get; set; }706 707 public string OutputDirectory { get; set; }708 }709 710 /// <summary>711 /// Setting about UpdatableHelp.712 /// </summary>713 internal sealed class UpdatableHelp : PolicyBase714 {715 public bool? EnableUpdateHelpDefaultSourcePath { get; set; }716 717 public string DefaultSourcePath { get; set; }718 }719 720 /// <summary>721 /// Setting about ConsoleSessionConfiguration.722 /// </summary>723 internal sealed class ConsoleSessionConfiguration : PolicyBase724 {725 public bool? EnableConsoleSessionConfiguration { get; set; }726 727 public string ConsoleSessionConfigurationName { get; set; }728 }729 730 /// <summary>731 /// Setting about ProtectedEventLogging.732 /// </summary>733 internal sealed class ProtectedEventLogging : PolicyBase734 {735 public bool? EnableProtectedEventLogging { get; set; }736 737 public string[] EncryptionCertificate { get; set; }738 }739 740 #endregion741}742 