From 4b8eff2817449b657390a42bd7a4b16d21b761ec Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Tue, 19 Jul 2016 14:37:20 -0700 Subject: [PATCH 01/12] Backing up in-progress changes to work on other stuff --- .../System.Management.Automation.csproj | 1 + .../engine/PropertyAccessor.cs | 581 ++++++++++++++++++ 2 files changed, 582 insertions(+) create mode 100644 src/System.Management.Automation/engine/PropertyAccessor.cs diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index bd8f4e67c..51ab6aaf7 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -390,6 +390,7 @@ + diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs new file mode 100644 index 000000000..e9b829992 --- /dev/null +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -0,0 +1,581 @@ +using System; +using System.Xml; +using System.IO; +using System.Text; +using System.Reflection; + +using System.Management.Automation; + +#if CORECLR +// Some APIs are missing from System.Environment. We use System.Management.Automation.Environment as a proxy type: +// - for missing APIs, System.Management.Automation.Environment has extension implementation. +// - for existing APIs, System.Management.Automation.Environment redirect the call to System.Environment. +using Environment = System.Management.Automation.Environment; +#endif + +// TODO: Add non-windows guard here or move to a different file +using Microsoft.Win32; + +namespace System.Management.Automation +{ + + /// + /// Leverages the strategy pattern to abstract away the details of gathering properties from outside sources. + /// Note: This is a class so that it can be internal. + /// + internal abstract class PropertyAccessor + { + /// + /// Describes the scope of the property query. + /// SystemWide properties apply to all users. + /// CurrentUser properties apply to the current user that is impersonated. + /// + internal enum PropertyScope + { + SystemWide = 0, + CurrentUser = 1 + } + + /// + /// Existing Key = HKLM:\System\CurrentControlSet\Control\Session Manager\Environment + /// Proposed value = %ProgramFiles%\PowerShell\Modules by default + /// + /// Note: There is no setter because this value is immutable. + /// + /// Module path values from the config file. + internal abstract string GetModulePath(); + + /// + /// Existing Key = HKCU and HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell + /// Proposed value = Existing default execution policy if not already specified + /// + /// The shell associated with this policy. Typically, it is "Microsoft.PowerShell" + /// + internal abstract string GetMachineExecutionPolicy(PropertyScope scope, string shellId); + internal abstract void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId); // TODO: Necessary? + internal abstract void AddMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy); // TODO: Necessary? + internal abstract void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy); + + /// + /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds + /// Proposed value = Existing value, otherwise 10. + /// + /// Max stack size in MB. If not set, defaults to 10 MB. + internal abstract int GetPipeLineMaxStackSizeMb(); + + internal abstract void SetPipeLineMaxStackSizeMb(int maxStackSize); + + /// + /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds + /// Proposed value = existing default. Probably "1" + /// + /// Whether console prompting should happen. + internal abstract Boolean GetConsolePrompting(); + internal abstract void SetConsolePrompting(Boolean shouldPrompt); + + /// + /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell + /// Proposed value = Existing default. Probably "0" + /// + /// Boolean indicating whether Update-Help should prompt + internal abstract Boolean GetDisablePromptToUpdateHelp(); + internal abstract void SetDisablePromptToUpdateHelp(Boolean prompt); + + /// + /// Existing Key = HKCU and HKLM\Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp + /// Proposed value = blank.This should be supported though + /// + /// + internal abstract string GetDefaultSourcePath(PropertyScope scope); + internal abstract void SetDefaultSourcePath(PropertyScope scope, string defaultPath); + } + + internal class PropertyAccessorFactory + { + /// + /// Template method to generate the appropriate accessor for a given environment. + /// TODO: Is it really necessary to make this Singleton-ish? + /// + /// + internal static PropertyAccessor GetPropertyAccessor() + { + if (null == _activePropertyAccessor) + { +#if CORECLR + // TODO: I must differentiate between inbox PS and side-by-side PS here! + _activePropertyAccessor = new XmlConfigFileAccessor(); +#else + _activePropertyAccessor = new RegistryAccessor(); +#endif + } + return _activePropertyAccessor; + } + + private static PropertyAccessor _activePropertyAccessor; + } + + /// + /// JSON configuration file accessor + /// + /// Reads from and writes to configuration files. The values stored were + /// originally stored in the Windows registry. + /// + internal class JsonConfigFileAccessor : PropertyAccessor + { + private string psHomeConfigDirectory; + private static string configDirectoryName = "Configuration"; + private static string execPolicyFileName = "ExecutionPolicy.json"; + + internal JsonConfigFileAccessor() + { + // TODO: Use Utils.GetApplicationBase("Microsoft.PowerShell") instead? + Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly; + psHomeConfigDirectory = Path.Combine(Path.GetDirectoryName(ClrFacade.GetAssemblyLocation(assembly)), configDirectoryName); + + + } + + internal override string GetModulePath() + { + return string.Empty; + } + + /// + /// Existing Key = HKCU and HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell + /// Proposed value = Existing default execution policy if not already specified + /// + /// Whether this is a system-wide or per-user setting. + /// The shell associated with this policy. Typically, it is "Microsoft.PowerShell" + /// + internal override string GetMachineExecutionPolicy(PropertyScope scope, string shellId) + { + string execPolicy = "Restricted"; + string scopeDirectory = psHomeConfigDirectory; + string fileToUse; + + if(PropertyScope.CurrentUser == scope) + { + scopeDirectory = getUserConfigLocation(); + } + + fileToUse = Path.Combine(scopeDirectory, execPolicyFileName); + return execPolicy; + } + + internal override void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId) // TODO: Necessary? + { + } + + internal override void AddMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) + // TODO: Necessary? + { + } + + internal override void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) + { + } + + /// + /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds + /// Proposed value = Existing value, otherwise 10. + /// + /// Max stack size in MB. If not set, defaults to 10 MB. + internal override int GetPipeLineMaxStackSizeMb() + { + return 0; + } + + internal override void SetPipeLineMaxStackSizeMb(int maxStackSize) + { + } + + /// + /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds + /// Proposed value = existing default. Probably "1" + /// + /// Whether console prompting should happen. + internal override Boolean GetConsolePrompting() + { + return true; + } + + internal override void SetConsolePrompting(Boolean shouldPrompt) + { + + } + + /// + /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell + /// Proposed value = Existing default. Probably "0" + /// + /// Boolean indicating whether Update-Help should prompt + internal override Boolean GetDisablePromptToUpdateHelp() + { + return true; + } + + internal override void SetDisablePromptToUpdateHelp(Boolean prompt) + { + } + + /// + /// Existing Key = HKCU and HKLM\Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp + /// Proposed value = blank.This should be supported though + /// + /// + internal override string GetDefaultSourcePath(PropertyScope scope) + { + return string.Empty; + } + + internal override void SetDefaultSourcePath(PropertyScope scope, string defaultPath) + { + } + + private string getUserConfigLocation() + { + string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string psConfigPath = Path.Combine("PowerShell", "1.0", configDirectoryName); + return Path.Combine(appDataPath, psConfigPath); + } + } + + /// + /// TODO: Add caching so for faster access? Would prevent runtime changes from affecting runtime reads though... + /// + internal class XmlConfigFileAccessor : PropertyAccessor + { + private string fileName = string.Empty; + + internal XmlConfigFileAccessor() + { + Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly; + var result = Path.GetDirectoryName(ClrFacade.GetAssemblyLocation(assembly)); + fileName = result + "\\PowerShellConfig.xml"; + } + + internal override string GetApplicationBase(string version) + { + return ReadElementContentFromFileAsString("PSHome"); + } + + internal override string GetModulePath() + { + return ReadElementContentFromFileAsString("PSModulePath"); + } + + internal override string GetMachineShellPath(string shellId) + { + return ReadElementContentFromFileAsString("MachinePowerShellExePath"); + } + + internal override string GetMachineExecutionPolicy(string shellId) + { + + return ReadElementContentFromFileAsString("MachineExecutionPolicy"); + } + + internal override void SetMachineExecutionPolicy(string shellId, string executionPolicy) + { + this.ReplaceElementValueInFile("MachineExecutionPolicy", executionPolicy); + } + + internal override void AddMachineExecutionPolicy(string shellId, string executionPolicy) + { + this.AddElementToFile("MachineExecutionPolicy", executionPolicy); + } + + internal override void RemoveMachineExecutionPolicy(string shellId) + { + this.RemoveElementFromFile("MachineExecutionPolicy"); + } + + private string ReadElementContentFromFileAsString(string elementName) + { + using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) + { + using (StreamReader streamRdr = new StreamReader(fs)) + { + XmlReaderSettings xmlReaderSettings = + InternalDeserializer.XmlReaderSettingsForUntrustedXmlDocument.Clone(); + + using (XmlReader reader = XmlReader.Create(streamRdr, xmlReaderSettings)) + { + reader.MoveToContent(); + if (reader.ReadToDescendant(elementName)) + { + return reader.ReadElementContentAsString(); + } + } + } + } + return string.Empty; + } + + private void AddElementToFile(string elementName, string elementValue) + { + XmlDocument doc = new XmlDocument(); + + using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) + { + using (StreamReader streamRdr = new StreamReader(fs)) + { + doc.Load(streamRdr); + + XmlNodeList nodeList = doc.GetElementsByTagName(elementName); + + if (nodeList.Count > 0) + { + // Element exists. Replace it. + this.ReplaceElementValueInFile(elementName, elementValue); + } + else + { + XmlElement newElem = doc.CreateElement(elementName); + newElem.InnerText = elementValue; + doc.DocumentElement.AppendChild(newElem); + } + } + } + WriteDocumentToFile(doc); + } + + private void RemoveElementFromFile(string elementName) + { + XmlDocument doc = new XmlDocument(); + + using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) + { + using (StreamReader streamRdr = new StreamReader(fs)) + { + doc.Load(streamRdr); + + XmlNodeList nodeList = doc.GetElementsByTagName(elementName); + + if (0 == nodeList.Count) + { + // There is nothing to do because the specified node does not exist + return; + } + else if (nodeList.Count > 1) + { + // throw docuemnt format exception? Nodes should be unique... + return; + } + else + { + XmlNode first = nodeList.Item(0); + if (null != first) + { + doc.DocumentElement.RemoveChild(first); + } + } + } + } + WriteDocumentToFile(doc); + } + + private void ReplaceElementValueInFile(string elementName, string elementValue) + { + XmlDocument doc = new XmlDocument(); + + using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) + { + using (StreamReader streamRdr = new StreamReader(fs)) + { + doc.Load(streamRdr); + + XmlNodeList nodeList = doc.GetElementsByTagName(elementName); + + if (0 == nodeList.Count) + { + // There is nothing to do because the specified node does not exist + return; + } + else if (nodeList.Count > 1) + { + // throw docuemnt format exception? Nodes should be unique... + return; + } + else + { + XmlNode first = nodeList.Item(0); + if (null != first) + { + first.InnerText = elementValue; + } + } + /* // Alternate technique if the first one doesnt work + // Node is present, so replace it + XmlElement newElem = doc.CreateElement("title"); + newElem.InnerText = elementValue; + + //Replace the title element. + doc.DocumentElement.ReplaceChild(elem, root.FirstChild); + */ + } + } + WriteDocumentToFile(doc); + } + + private void WriteDocumentToFile(XmlDocument doc) + { + using (FileStream fs = new FileStream(this.fileName, FileMode.Create, FileAccess.Write)) + { + using (StreamWriter streamWriter = new StreamWriter(fs)) + { + XmlWriterSettings xmlSettings = new XmlWriterSettings(); + xmlSettings.Encoding = Encoding.UTF8; + xmlSettings.CloseOutput = true; + xmlSettings.Indent = true; + + using (XmlWriter writer = XmlWriter.Create(streamWriter, xmlSettings)) + { + doc.WriteTo(writer); + } + } + } + } + } + + internal class RegistryAccessor : PropertyAccessor + { + internal RegistryAccessor() + { + } + + internal override string GetApplicationBase(string version) + { + string engineKeyPath = RegistryStrings.MonadRootKeyPath + "\\" + version + "\\" + RegistryStrings.MonadEngineKey; + + return GetHklmString(engineKeyPath, RegistryStrings.MonadEngine_ApplicationBase); + } + + internal override string GetModulePath() + { + return string.Empty; + } + + internal override string GetMachineShellPath(string shellId) + { + string regKeyName = Utils.GetRegistryConfigurationPath(shellId); + return GetHklmString(regKeyName, "Path"); + } + + internal override string GetMachineExecutionPolicy(string shellId) + { + string regKeyName = Utils.GetRegistryConfigurationPath(shellId); + return GetHklmString(regKeyName, "ExecutionPolicy"); + } + + internal override void SetMachineExecutionPolicy(string shellId, string executionPolicy) + { + string regKeyName = Utils.GetRegistryConfigurationPath(shellId); + using (RegistryKey key = Registry.CurrentUser.CreateSubKey(regKeyName)) + { + key.SetValue("ExecutionPolicy", executionPolicy, RegistryValueKind.String); + } + } + + internal override void AddMachineExecutionPolicy(string shellId, string executionPolicy) + { + string regKeyName = Utils.GetRegistryConfigurationPath(shellId); + using (RegistryKey key = Registry.LocalMachine.CreateSubKey(regKeyName)) + { + key.SetValue("ExecutionPolicy", executionPolicy, RegistryValueKind.String); + } + } + + internal override void RemoveMachineExecutionPolicy(string shellId) + { + string regKeyName = Utils.GetRegistryConfigurationPath(shellId); + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(regKeyName, true)) + { + if (key != null) + { + if (key.GetValue("ExecutionPolicy") != null) + key.DeleteValue("ExecutionPolicy"); + } + } + } + + private string GetHklmString(string pathToKey, string valueName) + { + try + { + using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(pathToKey)) + { + if (null == regKey) + { + // Key not found + return null; + } + + // verify the value kind as a string + RegistryValueKind kind = regKey.GetValueKind(valueName); + + if (kind == RegistryValueKind.ExpandString || + kind == RegistryValueKind.String) + { + return regKey.GetValue(valueName) as string; + } + else + { + // The function expected a string, but got another type. This is a coding error or a registry key typing error. + return null; + } + } + } + catch (ObjectDisposedException) { } + catch (System.Security.SecurityException) { } + catch (ArgumentException) { } + catch (System.IO.IOException) { } + catch (UnauthorizedAccessException) { } + catch (FormatException) { } + catch (OverflowException) { } + catch (InvalidCastException) { } + + return null; + } + + private string GetHkcuString(string pathToKey, string valueName) + { + try + { + using (RegistryKey regKey = Registry.CurrentUser.OpenSubKey(pathToKey)) + { + if (null == regKey) + { + // Key not found + return null; + } + + // verify the value kind as a string + RegistryValueKind kind = regKey.GetValueKind(valueName); + + if (kind == RegistryValueKind.ExpandString || + kind == RegistryValueKind.String) + { + return regKey.GetValue(valueName) as string; + } + else + { + // The function expected a string, but got another type. This is a coding error or a registry key typing error. + return null; + } + } + } + catch (ObjectDisposedException) { } + catch (System.Security.SecurityException) { } + catch (ArgumentException) { } + catch (System.IO.IOException) { } + catch (UnauthorizedAccessException) { } + catch (FormatException) { } + catch (OverflowException) { } + catch (InvalidCastException) { } + + return null; + } + } +} // Namespace System.Management.Automation + From 73689d697a79a2151b21f8c11b0ed29f7acef82b Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Fri, 22 Jul 2016 10:14:52 -0700 Subject: [PATCH 02/12] ExecutionPolicy is now working from JSON files --- .../engine/PropertyAccessor.cs | 532 +++++++++++++----- .../engine/Utils.cs | 21 +- src/System.Management.Automation/project.json | 1 + .../security/SecuritySupport.cs | 75 +-- 4 files changed, 411 insertions(+), 218 deletions(-) diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index e9b829992..ed72e1d87 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -1,19 +1,24 @@ using System; +using System.Collections.Generic; using System.Xml; using System.IO; using System.Text; using System.Reflection; using System.Management.Automation; +using System.Threading; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; #if CORECLR // Some APIs are missing from System.Environment. We use System.Management.Automation.Environment as a proxy type: // - for missing APIs, System.Management.Automation.Environment has extension implementation. // - for existing APIs, System.Management.Automation.Environment redirect the call to System.Environment. using Environment = System.Management.Automation.Environment; +#else +//using Microsoft.Win32; #endif -// TODO: Add non-windows guard here or move to a different file using Microsoft.Win32; namespace System.Management.Automation @@ -49,11 +54,11 @@ namespace System.Management.Automation /// Existing Key = HKCU and HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell /// Proposed value = Existing default execution policy if not already specified /// + /// Where it should check for the value. /// The shell associated with this policy. Typically, it is "Microsoft.PowerShell" /// internal abstract string GetMachineExecutionPolicy(PropertyScope scope, string shellId); internal abstract void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId); // TODO: Necessary? - internal abstract void AddMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy); // TODO: Necessary? internal abstract void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy); /// @@ -70,16 +75,16 @@ namespace System.Management.Automation /// Proposed value = existing default. Probably "1" /// /// Whether console prompting should happen. - internal abstract Boolean GetConsolePrompting(); - internal abstract void SetConsolePrompting(Boolean shouldPrompt); + internal abstract bool GetConsolePrompting(); + internal abstract void SetConsolePrompting(bool shouldPrompt); /// /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell /// Proposed value = Existing default. Probably "0" /// /// Boolean indicating whether Update-Help should prompt - internal abstract Boolean GetDisablePromptToUpdateHelp(); - internal abstract void SetDisablePromptToUpdateHelp(Boolean prompt); + internal abstract bool GetDisablePromptToUpdateHelp(); + internal abstract void SetDisablePromptToUpdateHelp(bool prompt); /// /// Existing Key = HKCU and HKLM\Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp @@ -101,9 +106,9 @@ namespace System.Management.Automation { if (null == _activePropertyAccessor) { -#if CORECLR +#if CORECLR // TODO: I must differentiate between inbox PS and side-by-side PS here! - _activePropertyAccessor = new XmlConfigFileAccessor(); + _activePropertyAccessor = new JsonConfigFileAccessor(); #else _activePropertyAccessor = new RegistryAccessor(); #endif @@ -123,16 +128,39 @@ namespace System.Management.Automation internal class JsonConfigFileAccessor : PropertyAccessor { private string psHomeConfigDirectory; + private string appDataConfigDirectory; private static string configDirectoryName = "Configuration"; private static string execPolicyFileName = "ExecutionPolicy.json"; + private static string maxStackSizeFileName = "PipeLineMaxStackSizeMB.json"; + private static string consolePromptingFileName = "ConsolePrompting.json"; + private static string updateHelpPromptFileName = "UpdateHelpPrompt.json"; + private static string updatableHelpSourcePathFileName = "UpdatableHelpDefaultSourcePath.json"; internal JsonConfigFileAccessor() { + // + // Initialize (and create if necessary) the system-wide configuration directory + // // TODO: Use Utils.GetApplicationBase("Microsoft.PowerShell") instead? Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly; - psHomeConfigDirectory = Path.Combine(Path.GetDirectoryName(ClrFacade.GetAssemblyLocation(assembly)), configDirectoryName); + psHomeConfigDirectory = Path.Combine(Path.GetDirectoryName(assembly.Location), configDirectoryName); + if (!Directory.Exists(psHomeConfigDirectory)) + { + Directory.CreateDirectory(psHomeConfigDirectory); + } + // + // Initialize (and create if necessary) the per-user configuration directory + // + string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string psConfigPath = Path.Combine("PowerShell", "1.0", configDirectoryName); + appDataConfigDirectory = Path.Combine(appDataPath, psConfigPath); + + if (!Directory.Exists(appDataConfigDirectory)) + { + Directory.CreateDirectory(appDataConfigDirectory); + } } internal override string GetModulePath() @@ -143,103 +171,262 @@ namespace System.Management.Automation /// /// Existing Key = HKCU and HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell /// Proposed value = Existing default execution policy if not already specified + /// + /// Schema: + /// { + /// "shell ID string" : "execution policy string" + /// } + /// + /// Note: If we switch to a single config file, then this will need to be nested /// /// Whether this is a system-wide or per-user setting. /// The shell associated with this policy. Typically, it is "Microsoft.PowerShell" /// internal override string GetMachineExecutionPolicy(PropertyScope scope, string shellId) { - string execPolicy = "Restricted"; + string execPolicy = "Undefined"; string scopeDirectory = psHomeConfigDirectory; - string fileToUse; + // Defaults to system wide. if(PropertyScope.CurrentUser == scope) { - scopeDirectory = getUserConfigLocation(); + scopeDirectory = appDataConfigDirectory; } - fileToUse = Path.Combine(scopeDirectory, execPolicyFileName); + string fileName = Path.Combine(scopeDirectory, execPolicyFileName); + + string rawExecPolicy = ReadValueFromFile(fileName, shellId); + + if (!String.IsNullOrEmpty(rawExecPolicy)) + { + execPolicy = rawExecPolicy; + } + // TODO: Throw if NullOrEmpty? return execPolicy; } internal override void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId) // TODO: Necessary? { - } - - internal override void AddMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) - // TODO: Necessary? - { + // TODO: Work to do here if I decide to support it } internal override void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) { + string scopeDirectory = psHomeConfigDirectory; + + // Defaults to system wide. + if (PropertyScope.CurrentUser == scope) + { + scopeDirectory = appDataConfigDirectory; + } + + string fileName = Path.Combine(scopeDirectory, execPolicyFileName); + + WriteValueToFile(fileName, shellId, executionPolicy); } /// /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds - /// Proposed value = Existing value, otherwise 10. + /// Proposed value = Existing value, otherwise 10. + /// + /// Schema: + /// { + /// "PipeLineMaxStackSizeMB" : int + /// } /// /// Max stack size in MB. If not set, defaults to 10 MB. internal override int GetPipeLineMaxStackSizeMb() { - return 0; + string fileName = Path.Combine(psHomeConfigDirectory, maxStackSizeFileName); + + int maxStackSize = 10; + int rawMaxStackSize = ReadValueFromFile(fileName, "PipeLineMaxStackSizeMB"); + + if (0 != rawMaxStackSize) + { + maxStackSize = rawMaxStackSize; + } + return maxStackSize; } internal override void SetPipeLineMaxStackSizeMb(int maxStackSize) { + string fileName = Path.Combine(psHomeConfigDirectory, maxStackSizeFileName); + WriteValueToFile(fileName, "PipeLineMaxStackSizeMB", maxStackSize); } /// /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds /// Proposed value = existing default. Probably "1" + /// + /// Schema: + /// { + /// "ConsolePrompting" : bool + /// } /// /// Whether console prompting should happen. - internal override Boolean GetConsolePrompting() + internal override bool GetConsolePrompting() { - return true; + string fileName = Path.Combine(psHomeConfigDirectory, consolePromptingFileName); + return ReadValueFromFile(fileName, "ConsolePrompting"); } - internal override void SetConsolePrompting(Boolean shouldPrompt) + internal override void SetConsolePrompting(bool shouldPrompt) { - + string fileName = Path.Combine(psHomeConfigDirectory, consolePromptingFileName); + WriteValueToFile(fileName, "ConsolePrompting", shouldPrompt); } /// /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell /// Proposed value = Existing default. Probably "0" + /// + /// Schema: + /// { + /// "DisablePromptToUpdateHelp" : bool + /// } /// /// Boolean indicating whether Update-Help should prompt - internal override Boolean GetDisablePromptToUpdateHelp() + internal override bool GetDisablePromptToUpdateHelp() { - return true; + string fileName = Path.Combine(psHomeConfigDirectory, updateHelpPromptFileName); + return ReadValueFromFile(fileName, "DisablePromptToUpdateHelp"); } - internal override void SetDisablePromptToUpdateHelp(Boolean prompt) + internal override void SetDisablePromptToUpdateHelp(bool prompt) { + string fileName = Path.Combine(psHomeConfigDirectory, updateHelpPromptFileName); + WriteValueToFile(fileName, "DisablePromptToUpdateHelp", prompt); } /// /// Existing Key = HKCU and HKLM\Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp /// Proposed value = blank.This should be supported though + /// + /// Schema: + /// { + /// "DefaultSourcePath" : "path to local updatable help location" + /// } /// /// internal override string GetDefaultSourcePath(PropertyScope scope) { - return string.Empty; + string scopeDirectory = psHomeConfigDirectory; + + // Defaults to system wide. + if (PropertyScope.CurrentUser == scope) + { + scopeDirectory = appDataConfigDirectory; + } + + string fileName = Path.Combine(scopeDirectory, updatableHelpSourcePathFileName); + + string rawExecPolicy = ReadValueFromFile(fileName, "DefaultSourcePath"); + + if (!String.IsNullOrEmpty(rawExecPolicy)) + { + return rawExecPolicy; + } + // TODO: Throw if NullOrEmpty? + return String.Empty; } internal override void SetDefaultSourcePath(PropertyScope scope, string defaultPath) { + string scopeDirectory = psHomeConfigDirectory; + + // Defaults to system wide. + if (PropertyScope.CurrentUser == scope) + { + scopeDirectory = appDataConfigDirectory; + } + + string fileName = Path.Combine(scopeDirectory, updatableHelpSourcePathFileName); + + WriteValueToFile(fileName, "DefaultSourcePath", defaultPath); } - private string getUserConfigLocation() + private T ReadValueFromFile(string fileName, string key) { - string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string psConfigPath = Path.Combine("PowerShell", "1.0", configDirectoryName); - return Path.Combine(appDataPath, psConfigPath); + try + { + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + using (StreamReader streamRdr = new StreamReader(fs)) + using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) + { + JObject jsonObject = (JObject) JToken.ReadFrom(jsonReader); + return jsonObject.GetValue(key).ToObject(); + } + } + catch (ArgumentException) { } + //catch (ArgumentNullException) { } + catch (NotSupportedException) { } + catch (FileNotFoundException) { } + catch (System.IO.IOException) { } + //catch (DirectoryNotFoundException) { } + catch (System.Security.SecurityException) { } + catch (UnauthorizedAccessException) { } + //catch (PathTooLongException) { } + //catch (ArgumentOutOfRangeException) { } + + return default(T); + } + + /// + /// TODO: Should this return success fail or throw? + /// + /// TODO: Catch exceptions + /// + /// + /// + /// + /// + private void WriteValueToFile(string fileName, string key, T value) + { + JObject objectToWrite = new JObject(); + + if (File.Exists(fileName)) + { + // Since multiple properties can be in a single file, replacement + // is required instead of overwrite if a file already exists. + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + using (StreamReader streamRdr = new StreamReader(fs)) + using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) + { + JObject jsonObject = (JObject) JToken.ReadFrom(jsonReader); + IEnumerable properties = jsonObject.Properties(); + foreach (JProperty property in properties) + { + if (String.Equals(property.Name, key, StringComparison.OrdinalIgnoreCase)) + { + // Add the updated value to the output object instead + // of the preexisting one + objectToWrite.Add(new JProperty(key, value)); + } + else + { + // This preserves existing properties that do not + // match the one to update + objectToWrite.Add(new JProperty(property)); + } + } + } + } + else + { + // The file doesn't exist, so create it with the new property + objectToWrite.Add(new JProperty(key, value)); + } + + using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + using (StreamWriter streamWriter = new StreamWriter(fs)) + using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) + { + objectToWrite.WriteTo(jsonWriter); + } } } - + /* /// /// TODO: Add caching so for faster access? Would prevent runtime changes from affecting runtime reads though... /// @@ -280,11 +467,6 @@ namespace System.Management.Automation this.ReplaceElementValueInFile("MachineExecutionPolicy", executionPolicy); } - internal override void AddMachineExecutionPolicy(string shellId, string executionPolicy) - { - this.AddElementToFile("MachineExecutionPolicy", executionPolicy); - } - internal override void RemoveMachineExecutionPolicy(string shellId) { this.RemoveElementFromFile("MachineExecutionPolicy"); @@ -293,19 +475,17 @@ namespace System.Management.Automation private string ReadElementContentFromFileAsString(string elementName) { using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) + using (StreamReader streamRdr = new StreamReader(fs)) { - using (StreamReader streamRdr = new StreamReader(fs)) - { - XmlReaderSettings xmlReaderSettings = - InternalDeserializer.XmlReaderSettingsForUntrustedXmlDocument.Clone(); + XmlReaderSettings xmlReaderSettings = + InternalDeserializer.XmlReaderSettingsForUntrustedXmlDocument.Clone(); - using (XmlReader reader = XmlReader.Create(streamRdr, xmlReaderSettings)) + using (XmlReader reader = XmlReader.Create(streamRdr, xmlReaderSettings)) + { + reader.MoveToContent(); + if (reader.ReadToDescendant(elementName)) { - reader.MoveToContent(); - if (reader.ReadToDescendant(elementName)) - { - return reader.ReadElementContentAsString(); - } + return reader.ReadElementContentAsString(); } } } @@ -317,24 +497,22 @@ namespace System.Management.Automation XmlDocument doc = new XmlDocument(); using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) + using (StreamReader streamRdr = new StreamReader(fs)) { - using (StreamReader streamRdr = new StreamReader(fs)) + doc.Load(streamRdr); + + XmlNodeList nodeList = doc.GetElementsByTagName(elementName); + + if (nodeList.Count > 0) { - doc.Load(streamRdr); - - XmlNodeList nodeList = doc.GetElementsByTagName(elementName); - - if (nodeList.Count > 0) - { - // Element exists. Replace it. - this.ReplaceElementValueInFile(elementName, elementValue); - } - else - { - XmlElement newElem = doc.CreateElement(elementName); - newElem.InnerText = elementValue; - doc.DocumentElement.AppendChild(newElem); - } + // Element exists. Replace it. + this.ReplaceElementValueInFile(elementName, elementValue); + } + else + { + XmlElement newElem = doc.CreateElement(elementName); + newElem.InnerText = elementValue; + doc.DocumentElement.AppendChild(newElem); } } WriteDocumentToFile(doc); @@ -345,30 +523,28 @@ namespace System.Management.Automation XmlDocument doc = new XmlDocument(); using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) + using (StreamReader streamRdr = new StreamReader(fs)) { - using (StreamReader streamRdr = new StreamReader(fs)) + doc.Load(streamRdr); + + XmlNodeList nodeList = doc.GetElementsByTagName(elementName); + + if (0 == nodeList.Count) { - doc.Load(streamRdr); - - XmlNodeList nodeList = doc.GetElementsByTagName(elementName); - - if (0 == nodeList.Count) + // There is nothing to do because the specified node does not exist + return; + } + else if (nodeList.Count > 1) + { + // throw docuemnt format exception? Nodes should be unique... + return; + } + else + { + XmlNode first = nodeList.Item(0); + if (null != first) { - // There is nothing to do because the specified node does not exist - return; - } - else if (nodeList.Count > 1) - { - // throw docuemnt format exception? Nodes should be unique... - return; - } - else - { - XmlNode first = nodeList.Item(0); - if (null != first) - { - doc.DocumentElement.RemoveChild(first); - } + doc.DocumentElement.RemoveChild(first); } } } @@ -380,40 +556,38 @@ namespace System.Management.Automation XmlDocument doc = new XmlDocument(); using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) + using (StreamReader streamRdr = new StreamReader(fs)) { - using (StreamReader streamRdr = new StreamReader(fs)) + doc.Load(streamRdr); + + XmlNodeList nodeList = doc.GetElementsByTagName(elementName); + + if (0 == nodeList.Count) { - doc.Load(streamRdr); - - XmlNodeList nodeList = doc.GetElementsByTagName(elementName); - - if (0 == nodeList.Count) - { - // There is nothing to do because the specified node does not exist - return; - } - else if (nodeList.Count > 1) - { - // throw docuemnt format exception? Nodes should be unique... - return; - } - else - { - XmlNode first = nodeList.Item(0); - if (null != first) - { - first.InnerText = elementValue; - } - } - /* // Alternate technique if the first one doesnt work - // Node is present, so replace it - XmlElement newElem = doc.CreateElement("title"); - newElem.InnerText = elementValue; - - //Replace the title element. - doc.DocumentElement.ReplaceChild(elem, root.FirstChild); - */ + // There is nothing to do because the specified node does not exist + return; } + else if (nodeList.Count > 1) + { + // throw docuemnt format exception? Nodes should be unique... + return; + } + else + { + XmlNode first = nodeList.Item(0); + if (null != first) + { + first.InnerText = elementValue; + } + } + // Alternate technique if the first one doesnt work + // Node is present, so replace it + //XmlElement newElem = doc.CreateElement("title"); + //newElem.InnerText = elementValue; + + //Replace the title element. + //doc.DocumentElement.ReplaceChild(elem, root.FirstChild); + } WriteDocumentToFile(doc); } @@ -421,75 +595,96 @@ namespace System.Management.Automation private void WriteDocumentToFile(XmlDocument doc) { using (FileStream fs = new FileStream(this.fileName, FileMode.Create, FileAccess.Write)) + using (StreamWriter streamWriter = new StreamWriter(fs)) { - using (StreamWriter streamWriter = new StreamWriter(fs)) - { - XmlWriterSettings xmlSettings = new XmlWriterSettings(); - xmlSettings.Encoding = Encoding.UTF8; - xmlSettings.CloseOutput = true; - xmlSettings.Indent = true; + XmlWriterSettings xmlSettings = new XmlWriterSettings(); + xmlSettings.Encoding = Encoding.UTF8; + xmlSettings.CloseOutput = true; + xmlSettings.Indent = true; - using (XmlWriter writer = XmlWriter.Create(streamWriter, xmlSettings)) - { - doc.WriteTo(writer); - } + using (XmlWriter writer = XmlWriter.Create(streamWriter, xmlSettings)) + { + doc.WriteTo(writer); } } } } + */ internal class RegistryAccessor : PropertyAccessor { internal RegistryAccessor() { } - + /* internal override string GetApplicationBase(string version) { string engineKeyPath = RegistryStrings.MonadRootKeyPath + "\\" + version + "\\" + RegistryStrings.MonadEngineKey; return GetHklmString(engineKeyPath, RegistryStrings.MonadEngine_ApplicationBase); } - + */ internal override string GetModulePath() { return string.Empty; } - internal override string GetMachineShellPath(string shellId) + /// + /// + /// + /// + /// + /// The execution policy string if found, otherwise null. + internal override string GetMachineExecutionPolicy(PropertyScope scope, string shellId) { string regKeyName = Utils.GetRegistryConfigurationPath(shellId); - return GetHklmString(regKeyName, "Path"); - } - internal override string GetMachineExecutionPolicy(string shellId) - { - string regKeyName = Utils.GetRegistryConfigurationPath(shellId); - return GetHklmString(regKeyName, "ExecutionPolicy"); - } - - internal override void SetMachineExecutionPolicy(string shellId, string executionPolicy) - { - string regKeyName = Utils.GetRegistryConfigurationPath(shellId); - using (RegistryKey key = Registry.CurrentUser.CreateSubKey(regKeyName)) + if (PropertyScope.SystemWide == scope) { - key.SetValue("ExecutionPolicy", executionPolicy, RegistryValueKind.String); + return GetHklmString(regKeyName, "ExecutionPolicy"); + } + else if (PropertyScope.CurrentUser == scope) + { + return GetHkcuString(regKeyName, "ExecutionPolicy"); + } + else + { + return null; } } - internal override void AddMachineExecutionPolicy(string shellId, string executionPolicy) + internal override void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) { string regKeyName = Utils.GetRegistryConfigurationPath(shellId); - using (RegistryKey key = Registry.LocalMachine.CreateSubKey(regKeyName)) + RegistryKey scopedKey = Registry.LocalMachine; + + // Override if set to another value; + if (PropertyScope.CurrentUser == scope) { - key.SetValue("ExecutionPolicy", executionPolicy, RegistryValueKind.String); + scopedKey = Registry.CurrentUser; + } + + using (RegistryKey key = scopedKey.CreateSubKey(regKeyName)) + { + if (null != key) + { + key.SetValue("ExecutionPolicy", executionPolicy, RegistryValueKind.String); + } } } - internal override void RemoveMachineExecutionPolicy(string shellId) + internal override void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId) { string regKeyName = Utils.GetRegistryConfigurationPath(shellId); - using (RegistryKey key = Registry.LocalMachine.OpenSubKey(regKeyName, true)) + RegistryKey scopedKey = Registry.LocalMachine; + + // Override if set to another value; + if (PropertyScope.CurrentUser == scope) + { + scopedKey = Registry.CurrentUser; + } + + using (RegistryKey key = scopedKey.OpenSubKey(regKeyName, true)) { if (key != null) { @@ -499,6 +694,49 @@ namespace System.Management.Automation } } + internal override int GetPipeLineMaxStackSizeMb() + { + return 0; + } + internal override void SetPipeLineMaxStackSizeMb(int maxStackSize) + { } + + /// + /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds + /// Proposed value = existing default. Probably "1" + /// + /// Whether console prompting should happen. + internal override bool GetConsolePrompting() + { + return true; + } + internal override void SetConsolePrompting(bool shouldPrompt) + { } + + /// + /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell + /// Proposed value = Existing default. Probably "0" + /// + /// Boolean indicating whether Update-Help should prompt + internal override bool GetDisablePromptToUpdateHelp() + { + return true; + } + internal override void SetDisablePromptToUpdateHelp(bool prompt) + { } + + /// + /// Existing Key = HKCU and HKLM\Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp + /// Proposed value = blank.This should be supported though + /// + /// + internal override string GetDefaultSourcePath(PropertyScope scope) + { + return string.Empty; + } + internal override void SetDefaultSourcePath(PropertyScope scope, string defaultPath) + { } + private string GetHklmString(string pathToKey, string valueName) { try diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 8fb2d8b04..9c61a6cab 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -237,24 +237,21 @@ namespace System.Management.Automation /// internal static string GetApplicationBase(string shellId) { - // TODO: #1184 will resolve this work-around - // The application base cannot be resolved from the registry for side-by-side versions - // of PowerShell. -#if !CORECLR +#if CORECLR // Use the location of SMA.dll as the application base + // Assembly.GetEntryAssembly is not in CoreCLR. GAC is not in CoreCLR. + Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly; + return Path.GetDirectoryName(assembly.Location); +#else + // This code path applies to Windows FullCLR inbox deployments. All CoreCLR + // implementations should use the location of SMA.dll since it must reside in PSHOME. + // // try to get the path from the registry first string result = GetApplicationBaseFromRegistry(shellId); if (result != null) { return result; } -#endif - - -#if CORECLR // Use the location of SMA.dll as the application base - // Assembly.GetEntryAssembly is not in CoreCLR. GAC is not in CoreCLR. - Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly; - return Path.GetDirectoryName(assembly.Location); -#else + // The default keys aren't installed, so try and use the entry assembly to // get the application base. This works for managed apps like minishells... Assembly assem = Assembly.GetEntryAssembly(); diff --git a/src/System.Management.Automation/project.json b/src/System.Management.Automation/project.json index 0d6ee12b6..f60feb16c 100644 --- a/src/System.Management.Automation/project.json +++ b/src/System.Management.Automation/project.json @@ -144,6 +144,7 @@ "Microsoft.CSharp": "4.0.1", "Microsoft.Win32.Registry.AccessControl": "4.0.0", + "Newtonsoft.Json": "7.0.1", "System.Collections.Specialized": "4.0.1", "System.Collections.NonGeneric": "4.0.1", "System.ComponentModel.EventBasedAsync": "4.0.11", diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index 9da5da10e..f3b731745 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -146,7 +146,7 @@ namespace System.Management.Automation.Internal #else string executionPolicy = "Restricted"; string preferenceKey = Utils.GetRegistryConfigurationPath(shellId); - const string PolicyKeyValueName = "ExecutionPolicy"; + PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); switch (policy) { @@ -179,25 +179,13 @@ namespace System.Management.Automation.Internal // They want to remove it if (policy == ExecutionPolicy.Undefined) { - using (RegistryKey key = Registry.CurrentUser.OpenSubKey(preferenceKey, true)) - { - if (key != null) - { - if (key.GetValue(PolicyKeyValueName) != null) - key.DeleteValue(PolicyKeyValueName); - } - } - + propertyAccessor.RemoveMachineExecutionPolicy(PropertyAccessor.PropertyScope.CurrentUser, shellId); CleanKeyParents(Registry.CurrentUser, preferenceKey); } else { - using (RegistryKey key = Registry.CurrentUser.CreateSubKey(preferenceKey)) - { - key.SetValue(PolicyKeyValueName, executionPolicy, RegistryValueKind.String); - } + propertyAccessor.SetMachineExecutionPolicy(PropertyAccessor.PropertyScope.CurrentUser, shellId, executionPolicy); } - break; } @@ -206,25 +194,13 @@ namespace System.Management.Automation.Internal // They want to remove it if (policy == ExecutionPolicy.Undefined) { - using (RegistryKey key = Registry.LocalMachine.OpenSubKey(preferenceKey, true)) - { - if (key != null) - { - if (key.GetValue(PolicyKeyValueName) != null) - key.DeleteValue(PolicyKeyValueName); - } - } - + propertyAccessor.RemoveMachineExecutionPolicy(PropertyAccessor.PropertyScope.SystemWide, shellId); CleanKeyParents(Registry.LocalMachine, preferenceKey); } else { - using (RegistryKey key = Registry.LocalMachine.CreateSubKey(preferenceKey)) - { - key.SetValue(PolicyKeyValueName, executionPolicy, RegistryValueKind.String); - } + propertyAccessor.SetMachineExecutionPolicy(PropertyAccessor.PropertyScope.SystemWide, shellId, executionPolicy); } - break; } } @@ -235,6 +211,9 @@ namespace System.Management.Automation.Internal // contain at most a single child private static void CleanKeyParents(RegistryKey baseKey, string keyPath) { +#if CORECLR + return; +#else // Windows && FullCLR using (RegistryKey key = baseKey.OpenSubKey(keyPath, true)) { // Verify the child key has no children @@ -269,6 +248,7 @@ namespace System.Management.Automation.Internal return; } } +#endif } internal static ExecutionPolicy GetExecutionPolicy(string shellId) @@ -311,6 +291,8 @@ namespace System.Management.Automation.Internal return ExecutionPolicy.Undefined; } + // TODO: Group Policy is only supported on Full systems, but !LINUX && CORECLR + // will run there as well, so I don't think we should remove it. case ExecutionPolicyScope.UserPolicy: case ExecutionPolicyScope.MachinePolicy: { @@ -604,48 +586,23 @@ namespace System.Management.Automation.Internal /// NULL if it is not defined at this level private static string GetLocalPreferenceValue(string shellId, ExecutionPolicyScope scope) { - const string PolicyValueName = "ExecutionPolicy"; - string LocalPreferenceKey = Utils.GetRegistryConfigurationPath(shellId); + PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); switch (scope) { // 1: Look up the current-user preference case ExecutionPolicyScope.CurrentUser: - { - using (RegistryKey key = Registry.CurrentUser.OpenSubKey(LocalPreferenceKey)) - { - if (key != null) - { - object temp = key.GetValue(PolicyValueName); - string policy = temp as string; - key.Dispose(); + return propertyAccessor.GetMachineExecutionPolicy(PropertyAccessor.PropertyScope.CurrentUser, shellId); - return policy; - } - } - }; break; - - // 1: Look up the system-wide preference + // 2: Look up the system-wide preference case ExecutionPolicyScope.LocalMachine: - { - using (RegistryKey key = Registry.LocalMachine.OpenSubKey(LocalPreferenceKey)) - { - if (key != null) - { - object temp = key.GetValue(PolicyValueName); - string policy = temp as string; - key.Dispose(); - - return policy; - } - } - }; break; + return propertyAccessor.GetMachineExecutionPolicy(PropertyAccessor.PropertyScope.SystemWide, shellId); } return null; } - #endregion execution policy +#endregion execution policy /// /// throw if file does not exist From 2c365356b6b0383c1aadc06ebc20efcaa78ed492 Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Fri, 22 Jul 2016 14:41:24 -0700 Subject: [PATCH 03/12] Adding UpdatableHelp checks. --- .../msh/ConsoleHostUserInterfaceSecurity.cs | 53 +--- .../engine/PropertyAccessor.cs | 284 ++++++++++++------ .../engine/hostifaces/LocalPipeline.cs | 38 +-- .../help/UpdatableHelpSystem.cs | 86 +----- 4 files changed, 216 insertions(+), 245 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs index bff625594..15c63ce7a 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs @@ -173,56 +173,19 @@ namespace Microsoft.PowerShell #if CORECLR // on Nano there is no other way to prompt except by using console return true; #else - string PolicyKeyName = Utils.GetRegistryConfigurationPrefix(); - const string PromptValueName = "ConsolePrompting"; - bool promptUsingConsole = false; - RegistryKey key; - - // Open the registry key that holds the configuration setting - try + PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); + Exception exception = null; + bool promptUsingConsole = propertyAccessor.GetConsolePrompting(ref exception); + if (null != exception) { - key = Registry.LocalMachine.OpenSubKey(PolicyKeyName); + s_tracer.TraceError("Could not read CredUI registry key: " + exception.Message); + return false; } - catch (System.Security.SecurityException) + else { - s_tracer.TraceError("User doesn't have access to read CredUI registry key."); + s_tracer.WriteLine("DetermineCredUIPolicy: policy == {0}", promptUsingConsole); return promptUsingConsole; } - - if (key == null) - { - return promptUsingConsole; - } - - // Get the configuration setting - try - { - object consolePromptingKey = key.GetValue(PromptValueName); - if (consolePromptingKey != null) { promptUsingConsole = Convert.ToBoolean(consolePromptingKey.ToString(), CultureInfo.InvariantCulture); } - } - catch (System.Security.SecurityException e) - { - s_tracer.TraceError("Could not read CredUI registry key: " + e.Message); - if (key != null) { key.Dispose(); } // No RegistryKey.Close() on CoreCLR - return promptUsingConsole; - } - catch (InvalidCastException e) - { - s_tracer.TraceError("Could not parse CredUI registry key: " + e.Message); - if (key != null) { key.Dispose(); } // No RegistryKey.Close() on CoreCLR - return promptUsingConsole; - } - catch (FormatException e) - { - s_tracer.TraceError("Could not parse CredUI registry key: " + e.Message); - if (key != null) { key.Dispose(); } // No RegistryKey.Close() on CoreCLR - return promptUsingConsole; - } - - s_tracer.WriteLine("DetermineCredUIPolicy: policy == {0}", promptUsingConsole); - - if (key != null) { key.Dispose(); } // No RegistryKey.Close() on CoreCLR - return promptUsingConsole; #endif } } diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index ed72e1d87..88e46cf41 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -4,9 +4,10 @@ using System.Xml; using System.IO; using System.Text; using System.Reflection; +using System.Globalization; +using System.Threading; using System.Management.Automation; -using System.Threading; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -66,7 +67,7 @@ namespace System.Management.Automation /// Proposed value = Existing value, otherwise 10. /// /// Max stack size in MB. If not set, defaults to 10 MB. - internal abstract int GetPipeLineMaxStackSizeMb(); + internal abstract int GetPipeLineMaxStackSizeMb(int defaultValue); internal abstract void SetPipeLineMaxStackSizeMb(int maxStackSize); @@ -75,7 +76,7 @@ namespace System.Management.Automation /// Proposed value = existing default. Probably "1" /// /// Whether console prompting should happen. - internal abstract bool GetConsolePrompting(); + internal abstract bool GetConsolePrompting(ref Exception exception); internal abstract void SetConsolePrompting(bool shouldPrompt); /// @@ -91,8 +92,8 @@ namespace System.Management.Automation /// Proposed value = blank.This should be supported though /// /// - internal abstract string GetDefaultSourcePath(PropertyScope scope); - internal abstract void SetDefaultSourcePath(PropertyScope scope, string defaultPath); + internal abstract string GetDefaultSourcePath(); + internal abstract void SetDefaultSourcePath(string defaultPath); } internal class PropertyAccessorFactory @@ -129,12 +130,12 @@ namespace System.Management.Automation { private string psHomeConfigDirectory; private string appDataConfigDirectory; - private static string configDirectoryName = "Configuration"; - private static string execPolicyFileName = "ExecutionPolicy.json"; - private static string maxStackSizeFileName = "PipeLineMaxStackSizeMB.json"; - private static string consolePromptingFileName = "ConsolePrompting.json"; - private static string updateHelpPromptFileName = "UpdateHelpPrompt.json"; - private static string updatableHelpSourcePathFileName = "UpdatableHelpDefaultSourcePath.json"; + private const string configDirectoryName = "Configuration"; + private const string execPolicyFileName = "ExecutionPolicy.json"; + private const string maxStackSizeFileName = "PipeLineMaxStackSizeMB.json"; + private const string consolePromptingFileName = "ConsolePrompting.json"; + private const string updateHelpPromptFileName = "UpdateHelpPrompt.json"; + private const string updatableHelpSourcePathFileName = "UpdatableHelpDefaultSourcePath.json"; internal JsonConfigFileAccessor() { @@ -235,11 +236,11 @@ namespace System.Management.Automation /// } /// /// Max stack size in MB. If not set, defaults to 10 MB. - internal override int GetPipeLineMaxStackSizeMb() + internal override int GetPipeLineMaxStackSizeMb(int defaultValue) { string fileName = Path.Combine(psHomeConfigDirectory, maxStackSizeFileName); - int maxStackSize = 10; + int maxStackSize = defaultValue; int rawMaxStackSize = ReadValueFromFile(fileName, "PipeLineMaxStackSizeMB"); if (0 != rawMaxStackSize) @@ -265,7 +266,7 @@ namespace System.Management.Automation /// } /// /// Whether console prompting should happen. - internal override bool GetConsolePrompting() + internal override bool GetConsolePrompting(ref Exception exception) { string fileName = Path.Combine(psHomeConfigDirectory, consolePromptingFileName); return ReadValueFromFile(fileName, "ConsolePrompting"); @@ -309,17 +310,9 @@ namespace System.Management.Automation /// } /// /// - internal override string GetDefaultSourcePath(PropertyScope scope) + internal override string GetDefaultSourcePath() { - string scopeDirectory = psHomeConfigDirectory; - - // Defaults to system wide. - if (PropertyScope.CurrentUser == scope) - { - scopeDirectory = appDataConfigDirectory; - } - - string fileName = Path.Combine(scopeDirectory, updatableHelpSourcePathFileName); + string fileName = Path.Combine(psHomeConfigDirectory, updatableHelpSourcePathFileName); string rawExecPolicy = ReadValueFromFile(fileName, "DefaultSourcePath"); @@ -331,17 +324,9 @@ namespace System.Management.Automation return String.Empty; } - internal override void SetDefaultSourcePath(PropertyScope scope, string defaultPath) + internal override void SetDefaultSourcePath(string defaultPath) { - string scopeDirectory = psHomeConfigDirectory; - - // Defaults to system wide. - if (PropertyScope.CurrentUser == scope) - { - scopeDirectory = appDataConfigDirectory; - } - - string fileName = Path.Combine(scopeDirectory, updatableHelpSourcePathFileName); + string fileName = Path.Combine(psHomeConfigDirectory, updatableHelpSourcePathFileName); WriteValueToFile(fileName, "DefaultSourcePath", defaultPath); } @@ -613,6 +598,12 @@ namespace System.Management.Automation internal class RegistryAccessor : PropertyAccessor { + private const string DisablePromptToUpdateHelpRegPath = "Software\\Microsoft\\PowerShell"; + private const string DisablePromptToUpdateHelpRegPath32 = "Software\\Wow6432Node\\Microsoft\\PowerShell"; + private const string DisablePromptToUpdateHelpRegKey = "DisablePromptToUpdateHelp"; + private const string DefaultSourcePathRegPath = "Software\\Policies\\Microsoft\\Windows\\PowerShell\\UpdatableHelp"; + private const string DefaultSourcePathRegKey = "DefaultSourcePath"; + internal RegistryAccessor() { } @@ -638,19 +629,15 @@ namespace System.Management.Automation internal override string GetMachineExecutionPolicy(PropertyScope scope, string shellId) { string regKeyName = Utils.GetRegistryConfigurationPath(shellId); + RegistryKey scopedKey = Registry.LocalMachine; - if (PropertyScope.SystemWide == scope) + // Override if set to another value; + if (PropertyScope.CurrentUser == scope) { - return GetHklmString(regKeyName, "ExecutionPolicy"); - } - else if (PropertyScope.CurrentUser == scope) - { - return GetHkcuString(regKeyName, "ExecutionPolicy"); - } - else - { - return null; + scopedKey = Registry.CurrentUser; } + + return GetRegistryString(scopedKey, regKeyName, "ExecutionPolicy"); } internal override void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) @@ -694,24 +681,48 @@ namespace System.Management.Automation } } - internal override int GetPipeLineMaxStackSizeMb() + internal override int GetPipeLineMaxStackSizeMb(int defaultValue) { - return 0; + string regKeyName = Utils.GetRegistryConfigurationPrefix(); + + int? tempInt = GetRegistryDword(Registry.LocalMachine, regKeyName, "PipelineMaxStackSizeMB"); + + return (tempInt.HasValue ? tempInt.Value : defaultValue); } + internal override void SetPipeLineMaxStackSizeMb(int maxStackSize) - { } + { + string regKeyName = Utils.GetRegistryConfigurationPrefix(); + SetRegistryDword(Registry.LocalMachine, regKeyName, "PipelineMaxStackSizeMB", maxStackSize); + } /// /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds /// Proposed value = existing default. Probably "1" /// /// Whether console prompting should happen. - internal override bool GetConsolePrompting() + internal override bool GetConsolePrompting(ref Exception exception) { - return true; + string policyKeyName = Utils.GetRegistryConfigurationPrefix(); + string tempPrompt = GetRegistryString(Registry.LocalMachine, policyKeyName, "ConsolePrompting", ref exception); + + if (null != tempPrompt) + { + // TODO: It is difficult to tell from the original code how this value is actually stored in the registry. + // I am inferring that it is a "true" or "false" string based on the original code. + return Convert.ToBoolean(tempPrompt, CultureInfo.InvariantCulture); + } + else + { + return false; + } } + internal override void SetConsolePrompting(bool shouldPrompt) - { } + { + string policyKeyName = Utils.GetRegistryConfigurationPrefix(); + SetRegistryString(Registry.LocalMachine, policyKeyName, "ConsolePrompting", shouldPrompt.ToString()); + } /// /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell @@ -720,28 +731,146 @@ namespace System.Management.Automation /// Boolean indicating whether Update-Help should prompt internal override bool GetDisablePromptToUpdateHelp() { - return true; + using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DisablePromptToUpdateHelpRegPath)) + { + if (hklm != null) + { + object disablePromptToUpdateHelp = hklm.GetValue(DisablePromptToUpdateHelpRegKey, null, RegistryValueOptions.None); + + if (disablePromptToUpdateHelp == null) + { + return true; + } + else + { + int result; + + if (LanguagePrimitives.TryConvertTo(disablePromptToUpdateHelp, out result)) + { + return (result != 1); + } + + return true; + } + } + else + { + return true; + } + } } + internal override void SetDisablePromptToUpdateHelp(bool prompt) - { } + { + int valueToSet = prompt ? 1 : 0; + try + { + using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DisablePromptToUpdateHelpRegPath, true)) + { + if (hklm != null) + { + hklm.SetValue(DisablePromptToUpdateHelpRegKey, valueToSet, RegistryValueKind.DWord); + } + } + + using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DisablePromptToUpdateHelpRegPath32, true)) + { + if (hklm != null) + { + hklm.SetValue(DisablePromptToUpdateHelpRegKey, valueToSet, RegistryValueKind.DWord); + } + } + } + catch (UnauthorizedAccessException) {} + catch (System.Security.SecurityException) {} + } /// /// Existing Key = HKCU and HKLM\Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp /// Proposed value = blank.This should be supported though /// /// - internal override string GetDefaultSourcePath(PropertyScope scope) + internal override string GetDefaultSourcePath() { - return string.Empty; + return GetRegistryString(Registry.LocalMachine, DefaultSourcePathRegPath, DefaultSourcePathRegKey); } - internal override void SetDefaultSourcePath(PropertyScope scope, string defaultPath) - { } - private string GetHklmString(string pathToKey, string valueName) + internal override void SetDefaultSourcePath(string defaultPath) + { + SetRegistryString(Registry.LocalMachine, DefaultSourcePathRegPath, DefaultSourcePathRegKey, defaultPath); + } + + private int? GetRegistryDword(RegistryKey rootKey, string pathToKey, string valueName) { try { - using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(pathToKey)) + using (RegistryKey regKey = rootKey.OpenSubKey(pathToKey)) + { + if (null == regKey) + { + // Key not found + return null; + } + + // verify the value kind as a string + RegistryValueKind kind = regKey.GetValueKind(valueName); + + if (kind == RegistryValueKind.DWord) + { + return regKey.GetValue(valueName) as int?; + } + else + { + // The function expected a DWORD, but got another type. This is a coding error or a registry key typing error. + return null; + } + } + } + catch (ObjectDisposedException) { } + catch (System.Security.SecurityException) { } + catch (ArgumentException) { } + catch (System.IO.IOException) { } + catch (UnauthorizedAccessException) { } + catch (FormatException) { } + catch (OverflowException) { } + catch (InvalidCastException) { } + + return null; + } + + private void SetRegistryDword(RegistryKey rootKey, string pathToKey, string valueName, int value) + { + try + { + using (RegistryKey regKey = rootKey.OpenSubKey(pathToKey)) + { + if (null != regKey) + { + regKey.SetValue(valueName, value, RegistryValueKind.DWord); + } + } + } + catch (ObjectDisposedException) { } + catch (System.Security.SecurityException) { } + catch (ArgumentException) { } + catch (System.IO.IOException) { } + catch (UnauthorizedAccessException) { } + catch (FormatException) { } + catch (OverflowException) { } + catch (InvalidCastException) { } + } + + private string GetRegistryString(RegistryKey rootKey, string pathToKey, string valueName) + { + Exception e = null; + return GetRegistryString(rootKey, pathToKey, valueName, ref e); + } + + private string GetRegistryString(RegistryKey rootKey, string pathToKey, string valueName, ref Exception exception) + { + try + { + using (RegistryKey regKey = rootKey.OpenSubKey(pathToKey)) { if (null == regKey) { @@ -764,42 +893,27 @@ namespace System.Management.Automation } } } - catch (ObjectDisposedException) { } - catch (System.Security.SecurityException) { } - catch (ArgumentException) { } - catch (System.IO.IOException) { } - catch (UnauthorizedAccessException) { } - catch (FormatException) { } - catch (OverflowException) { } - catch (InvalidCastException) { } + catch (ObjectDisposedException e) { exception = e; } + catch (System.Security.SecurityException e) { exception = e; } + catch (ArgumentException e) { exception = e; } + catch (System.IO.IOException e) { exception = e; } + catch (UnauthorizedAccessException e) { exception = e; } + catch (FormatException e) { exception = e; } + catch (OverflowException e) { exception = e; } + catch (InvalidCastException e) { exception = e; } return null; } - private string GetHkcuString(string pathToKey, string valueName) + private string SetRegistryString(RegistryKey rootKey, string pathToKey, string valueName, string value) { try { - using (RegistryKey regKey = Registry.CurrentUser.OpenSubKey(pathToKey)) + using (RegistryKey key = rootKey.CreateSubKey(pathToKey)) { - if (null == regKey) + if (null != key) { - // Key not found - return null; - } - - // verify the value kind as a string - RegistryValueKind kind = regKey.GetValueKind(valueName); - - if (kind == RegistryValueKind.ExpandString || - kind == RegistryValueKind.String) - { - return regKey.GetValue(valueName) as string; - } - else - { - // The function expected a string, but got another type. This is a coding error or a registry key typing error. - return null; + key.SetValue(valueName, value, RegistryValueKind.String); } } } diff --git a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs index 468734b33..6ff6f7a1e 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs @@ -254,7 +254,9 @@ namespace System.Management.Automation.Runspaces { get { - int i = ReadRegistryInt("PipelineMaxStackSizeMB", 10); + PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); + int i = propertyAccessor.GetPipeLineMaxStackSizeMb(10); + if (i < 10) i = 10; // minimum 10MB else if (i > 100) @@ -531,40 +533,6 @@ namespace System.Management.Automation.Runspaces return flowControlException; } - // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 - // Removed HandleUnexpectedExceptions infrastructure - - internal static int ReadRegistryInt(string policyValueName, int defaultValue) - { - RegistryKey key; - try - { - key = Registry.LocalMachine.OpenSubKey(Utils.GetRegistryConfigurationPrefix()); - } - catch (System.Security.SecurityException) - { - return defaultValue; - } - if (null == key) - return defaultValue; - - object temp; - try - { - temp = key.GetValue(policyValueName); - } - catch (System.Security.SecurityException) - { - return defaultValue; - } - if (!(temp is int)) - { - return defaultValue; - } - int i = (int)temp; - return i; - } - // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 // Removed HandleUnexpectedExceptions infrastructure /// diff --git a/src/System.Management.Automation/help/UpdatableHelpSystem.cs b/src/System.Management.Automation/help/UpdatableHelpSystem.cs index fc7111cbc..0776fb8a1 100644 --- a/src/System.Management.Automation/help/UpdatableHelpSystem.cs +++ b/src/System.Management.Automation/help/UpdatableHelpSystem.cs @@ -238,12 +238,6 @@ namespace System.Management.Automation.Help /// internal class UpdatableHelpSystem : IDisposable { - internal const string DisablePromptToUpdateHelpRegPath = "Software\\Microsoft\\PowerShell"; - internal const string DisablePromptToUpdateHelpRegPath32 = "Software\\Wow6432Node\\Microsoft\\PowerShell"; - internal const string DisablePromptToUpdateHelpRegKey = "DisablePromptToUpdateHelp"; - internal const string DefaultSourcePathRegPath = "Software\\Policies\\Microsoft\\Windows\\PowerShell\\UpdatableHelp"; - internal const string DefaultSourcePathRegKey = "DefaultSourcePath"; - #if CORECLR private TimeSpan _defaultTimeout; #else @@ -1599,27 +1593,8 @@ namespace System.Management.Automation.Help /// internal string GetDefaultSourcePath() { - try - { - using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DefaultSourcePathRegPath)) - { - if (hklm != null) - { - object defaultSourcePath = hklm.GetValue(DefaultSourcePathRegKey, null, RegistryValueOptions.None); - - if (defaultSourcePath != null) - { - return defaultSourcePath as string; - } - } - } - } - catch (SecurityException) - { - return null; - } - - return null; + PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); + return propertyAccessor.GetDefaultSourcePath(); } /// @@ -1627,32 +1602,8 @@ namespace System.Management.Automation.Help /// internal static void SetDisablePromptToUpdateHelp() { - try - { - using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DisablePromptToUpdateHelpRegPath, true)) - { - if (hklm != null) - { - hklm.SetValue(DisablePromptToUpdateHelpRegKey, 1, RegistryValueKind.DWord); - } - } - - using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DisablePromptToUpdateHelpRegPath32, true)) - { - if (hklm != null) - { - hklm.SetValue(DisablePromptToUpdateHelpRegKey, 1, RegistryValueKind.DWord); - } - } - } - catch (UnauthorizedAccessException) - { - // Ignore AccessDenied related exceptions - } - catch (SecurityException) - { - // Ignore AccessDenied related exceptions - } + PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); + propertyAccessor.SetDisablePromptToUpdateHelp(true); } /// @@ -1668,33 +1619,8 @@ namespace System.Management.Automation.Help return false; } - using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DisablePromptToUpdateHelpRegPath)) - { - if (hklm != null) - { - object disablePromptToUpdateHelp = hklm.GetValue(DisablePromptToUpdateHelpRegKey, null, RegistryValueOptions.None); - - if (disablePromptToUpdateHelp == null) - { - return true; - } - else - { - int result; - - if (LanguagePrimitives.TryConvertTo(disablePromptToUpdateHelp, out result)) - { - return (result != 1); - } - - return true; - } - } - else - { - return true; - } - } + PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); + return propertyAccessor.GetDisablePromptToUpdateHelp(); } catch (SecurityException) { From e6e785ec9233a54eb327333d17912c009216fe1b Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Mon, 25 Jul 2016 14:38:33 -0700 Subject: [PATCH 04/12] Added module path support. Not plumbed trhough yet. --- .../engine/Modules/ModuleIntrinsics.cs | 2 +- .../engine/PropertyAccessor.cs | 54 ++++++++++++++----- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index a1a353766..3efd866e9 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -929,7 +929,7 @@ namespace System.Management.Automation if (processedPath != null) yield return processedPath; } - } + } if (includeSystemModulePath) { diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index 88e46cf41..6ae61d29c 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Xml; using System.IO; using System.Text; @@ -42,6 +43,16 @@ namespace System.Management.Automation CurrentUser = 1 } + /// + /// Describes the desired module path during a GetModulePath call. + /// + internal enum ModulePathTarget + { + CurrentUser = 0, + SharedCommon = 1, + VersionSpecific = 2 + } + /// /// Existing Key = HKLM:\System\CurrentControlSet\Control\Session Manager\Environment /// Proposed value = %ProgramFiles%\PowerShell\Modules by default @@ -49,7 +60,7 @@ namespace System.Management.Automation /// Note: There is no setter because this value is immutable. /// /// Module path values from the config file. - internal abstract string GetModulePath(); + internal abstract string GetModulePath(ModulePathTarget target); /// /// Existing Key = HKCU and HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell @@ -131,6 +142,7 @@ namespace System.Management.Automation private string psHomeConfigDirectory; private string appDataConfigDirectory; private const string configDirectoryName = "Configuration"; + private const string modulePathFileName = "PSModulePath.json"; private const string execPolicyFileName = "ExecutionPolicy.json"; private const string maxStackSizeFileName = "PipeLineMaxStackSizeMB.json"; private const string consolePromptingFileName = "ConsolePrompting.json"; @@ -164,9 +176,27 @@ namespace System.Management.Automation } } - internal override string GetModulePath() + /// + /// Note: This value is not writable, so it MUST be set during post-install configuration + /// + /// + /// + internal override string GetModulePath(ModulePathTarget target) { - return string.Empty; + // TODO: This feels unnecessary since it can be calculated based on the current user... + if (ModulePathTarget.CurrentUser == target) + { + return ReadValueFromFile(modulePathFileName, "UserModulePath"); + } + else if (ModulePathTarget.SharedCommon == target) + { + return ReadValueFromFile(modulePathFileName, "SharedCommonModulePath"); + } + else + { + // TODO: Better way to handle this? + throw new InvalidEnumArgumentException(); + } } /// @@ -607,17 +637,17 @@ namespace System.Management.Automation internal RegistryAccessor() { } - /* - internal override string GetApplicationBase(string version) - { - string engineKeyPath = RegistryStrings.MonadRootKeyPath + "\\" + version + "\\" + RegistryStrings.MonadEngineKey; - return GetHklmString(engineKeyPath, RegistryStrings.MonadEngine_ApplicationBase); - } - */ - internal override string GetModulePath() + /// + /// This method is not supported for systems when reading from the registry. Its value is + /// known at compile time for inbox PowerShell. + /// + /// + /// + internal override string GetModulePath(ModulePathTarget target) { - return string.Empty; + // TODO: Better way to handle this? + throw new InvalidEnumArgumentException(); } /// From f8b9b88986877edcdfe8d16fc23e59ac164ea7a1 Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Tue, 26 Jul 2016 15:48:33 -0700 Subject: [PATCH 05/12] Bug fixes and cleanup in preparation for push. --- build.psm1 | 2 +- .../engine/PropertyAccessor.cs | 362 ++++++------------ .../engine/Utils.cs | 12 + 3 files changed, 128 insertions(+), 248 deletions(-) diff --git a/build.psm1 b/build.psm1 index ad44e0e92..b345f7617 100644 --- a/build.psm1 +++ b/build.psm1 @@ -537,7 +537,7 @@ function Start-PSPester { Write-Verbose "Import-Module '$moduleDir'; Invoke-Pester $tagString $Path" $powershellexe = get-psoutput - & $powershell -noprofile -c "Import-Module '$moduleDir'; Invoke-Pester $tagString $Path" + & $powershell -noprofile -c "Set-ExecutionPolicy Unrestricted; Import-Module '$moduleDir'; Invoke-Pester $tagString $Path" if ($LASTEXITCODE -ne 0) { throw "$LASTEXITCODE Pester tests failed" } diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index 6ae61d29c..10ca8c5fc 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -70,7 +70,7 @@ namespace System.Management.Automation /// The shell associated with this policy. Typically, it is "Microsoft.PowerShell" /// internal abstract string GetMachineExecutionPolicy(PropertyScope scope, string shellId); - internal abstract void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId); // TODO: Necessary? + internal abstract void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId); internal abstract void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy); /// @@ -111,7 +111,6 @@ namespace System.Management.Automation { /// /// Template method to generate the appropriate accessor for a given environment. - /// TODO: Is it really necessary to make this Singleton-ish? /// /// internal static PropertyAccessor GetPropertyAccessor() @@ -119,7 +118,7 @@ namespace System.Management.Automation if (null == _activePropertyAccessor) { #if CORECLR - // TODO: I must differentiate between inbox PS and side-by-side PS here! + // TODO: I must differentiate between inbox PS and side-by-side PS here eventually _activePropertyAccessor = new JsonConfigFileAccessor(); #else _activePropertyAccessor = new RegistryAccessor(); @@ -166,9 +165,7 @@ namespace System.Management.Automation // // Initialize (and create if necessary) the per-user configuration directory // - string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string psConfigPath = Path.Combine("PowerShell", "1.0", configDirectoryName); - appDataConfigDirectory = Path.Combine(appDataPath, psConfigPath); + appDataConfigDirectory = Path.Combine(Utils.GetUserSettingsDirectory(), configDirectoryName); if (!Directory.Exists(appDataConfigDirectory)) { @@ -183,9 +180,9 @@ namespace System.Management.Automation /// internal override string GetModulePath(ModulePathTarget target) { - // TODO: This feels unnecessary since it can be calculated based on the current user... if (ModulePathTarget.CurrentUser == target) { + // TODO: This feels unnecessary since it can be calculated based on the current user... return ReadValueFromFile(modulePathFileName, "UserModulePath"); } else if (ModulePathTarget.SharedCommon == target) @@ -194,8 +191,8 @@ namespace System.Management.Automation } else { - // TODO: Better way to handle this? - throw new InvalidEnumArgumentException(); + // TODO: Is there a better way to handle this? + throw new ArgumentException(); } } @@ -212,10 +209,10 @@ namespace System.Management.Automation /// /// Whether this is a system-wide or per-user setting. /// The shell associated with this policy. Typically, it is "Microsoft.PowerShell" - /// + /// The execution policy if found. Null otherwise. internal override string GetMachineExecutionPolicy(PropertyScope scope, string shellId) { - string execPolicy = "Undefined"; + string execPolicy = null; string scopeDirectory = psHomeConfigDirectory; // Defaults to system wide. @@ -232,13 +229,22 @@ namespace System.Management.Automation { execPolicy = rawExecPolicy; } - // TODO: Throw if NullOrEmpty? return execPolicy; } - internal override void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId) // TODO: Necessary? + internal override void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId) { - // TODO: Work to do here if I decide to support it + string scopeDirectory = psHomeConfigDirectory; + + // Defaults to system wide. + if (PropertyScope.CurrentUser == scope) + { + scopeDirectory = appDataConfigDirectory; + } + + string fileName = Path.Combine(scopeDirectory, execPolicyFileName); + + RemoveValueFromFile(fileName, shellId); } internal override void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) @@ -295,7 +301,7 @@ namespace System.Management.Automation /// "ConsolePrompting" : bool /// } /// - /// Whether console prompting should happen. + /// Whether console prompting should happen. If the value cannot be read it defaults to false. internal override bool GetConsolePrompting(ref Exception exception) { string fileName = Path.Combine(psHomeConfigDirectory, consolePromptingFileName); @@ -317,7 +323,7 @@ namespace System.Management.Automation /// "DisablePromptToUpdateHelp" : bool /// } /// - /// Boolean indicating whether Update-Help should prompt + /// Boolean indicating whether Update-Help should prompt. If the value cannot be read, it defaults to false. internal override bool GetDisablePromptToUpdateHelp() { string fileName = Path.Combine(psHomeConfigDirectory, updateHelpPromptFileName); @@ -339,7 +345,7 @@ namespace System.Management.Automation /// "DefaultSourcePath" : "path to local updatable help location" /// } /// - /// + /// The source path if found, null otherwise. internal override string GetDefaultSourcePath() { string fileName = Path.Combine(psHomeConfigDirectory, updatableHelpSourcePathFileName); @@ -350,8 +356,7 @@ namespace System.Management.Automation { return rawExecPolicy; } - // TODO: Throw if NullOrEmpty? - return String.Empty; + return null; } internal override void SetDefaultSourcePath(string defaultPath) @@ -370,7 +375,11 @@ namespace System.Management.Automation using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) { JObject jsonObject = (JObject) JToken.ReadFrom(jsonReader); - return jsonObject.GetValue(key).ToObject(); + JToken value = jsonObject.GetValue(key); + if (null != value) + { + return value.ToObject(); + } } } catch (ArgumentException) { } @@ -389,8 +398,80 @@ namespace System.Management.Automation /// /// TODO: Should this return success fail or throw? - /// - /// TODO: Catch exceptions + /// + /// + /// + /// + /// + /// Whether the key-value pair should be added or removed from the file + private void UpdateValueInFile(string fileName, string key, T value, bool addValue) + { + try + { + JObject objectToWrite = new JObject(); + + if (File.Exists(fileName)) + { + // Since multiple properties can be in a single file, replacement + // is required instead of overwrite if a file already exists. + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + using (StreamReader streamRdr = new StreamReader(fs)) + using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) + { + JObject jsonObject = (JObject) JToken.ReadFrom(jsonReader); + IEnumerable properties = jsonObject.Properties(); + foreach (JProperty property in properties) + { + if (String.Equals(property.Name, key, StringComparison.OrdinalIgnoreCase)) + { + if (addValue) + { + // Add the updated value to the output object instead + // of the preexisting one + objectToWrite.Add(new JProperty(key, value)); + } + // else the value is skipped so it is removed from the file + } + else + { + // This preserves existing properties that do not + // match the one to update + objectToWrite.Add(new JProperty(property)); + } + } + } + } + else + { + if (addValue) + { + // The file doesn't exist, so create it with the new property + objectToWrite.Add(new JProperty(key, value)); + } + } + + using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) + using (StreamWriter streamWriter = new StreamWriter(fs)) + using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) + { + objectToWrite.WriteTo(jsonWriter); + } + + } + catch (ArgumentException) { } + //catch (ArgumentNullException) { } + catch (NotSupportedException) { } + catch (FileNotFoundException) { } + catch (System.IO.IOException) { } + //catch (DirectoryNotFoundException) { } + catch (System.Security.SecurityException) { } + catch (UnauthorizedAccessException) { } + //catch (PathTooLongException) { } + //catch (ArgumentOutOfRangeException) { } + } + + /// + /// TODO: Should this return success, fail, or throw? /// /// /// @@ -398,233 +479,20 @@ namespace System.Management.Automation /// private void WriteValueToFile(string fileName, string key, T value) { - JObject objectToWrite = new JObject(); + UpdateValueInFile(fileName, key, value, true); + } - if (File.Exists(fileName)) - { - // Since multiple properties can be in a single file, replacement - // is required instead of overwrite if a file already exists. - using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) - using (StreamReader streamRdr = new StreamReader(fs)) - using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) - { - JObject jsonObject = (JObject) JToken.ReadFrom(jsonReader); - IEnumerable properties = jsonObject.Properties(); - foreach (JProperty property in properties) - { - if (String.Equals(property.Name, key, StringComparison.OrdinalIgnoreCase)) - { - // Add the updated value to the output object instead - // of the preexisting one - objectToWrite.Add(new JProperty(key, value)); - } - else - { - // This preserves existing properties that do not - // match the one to update - objectToWrite.Add(new JProperty(property)); - } - } - } - } - else - { - // The file doesn't exist, so create it with the new property - objectToWrite.Add(new JProperty(key, value)); - } - - using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) - using (StreamWriter streamWriter = new StreamWriter(fs)) - using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) - { - objectToWrite.WriteTo(jsonWriter); - } + /// + /// TODO: Should this return success, fail, or throw? + /// + /// + /// + /// + private void RemoveValueFromFile(string fileName, string key) + { + UpdateValueInFile(fileName, key, default(T), false); } } - /* - /// - /// TODO: Add caching so for faster access? Would prevent runtime changes from affecting runtime reads though... - /// - internal class XmlConfigFileAccessor : PropertyAccessor - { - private string fileName = string.Empty; - - internal XmlConfigFileAccessor() - { - Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly; - var result = Path.GetDirectoryName(ClrFacade.GetAssemblyLocation(assembly)); - fileName = result + "\\PowerShellConfig.xml"; - } - - internal override string GetApplicationBase(string version) - { - return ReadElementContentFromFileAsString("PSHome"); - } - - internal override string GetModulePath() - { - return ReadElementContentFromFileAsString("PSModulePath"); - } - - internal override string GetMachineShellPath(string shellId) - { - return ReadElementContentFromFileAsString("MachinePowerShellExePath"); - } - - internal override string GetMachineExecutionPolicy(string shellId) - { - - return ReadElementContentFromFileAsString("MachineExecutionPolicy"); - } - - internal override void SetMachineExecutionPolicy(string shellId, string executionPolicy) - { - this.ReplaceElementValueInFile("MachineExecutionPolicy", executionPolicy); - } - - internal override void RemoveMachineExecutionPolicy(string shellId) - { - this.RemoveElementFromFile("MachineExecutionPolicy"); - } - - private string ReadElementContentFromFileAsString(string elementName) - { - using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) - using (StreamReader streamRdr = new StreamReader(fs)) - { - XmlReaderSettings xmlReaderSettings = - InternalDeserializer.XmlReaderSettingsForUntrustedXmlDocument.Clone(); - - using (XmlReader reader = XmlReader.Create(streamRdr, xmlReaderSettings)) - { - reader.MoveToContent(); - if (reader.ReadToDescendant(elementName)) - { - return reader.ReadElementContentAsString(); - } - } - } - return string.Empty; - } - - private void AddElementToFile(string elementName, string elementValue) - { - XmlDocument doc = new XmlDocument(); - - using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) - using (StreamReader streamRdr = new StreamReader(fs)) - { - doc.Load(streamRdr); - - XmlNodeList nodeList = doc.GetElementsByTagName(elementName); - - if (nodeList.Count > 0) - { - // Element exists. Replace it. - this.ReplaceElementValueInFile(elementName, elementValue); - } - else - { - XmlElement newElem = doc.CreateElement(elementName); - newElem.InnerText = elementValue; - doc.DocumentElement.AppendChild(newElem); - } - } - WriteDocumentToFile(doc); - } - - private void RemoveElementFromFile(string elementName) - { - XmlDocument doc = new XmlDocument(); - - using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) - using (StreamReader streamRdr = new StreamReader(fs)) - { - doc.Load(streamRdr); - - XmlNodeList nodeList = doc.GetElementsByTagName(elementName); - - if (0 == nodeList.Count) - { - // There is nothing to do because the specified node does not exist - return; - } - else if (nodeList.Count > 1) - { - // throw docuemnt format exception? Nodes should be unique... - return; - } - else - { - XmlNode first = nodeList.Item(0); - if (null != first) - { - doc.DocumentElement.RemoveChild(first); - } - } - } - WriteDocumentToFile(doc); - } - - private void ReplaceElementValueInFile(string elementName, string elementValue) - { - XmlDocument doc = new XmlDocument(); - - using (FileStream fs = new FileStream(this.fileName, FileMode.Open, FileAccess.Read)) - using (StreamReader streamRdr = new StreamReader(fs)) - { - doc.Load(streamRdr); - - XmlNodeList nodeList = doc.GetElementsByTagName(elementName); - - if (0 == nodeList.Count) - { - // There is nothing to do because the specified node does not exist - return; - } - else if (nodeList.Count > 1) - { - // throw docuemnt format exception? Nodes should be unique... - return; - } - else - { - XmlNode first = nodeList.Item(0); - if (null != first) - { - first.InnerText = elementValue; - } - } - // Alternate technique if the first one doesnt work - // Node is present, so replace it - //XmlElement newElem = doc.CreateElement("title"); - //newElem.InnerText = elementValue; - - //Replace the title element. - //doc.DocumentElement.ReplaceChild(elem, root.FirstChild); - - } - WriteDocumentToFile(doc); - } - - private void WriteDocumentToFile(XmlDocument doc) - { - using (FileStream fs = new FileStream(this.fileName, FileMode.Create, FileAccess.Write)) - using (StreamWriter streamWriter = new StreamWriter(fs)) - { - XmlWriterSettings xmlSettings = new XmlWriterSettings(); - xmlSettings.Encoding = Encoding.UTF8; - xmlSettings.CloseOutput = true; - xmlSettings.Indent = true; - - using (XmlWriter writer = XmlWriter.Create(streamWriter, xmlSettings)) - { - doc.WriteTo(writer); - } - } - } - } - */ internal class RegistryAccessor : PropertyAccessor { @@ -647,7 +515,7 @@ namespace System.Management.Automation internal override string GetModulePath(ModulePathTarget target) { // TODO: Better way to handle this? - throw new InvalidEnumArgumentException(); + throw new ArgumentException(); } /// diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 9c61a6cab..d79544da6 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -278,6 +278,18 @@ namespace System.Management.Automation private static string[] s_productFolderDirectories; + /// + /// Specifies the per-user configuration settings directory in a platform agnostic manner. + /// Ex: + /// %LOCALAPPDATA%\PowerShell\ + /// + /// The current user's configuration settings directory + internal static string GetUserSettingsDirectory() + { + string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + return Path.Combine(appDataPath, "PowerShell", PSVersionInfo.PSVersion.ToString()); // TODO: This should be the external version#, but I don't see it anywhere in the code + } + private static string[] GetProductFolderDirectories() { if (s_productFolderDirectories == null) From 33c1f2db23234e0fb5fc182815c27caa2b832aca Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Tue, 26 Jul 2016 16:01:31 -0700 Subject: [PATCH 06/12] Fixing execution policy so test automation works --- appveyor.yml | 4 +- build.psm1 | 5 +- .../msh/ConsoleHostUserInterfaceSecurity.cs | 6 +- .../engine/PropertyAccessor.cs | 325 ++++++++---------- .../engine/Utils.cs | 13 +- .../engine/hostifaces/LocalPipeline.cs | 37 +- .../help/UpdatableHelpSystem.cs | 9 +- src/System.Management.Automation/project.json | 3 + .../security/SecuritySupport.cs | 67 ++-- src/powershell-native/CMakeLists.txt | 2 +- 10 files changed, 243 insertions(+), 228 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index f53c786d2..6caea8428 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -40,7 +40,7 @@ test_script: $env:CoreOutput = Split-Path -Parent (Get-PSOutput -Options (New-PSOptions -Publish)) Write-Host -Foreground Green 'Run CoreCLR tests' $testResultsFile = "$pwd\TestsResults.xml" - & ("$env:CoreOutput\powershell.exe") -noprofile -noninteractive -c "Invoke-Pester test/powershell -Tag 'CI' -ExcludeTag 'Slow' -OutputFormat NUnitXml -OutputFile $testResultsFile" + & ("$env:CoreOutput\powershell.exe") -noprofile -noninteractive -c "Set-ExecutionPolicy -Scope Process Unrestricted; Invoke-Pester test/powershell -Tag 'CI' -ExcludeTag 'Slow' -OutputFormat NUnitXml -OutputFile $testResultsFile" (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile)) # @@ -48,7 +48,7 @@ test_script: $env:FullOutput = Split-Path -Parent (Get-PSOutput -Options (New-PSOptions -FullCLR)) Write-Host -Foreground Green 'Run FullCLR tests' $testResultsFileFullCLR = "$pwd\TestsResults.FullCLR.xml" - Start-DevPowerShell -FullCLR -NoNewWindow -ArgumentList '-noprofile', '-noninteractive' -Command "Invoke-Pester test/fullCLR -ExcludeTag 'Slow' -OutputFormat NUnitXml -OutputFile $testResultsFileFullCLR" + Start-DevPowerShell -FullCLR -NoNewWindow -ArgumentList '-noprofile', '-noninteractive' -Command "Set-ExecutionPolicy -Scope Process Unrestricted; Invoke-Pester test/fullCLR -ExcludeTag 'Slow' -OutputFormat NUnitXml -OutputFile $testResultsFileFullCLR" (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFileFullCLR)) # # Fail the build, if tests failed diff --git a/build.psm1 b/build.psm1 index b345f7617..89db96c77 100644 --- a/build.psm1 +++ b/build.psm1 @@ -271,7 +271,7 @@ function Start-PSBuild { $nativeResourcesFolder = $_ Get-ChildItem $nativeResourcesFolder -Filter "*.mc" | % { $command = @" -cmd.exe /C cd /d "(Get-Location)" "&" "$($vcVarsPath)\vcvarsall.bat" "$NativeHostArch" "&" "$mcexe" -o -d -c -U $($_.FullName) -h "$nativeResourcesFolder" -r "$nativeResourcesFolder" +cmd.exe /C cd /d "(Get-Location)" "&" "$($vcVarsPath)\vcvarsall.bat" "$NativeHostArch" "&" mc.exe -o -d -c -U $($_.FullName) -h "$nativeResourcesFolder" -r "$nativeResourcesFolder" "@ log " Executing mc.exe Command: $command" Start-NativeExecution { Invoke-Expression -Command:$command } @@ -526,6 +526,7 @@ function Start-PSPester { [string[]]$Tag = "CI", [string]$Path = "$PSScriptRoot/test/powershell" ) + $tagString = "-outputFormat ${OutputFormat} -outputFile ${outputFile} " if ( ! $DisableExit ) { $tagString += " -EnableExit" } if ( $ExcludeTag -and ($ExcludeTag -ne "")) { $tagString += " -ExcludeTag @('" + (${ExcludeTag} -join "','") + "')" } @@ -537,7 +538,7 @@ function Start-PSPester { Write-Verbose "Import-Module '$moduleDir'; Invoke-Pester $tagString $Path" $powershellexe = get-psoutput - & $powershell -noprofile -c "Set-ExecutionPolicy Unrestricted; Import-Module '$moduleDir'; Invoke-Pester $tagString $Path" + & $powershell -noprofile -c "Set-ExecutionPolicy -Scope Process Unrestricted; Import-Module '$moduleDir'; Invoke-Pester $tagString $Path" if ($LASTEXITCODE -ne 0) { throw "$LASTEXITCODE Pester tests failed" } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs index 15c63ce7a..c5c88d67d 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs @@ -170,12 +170,12 @@ namespace Microsoft.PowerShell // APIs private bool PromptUsingConsole() { -#if CORECLR // on Nano there is no other way to prompt except by using console +#if CORECLR + // on Nano there is no other way to prompt except by using console return true; #else - PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); Exception exception = null; - bool promptUsingConsole = propertyAccessor.GetConsolePrompting(ref exception); + bool promptUsingConsole = ConfigPropertyAccessor.Instance.GetConsolePrompting(ref exception); if (null != exception) { s_tracer.TraceError("Could not read CredUI registry key: " + exception.Message); diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index 10ca8c5fc..c83f737c8 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -9,20 +9,17 @@ using System.Globalization; using System.Threading; using System.Management.Automation; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Microsoft.Win32; #if CORECLR // Some APIs are missing from System.Environment. We use System.Management.Automation.Environment as a proxy type: // - for missing APIs, System.Management.Automation.Environment has extension implementation. // - for existing APIs, System.Management.Automation.Environment redirect the call to System.Environment. using Environment = System.Management.Automation.Environment; -#else -//using Microsoft.Win32; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; #endif -using Microsoft.Win32; - namespace System.Management.Automation { @@ -30,8 +27,40 @@ namespace System.Management.Automation /// Leverages the strategy pattern to abstract away the details of gathering properties from outside sources. /// Note: This is a class so that it can be internal. /// - internal abstract class PropertyAccessor + internal abstract class ConfigPropertyAccessor { + #region Statics + /// + /// Static constructor to instantiate an instance + /// + static ConfigPropertyAccessor() + { +#if CORECLR + // TODO: Update this for #1184 + //if (Platform.IsInbox()) + //{ + // Instance = new RegistryAccessor(); + //} + //else + //{ + // Instance = new JsonConfigFileAccessor(); + //} + // Remove this following line: + Instance = new JsonConfigFileAccessor(); +#else + Instance = new RegistryAccessor(); +#endif + } + /// + /// The instance of the ConfigPropertyAccessor to use to iteract with properties. + /// Derived classes should not be directly instantiated. + /// + internal static readonly ConfigPropertyAccessor Instance; + + #endregion // Statics + + #region Enums + /// /// Describes the scope of the property query. /// SystemWide properties apply to all users. @@ -53,6 +82,10 @@ namespace System.Management.Automation VersionSpecific = 2 } + #endregion // Enums + + #region Interface Methods + /// /// Existing Key = HKLM:\System\CurrentControlSet\Control\Session Manager\Environment /// Proposed value = %ProgramFiles%\PowerShell\Modules by default @@ -69,18 +102,9 @@ namespace System.Management.Automation /// Where it should check for the value. /// The shell associated with this policy. Typically, it is "Microsoft.PowerShell" /// - internal abstract string GetMachineExecutionPolicy(PropertyScope scope, string shellId); - internal abstract void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId); - internal abstract void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy); - - /// - /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds - /// Proposed value = Existing value, otherwise 10. - /// - /// Max stack size in MB. If not set, defaults to 10 MB. - internal abstract int GetPipeLineMaxStackSizeMb(int defaultValue); - - internal abstract void SetPipeLineMaxStackSizeMb(int maxStackSize); + internal abstract string GetExecutionPolicy(PropertyScope scope, string shellId); + internal abstract void RemoveExecutionPolicy(PropertyScope scope, string shellId); + internal abstract void SetExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy); /// /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds @@ -105,30 +129,12 @@ namespace System.Management.Automation /// internal abstract string GetDefaultSourcePath(); internal abstract void SetDefaultSourcePath(string defaultPath); + + #endregion // Interface Methods } - internal class PropertyAccessorFactory - { - /// - /// Template method to generate the appropriate accessor for a given environment. - /// - /// - internal static PropertyAccessor GetPropertyAccessor() - { - if (null == _activePropertyAccessor) - { #if CORECLR - // TODO: I must differentiate between inbox PS and side-by-side PS here eventually - _activePropertyAccessor = new JsonConfigFileAccessor(); -#else - _activePropertyAccessor = new RegistryAccessor(); -#endif - } - return _activePropertyAccessor; - } - - private static PropertyAccessor _activePropertyAccessor; - } + // TODO: JSON .Net should not be a required component for inbox PowerShell. We need to decide on the long term plan. /// /// JSON configuration file accessor @@ -136,24 +142,18 @@ namespace System.Management.Automation /// Reads from and writes to configuration files. The values stored were /// originally stored in the Windows registry. /// - internal class JsonConfigFileAccessor : PropertyAccessor + internal class JsonConfigFileAccessor : ConfigPropertyAccessor { private string psHomeConfigDirectory; private string appDataConfigDirectory; private const string configDirectoryName = "Configuration"; - private const string modulePathFileName = "PSModulePath.json"; - private const string execPolicyFileName = "ExecutionPolicy.json"; - private const string maxStackSizeFileName = "PipeLineMaxStackSizeMB.json"; - private const string consolePromptingFileName = "ConsolePrompting.json"; - private const string updateHelpPromptFileName = "UpdateHelpPrompt.json"; - private const string updatableHelpSourcePathFileName = "UpdatableHelpDefaultSourcePath.json"; + private const string configFileName = "PowerShellProperties.json"; internal JsonConfigFileAccessor() { // // Initialize (and create if necessary) the system-wide configuration directory // - // TODO: Use Utils.GetApplicationBase("Microsoft.PowerShell") instead? Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly; psHomeConfigDirectory = Path.Combine(Path.GetDirectoryName(assembly.Location), configDirectoryName); @@ -174,25 +174,26 @@ namespace System.Management.Automation } /// - /// Note: This value is not writable, so it MUST be set during post-install configuration + /// Note: This value is not writable, so it MUST be set during post-install configuration. + /// Also, it will only be written to the $PSHOME configuration file (not the per-user file). /// /// - /// + /// Value if found, null otherwise. The behavior matches Environment.GetEnvironmentVariable(). internal override string GetModulePath(ModulePathTarget target) { + string fileName = Path.Combine(psHomeConfigDirectory, configFileName); if (ModulePathTarget.CurrentUser == target) { // TODO: This feels unnecessary since it can be calculated based on the current user... - return ReadValueFromFile(modulePathFileName, "UserModulePath"); + return ReadValueFromFile(fileName, "UserModulePath"); } else if (ModulePathTarget.SharedCommon == target) { - return ReadValueFromFile(modulePathFileName, "SharedCommonModulePath"); + return ReadValueFromFile(fileName, "SharedCommonModulePath"); } else { - // TODO: Is there a better way to handle this? - throw new ArgumentException(); + return null; } } @@ -202,15 +203,15 @@ namespace System.Management.Automation /// /// Schema: /// { - /// "shell ID string" : "execution policy string" + /// "shell ID string","ExecutionPolicy" : "execution policy string" /// } /// - /// Note: If we switch to a single config file, then this will need to be nested + /// 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. /// /// Whether this is a system-wide or per-user setting. /// The shell associated with this policy. Typically, it is "Microsoft.PowerShell" /// The execution policy if found. Null otherwise. - internal override string GetMachineExecutionPolicy(PropertyScope scope, string shellId) + internal override string GetExecutionPolicy(PropertyScope scope, string shellId) { string execPolicy = null; string scopeDirectory = psHomeConfigDirectory; @@ -221,9 +222,9 @@ namespace System.Management.Automation scopeDirectory = appDataConfigDirectory; } - string fileName = Path.Combine(scopeDirectory, execPolicyFileName); - - string rawExecPolicy = ReadValueFromFile(fileName, shellId); + string fileName = Path.Combine(scopeDirectory, configFileName); + string valueName = string.Concat(shellId, ":", "ExecutionPolicy"); + string rawExecPolicy = ReadValueFromFile(fileName, valueName); if (!String.IsNullOrEmpty(rawExecPolicy)) { @@ -232,7 +233,7 @@ namespace System.Management.Automation return execPolicy; } - internal override void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId) + internal override void RemoveExecutionPolicy(PropertyScope scope, string shellId) { string scopeDirectory = psHomeConfigDirectory; @@ -242,12 +243,12 @@ namespace System.Management.Automation scopeDirectory = appDataConfigDirectory; } - string fileName = Path.Combine(scopeDirectory, execPolicyFileName); - - RemoveValueFromFile(fileName, shellId); + string fileName = Path.Combine(scopeDirectory, configFileName); + string valueName = string.Concat(shellId, ":", "ExecutionPolicy"); + RemoveValueFromFile(fileName, valueName); } - internal override void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) + internal override void SetExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) { string scopeDirectory = psHomeConfigDirectory; @@ -257,39 +258,9 @@ namespace System.Management.Automation scopeDirectory = appDataConfigDirectory; } - string fileName = Path.Combine(scopeDirectory, execPolicyFileName); - - WriteValueToFile(fileName, shellId, executionPolicy); - } - - /// - /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds - /// Proposed value = Existing value, otherwise 10. - /// - /// Schema: - /// { - /// "PipeLineMaxStackSizeMB" : int - /// } - /// - /// Max stack size in MB. If not set, defaults to 10 MB. - internal override int GetPipeLineMaxStackSizeMb(int defaultValue) - { - string fileName = Path.Combine(psHomeConfigDirectory, maxStackSizeFileName); - - int maxStackSize = defaultValue; - int rawMaxStackSize = ReadValueFromFile(fileName, "PipeLineMaxStackSizeMB"); - - if (0 != rawMaxStackSize) - { - maxStackSize = rawMaxStackSize; - } - return maxStackSize; - } - - internal override void SetPipeLineMaxStackSizeMb(int maxStackSize) - { - string fileName = Path.Combine(psHomeConfigDirectory, maxStackSizeFileName); - WriteValueToFile(fileName, "PipeLineMaxStackSizeMB", maxStackSize); + string fileName = Path.Combine(scopeDirectory, configFileName); + string valueName = string.Concat(shellId, ":", "ExecutionPolicy"); + WriteValueToFile(fileName, valueName, executionPolicy); } /// @@ -304,13 +275,13 @@ namespace System.Management.Automation /// Whether console prompting should happen. If the value cannot be read it defaults to false. internal override bool GetConsolePrompting(ref Exception exception) { - string fileName = Path.Combine(psHomeConfigDirectory, consolePromptingFileName); + string fileName = Path.Combine(psHomeConfigDirectory, configFileName); return ReadValueFromFile(fileName, "ConsolePrompting"); } internal override void SetConsolePrompting(bool shouldPrompt) { - string fileName = Path.Combine(psHomeConfigDirectory, consolePromptingFileName); + string fileName = Path.Combine(psHomeConfigDirectory, configFileName); WriteValueToFile(fileName, "ConsolePrompting", shouldPrompt); } @@ -326,13 +297,13 @@ namespace System.Management.Automation /// Boolean indicating whether Update-Help should prompt. If the value cannot be read, it defaults to false. internal override bool GetDisablePromptToUpdateHelp() { - string fileName = Path.Combine(psHomeConfigDirectory, updateHelpPromptFileName); + string fileName = Path.Combine(psHomeConfigDirectory, configFileName); return ReadValueFromFile(fileName, "DisablePromptToUpdateHelp"); } internal override void SetDisablePromptToUpdateHelp(bool prompt) { - string fileName = Path.Combine(psHomeConfigDirectory, updateHelpPromptFileName); + string fileName = Path.Combine(psHomeConfigDirectory, configFileName); WriteValueToFile(fileName, "DisablePromptToUpdateHelp", prompt); } @@ -348,7 +319,7 @@ namespace System.Management.Automation /// The source path if found, null otherwise. internal override string GetDefaultSourcePath() { - string fileName = Path.Combine(psHomeConfigDirectory, updatableHelpSourcePathFileName); + string fileName = Path.Combine(psHomeConfigDirectory, configFileName); string rawExecPolicy = ReadValueFromFile(fileName, "DefaultSourcePath"); @@ -361,7 +332,7 @@ namespace System.Management.Automation internal override void SetDefaultSourcePath(string defaultPath) { - string fileName = Path.Combine(psHomeConfigDirectory, updatableHelpSourcePathFileName); + string fileName = Path.Combine(psHomeConfigDirectory, configFileName); WriteValueToFile(fileName, "DefaultSourcePath", defaultPath); } @@ -383,15 +354,11 @@ namespace System.Management.Automation } } catch (ArgumentException) { } - //catch (ArgumentNullException) { } catch (NotSupportedException) { } catch (FileNotFoundException) { } - catch (System.IO.IOException) { } - //catch (DirectoryNotFoundException) { } + catch (IOException) { } catch (System.Security.SecurityException) { } catch (UnauthorizedAccessException) { } - //catch (PathTooLongException) { } - //catch (ArgumentOutOfRangeException) { } return default(T); } @@ -403,71 +370,94 @@ namespace System.Management.Automation /// /// /// - /// Whether the key-value pair should be added or removed from the file + /// Whether the key-value pair should be added to or removed from the file private void UpdateValueInFile(string fileName, string key, T value, bool addValue) { try { - JObject objectToWrite = new JObject(); - - if (File.Exists(fileName)) + // Since multiple properties can be in a single file, replacement + // is required instead of overwrite if a file already exists. + // Handling the read and write operations within a single FileStream + // prevents other processes from reading or writing the file while + // the update is in progress. + using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) + using (StreamReader streamRdr = new StreamReader(fs)) + using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) { - // Since multiple properties can be in a single file, replacement - // is required instead of overwrite if a file already exists. - using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) - using (StreamReader streamRdr = new StreamReader(fs)) - using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) + JObject jsonObject = null; + bool isReadSuccess = jsonReader.Read(); // Safely determines whether there is content to read from the file + + if ( ! isReadSuccess) { - JObject jsonObject = (JObject) JToken.ReadFrom(jsonReader); - IEnumerable properties = jsonObject.Properties(); - foreach (JProperty property in properties) + // The file doesn't already exist and we want to write to it or + // exists with no content. + // A new file will be created that contains only this value. + // If the file doesn't exist and a we don't want to write to it, no + // action is necessary. + if (addValue) { - if (String.Equals(property.Name, key, StringComparison.OrdinalIgnoreCase)) + jsonObject = new JObject(new JProperty(key, value)); + } + else + { + return; + } + } + else + { + // Read the stream into a root JObject for manipulation + jsonObject = (JObject) JToken.ReadFrom(jsonReader); + JProperty propertyToModify = jsonObject.Property(key); + + if (null == propertyToModify) + { + // The property doesn't exist, so add it + if (addValue) { - if (addValue) - { - // Add the updated value to the output object instead - // of the preexisting one - objectToWrite.Add(new JProperty(key, value)); - } - // else the value is skipped so it is removed from the file + jsonObject.Add(new JProperty(key, value)); + } + // else the property doesn't exist so there is nothing to remove + } + // The property exists + else + { + if (addValue) + { + propertyToModify.Replace(new JProperty(key, value)); } else { - // This preserves existing properties that do not - // match the one to update - objectToWrite.Add(new JProperty(property)); + propertyToModify.Remove(); } } } - } - else - { - if (addValue) + + // Reset the stream position to the beginning so that the + // changes to the file can be written to disk + fs.Seek(0, SeekOrigin.Begin); + + // Update the file with new content + using (StreamWriter streamWriter = new StreamWriter(fs)) + using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) { - // The file doesn't exist, so create it with the new property - objectToWrite.Add(new JProperty(key, value)); + // The entire document exists within the root JObject. + // I just need to write that object to produce the document. + jsonObject.WriteTo(jsonWriter); + + // This trims the file if the file shrank. If the file grew, + // it is a no-op. The purpose is to trim extraneous characters + // from the file stream when the resultant JObject is smaller + // than the input JObject. + fs.SetLength(fs.Position); } } - - using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) - using (StreamWriter streamWriter = new StreamWriter(fs)) - using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) - { - objectToWrite.WriteTo(jsonWriter); - } - } catch (ArgumentException) { } - //catch (ArgumentNullException) { } catch (NotSupportedException) { } catch (FileNotFoundException) { } - catch (System.IO.IOException) { } - //catch (DirectoryNotFoundException) { } + catch (IOException) { } catch (System.Security.SecurityException) { } catch (UnauthorizedAccessException) { } - //catch (PathTooLongException) { } - //catch (ArgumentOutOfRangeException) { } } /// @@ -494,7 +484,9 @@ namespace System.Management.Automation } } - internal class RegistryAccessor : PropertyAccessor +#endif // CORECLR + + internal class RegistryAccessor : ConfigPropertyAccessor { private const string DisablePromptToUpdateHelpRegPath = "Software\\Microsoft\\PowerShell"; private const string DisablePromptToUpdateHelpRegPath32 = "Software\\Wow6432Node\\Microsoft\\PowerShell"; @@ -524,7 +516,7 @@ namespace System.Management.Automation /// /// /// The execution policy string if found, otherwise null. - internal override string GetMachineExecutionPolicy(PropertyScope scope, string shellId) + internal override string GetExecutionPolicy(PropertyScope scope, string shellId) { string regKeyName = Utils.GetRegistryConfigurationPath(shellId); RegistryKey scopedKey = Registry.LocalMachine; @@ -538,7 +530,7 @@ namespace System.Management.Automation return GetRegistryString(scopedKey, regKeyName, "ExecutionPolicy"); } - internal override void SetMachineExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) + internal override void SetExecutionPolicy(PropertyScope scope, string shellId, string executionPolicy) { string regKeyName = Utils.GetRegistryConfigurationPath(shellId); RegistryKey scopedKey = Registry.LocalMachine; @@ -558,7 +550,7 @@ namespace System.Management.Automation } } - internal override void RemoveMachineExecutionPolicy(PropertyScope scope, string shellId) + internal override void RemoveExecutionPolicy(PropertyScope scope, string shellId) { string regKeyName = Utils.GetRegistryConfigurationPath(shellId); RegistryKey scopedKey = Registry.LocalMachine; @@ -579,21 +571,6 @@ namespace System.Management.Automation } } - internal override int GetPipeLineMaxStackSizeMb(int defaultValue) - { - string regKeyName = Utils.GetRegistryConfigurationPrefix(); - - int? tempInt = GetRegistryDword(Registry.LocalMachine, regKeyName, "PipelineMaxStackSizeMB"); - - return (tempInt.HasValue ? tempInt.Value : defaultValue); - } - - internal override void SetPipeLineMaxStackSizeMb(int maxStackSize) - { - string regKeyName = Utils.GetRegistryConfigurationPrefix(); - SetRegistryDword(Registry.LocalMachine, regKeyName, "PipelineMaxStackSizeMB", maxStackSize); - } - /// /// Existing Key = HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds /// Proposed value = existing default. Probably "1" @@ -727,7 +704,7 @@ namespace System.Management.Automation catch (ObjectDisposedException) { } catch (System.Security.SecurityException) { } catch (ArgumentException) { } - catch (System.IO.IOException) { } + catch (IOException) { } catch (UnauthorizedAccessException) { } catch (FormatException) { } catch (OverflowException) { } @@ -751,7 +728,7 @@ namespace System.Management.Automation catch (ObjectDisposedException) { } catch (System.Security.SecurityException) { } catch (ArgumentException) { } - catch (System.IO.IOException) { } + catch (IOException) { } catch (UnauthorizedAccessException) { } catch (FormatException) { } catch (OverflowException) { } @@ -794,7 +771,7 @@ namespace System.Management.Automation catch (ObjectDisposedException e) { exception = e; } catch (System.Security.SecurityException e) { exception = e; } catch (ArgumentException e) { exception = e; } - catch (System.IO.IOException e) { exception = e; } + catch (IOException e) { exception = e; } catch (UnauthorizedAccessException e) { exception = e; } catch (FormatException e) { exception = e; } catch (OverflowException e) { exception = e; } @@ -818,7 +795,7 @@ namespace System.Management.Automation catch (ObjectDisposedException) { } catch (System.Security.SecurityException) { } catch (ArgumentException) { } - catch (System.IO.IOException) { } + catch (IOException) { } catch (UnauthorizedAccessException) { } catch (FormatException) { } catch (OverflowException) { } diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index d79544da6..3ba32c7bf 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -237,8 +237,9 @@ namespace System.Management.Automation /// internal static string GetApplicationBase(string shellId) { -#if CORECLR // Use the location of SMA.dll as the application base - // Assembly.GetEntryAssembly is not in CoreCLR. GAC is not in CoreCLR. +#if CORECLR + // Use the location of SMA.dll as the application base + // Assembly.GetEntryAssembly and GAC are not in CoreCLR. Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly; return Path.GetDirectoryName(assembly.Location); #else @@ -280,14 +281,16 @@ namespace System.Management.Automation /// /// Specifies the per-user configuration settings directory in a platform agnostic manner. - /// Ex: - /// %LOCALAPPDATA%\PowerShell\ + /// Windows Ex: + /// %LOCALAPPDATA%\PowerShell + /// Non-Windows Ex: + /// ~/.config/PowerShell /// /// The current user's configuration settings directory internal static string GetUserSettingsDirectory() { string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - return Path.Combine(appDataPath, "PowerShell", PSVersionInfo.PSVersion.ToString()); // TODO: This should be the external version#, but I don't see it anywhere in the code + return Path.Combine(appDataPath, "PowerShell"); } private static string[] GetProductFolderDirectories() diff --git a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs index 6ff6f7a1e..f2b305058 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs @@ -254,8 +254,7 @@ namespace System.Management.Automation.Runspaces { get { - PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); - int i = propertyAccessor.GetPipeLineMaxStackSizeMb(10); + int i = ReadRegistryInt("PipelineMaxStackSizeMB", 10); if (i < 10) i = 10; // minimum 10MB @@ -533,6 +532,40 @@ namespace System.Management.Automation.Runspaces return flowControlException; } + // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 + // Removed HandleUnexpectedExceptions infrastructure + + internal static int ReadRegistryInt(string policyValueName, int defaultValue) + { + RegistryKey key; + try + { + key = Registry.LocalMachine.OpenSubKey(Utils.GetRegistryConfigurationPrefix()); + } + catch (System.Security.SecurityException) + { + return defaultValue; + } + if (null == key) + return defaultValue; + + object temp; + try + { + temp = key.GetValue(policyValueName); + } + catch (System.Security.SecurityException) + { + return defaultValue; + } + if (!(temp is int)) + { + return defaultValue; + } + int i = (int)temp; + return i; + } + // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 // Removed HandleUnexpectedExceptions infrastructure /// diff --git a/src/System.Management.Automation/help/UpdatableHelpSystem.cs b/src/System.Management.Automation/help/UpdatableHelpSystem.cs index 0776fb8a1..4d7b43016 100644 --- a/src/System.Management.Automation/help/UpdatableHelpSystem.cs +++ b/src/System.Management.Automation/help/UpdatableHelpSystem.cs @@ -1593,8 +1593,7 @@ namespace System.Management.Automation.Help /// internal string GetDefaultSourcePath() { - PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); - return propertyAccessor.GetDefaultSourcePath(); + return ConfigPropertyAccessor.Instance.GetDefaultSourcePath(); } /// @@ -1602,8 +1601,7 @@ namespace System.Management.Automation.Help /// internal static void SetDisablePromptToUpdateHelp() { - PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); - propertyAccessor.SetDisablePromptToUpdateHelp(true); + ConfigPropertyAccessor.Instance.SetDisablePromptToUpdateHelp(true); } /// @@ -1619,8 +1617,7 @@ namespace System.Management.Automation.Help return false; } - PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); - return propertyAccessor.GetDisablePromptToUpdateHelp(); + return ConfigPropertyAccessor.Instance.GetDisablePromptToUpdateHelp(); } catch (SecurityException) { diff --git a/src/System.Management.Automation/project.json b/src/System.Management.Automation/project.json index f60feb16c..d692a01fe 100644 --- a/src/System.Management.Automation/project.json +++ b/src/System.Management.Automation/project.json @@ -197,6 +197,9 @@ "System.Management": "", "System.Configuration": "", "System.Configuration.Install": "" + }, + "dependencies": { + "Newtonsoft.Json": "7.0.1" } } } diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index f3b731745..17e6851ff 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -146,7 +146,6 @@ namespace System.Management.Automation.Internal #else string executionPolicy = "Restricted"; string preferenceKey = Utils.GetRegistryConfigurationPath(shellId); - PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); switch (policy) { @@ -166,43 +165,47 @@ namespace System.Management.Automation.Internal switch (scope) { case ExecutionPolicyScope.Process: - { - if (policy == ExecutionPolicy.Undefined) - executionPolicy = null; + { + if (policy == ExecutionPolicy.Undefined) + executionPolicy = null; - Environment.SetEnvironmentVariable("PSExecutionPolicyPreference", executionPolicy); - break; - } + Environment.SetEnvironmentVariable("PSExecutionPolicyPreference", executionPolicy); + break; + } case ExecutionPolicyScope.CurrentUser: + { + // They want to remove it + if (policy == ExecutionPolicy.Undefined) { - // They want to remove it - if (policy == ExecutionPolicy.Undefined) - { - propertyAccessor.RemoveMachineExecutionPolicy(PropertyAccessor.PropertyScope.CurrentUser, shellId); - CleanKeyParents(Registry.CurrentUser, preferenceKey); - } - else - { - propertyAccessor.SetMachineExecutionPolicy(PropertyAccessor.PropertyScope.CurrentUser, shellId, executionPolicy); - } - break; + ConfigPropertyAccessor.Instance.RemoveExecutionPolicy( + ConfigPropertyAccessor.PropertyScope.CurrentUser, shellId); + CleanKeyParents(Registry.CurrentUser, preferenceKey); } + else + { + ConfigPropertyAccessor.Instance.SetExecutionPolicy( + ConfigPropertyAccessor.PropertyScope.CurrentUser, shellId, executionPolicy); + } + break; + } case ExecutionPolicyScope.LocalMachine: + { + // They want to remove it + if (policy == ExecutionPolicy.Undefined) { - // They want to remove it - if (policy == ExecutionPolicy.Undefined) - { - propertyAccessor.RemoveMachineExecutionPolicy(PropertyAccessor.PropertyScope.SystemWide, shellId); - CleanKeyParents(Registry.LocalMachine, preferenceKey); - } - else - { - propertyAccessor.SetMachineExecutionPolicy(PropertyAccessor.PropertyScope.SystemWide, shellId, executionPolicy); - } - break; + ConfigPropertyAccessor.Instance.RemoveExecutionPolicy( + ConfigPropertyAccessor.PropertyScope.SystemWide, shellId); + CleanKeyParents(Registry.LocalMachine, preferenceKey); } + else + { + ConfigPropertyAccessor.Instance.SetExecutionPolicy( + ConfigPropertyAccessor.PropertyScope.SystemWide, shellId, executionPolicy); + } + break; + } } #endif } @@ -586,17 +589,15 @@ namespace System.Management.Automation.Internal /// NULL if it is not defined at this level private static string GetLocalPreferenceValue(string shellId, ExecutionPolicyScope scope) { - PropertyAccessor propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor(); - switch (scope) { // 1: Look up the current-user preference case ExecutionPolicyScope.CurrentUser: - return propertyAccessor.GetMachineExecutionPolicy(PropertyAccessor.PropertyScope.CurrentUser, shellId); + return ConfigPropertyAccessor.Instance.GetExecutionPolicy(ConfigPropertyAccessor.PropertyScope.CurrentUser, shellId); // 2: Look up the system-wide preference case ExecutionPolicyScope.LocalMachine: - return propertyAccessor.GetMachineExecutionPolicy(PropertyAccessor.PropertyScope.SystemWide, shellId); + return ConfigPropertyAccessor.Instance.GetExecutionPolicy(ConfigPropertyAccessor.PropertyScope.SystemWide, shellId); } return null; diff --git a/src/powershell-native/CMakeLists.txt b/src/powershell-native/CMakeLists.txt index 7a7d72a53..dd039341f 100644 --- a/src/powershell-native/CMakeLists.txt +++ b/src/powershell-native/CMakeLists.txt @@ -40,7 +40,7 @@ endif (BUILD_ONECORE) foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${PROJECT_SOURCE_DIR}/Bin/${OUTPUTCONFIG}/${PWRSH_NATIVE_OUTPUT_DIRECTORY}") - message(" Setting output directory for ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG}}") + # message(" Setting output directory for ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG}}") endforeach( OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES ) # From 419db0e832bd8e7f9ded0b28ebe7ae1620bf55d4 Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Fri, 29 Jul 2016 14:01:08 -0700 Subject: [PATCH 07/12] Removing dependency on JSON .Net for FullClr --- build.psm1 | 3 +- .../engine/hostifaces/LocalPipeline.cs | 59 +++++++++---------- src/System.Management.Automation/project.json | 3 - .../nativemsh/pwrshcommon/CMakeLists.txt | 2 - 4 files changed, 31 insertions(+), 36 deletions(-) diff --git a/build.psm1 b/build.psm1 index 89db96c77..dd850d1c8 100644 --- a/build.psm1 +++ b/build.psm1 @@ -267,11 +267,12 @@ function Start-PSBuild { Push-Location "$PSScriptRoot\src\powershell-native" # Compile native resources + $currentLocation = Get-Location @("nativemsh/pwrshplugin") | % { $nativeResourcesFolder = $_ Get-ChildItem $nativeResourcesFolder -Filter "*.mc" | % { $command = @" -cmd.exe /C cd /d "(Get-Location)" "&" "$($vcVarsPath)\vcvarsall.bat" "$NativeHostArch" "&" mc.exe -o -d -c -U $($_.FullName) -h "$nativeResourcesFolder" -r "$nativeResourcesFolder" +cmd.exe /C cd /d "$currentLocation" "&" "$($vcVarsPath)\vcvarsall.bat" "$NativeHostArch" "&" mc.exe -o -d -c -U $($_.FullName) -h "$nativeResourcesFolder" -r "$nativeResourcesFolder" "@ log " Executing mc.exe Command: $command" Start-NativeExecution { Invoke-Expression -Command:$command } diff --git a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs index f2b305058..468734b33 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs @@ -255,7 +255,6 @@ namespace System.Management.Automation.Runspaces get { int i = ReadRegistryInt("PipelineMaxStackSizeMB", 10); - if (i < 10) i = 10; // minimum 10MB else if (i > 100) @@ -536,35 +535,35 @@ namespace System.Management.Automation.Runspaces // Removed HandleUnexpectedExceptions infrastructure internal static int ReadRegistryInt(string policyValueName, int defaultValue) - { - RegistryKey key; - try - { - key = Registry.LocalMachine.OpenSubKey(Utils.GetRegistryConfigurationPrefix()); - } - catch (System.Security.SecurityException) - { - return defaultValue; - } - if (null == key) - return defaultValue; - - object temp; - try - { - temp = key.GetValue(policyValueName); - } - catch (System.Security.SecurityException) - { - return defaultValue; - } - if (!(temp is int)) - { - return defaultValue; - } - int i = (int)temp; - return i; - } + { + RegistryKey key; + try + { + key = Registry.LocalMachine.OpenSubKey(Utils.GetRegistryConfigurationPrefix()); + } + catch (System.Security.SecurityException) + { + return defaultValue; + } + if (null == key) + return defaultValue; + + object temp; + try + { + temp = key.GetValue(policyValueName); + } + catch (System.Security.SecurityException) + { + return defaultValue; + } + if (!(temp is int)) + { + return defaultValue; + } + int i = (int)temp; + return i; + } // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 // Removed HandleUnexpectedExceptions infrastructure diff --git a/src/System.Management.Automation/project.json b/src/System.Management.Automation/project.json index d692a01fe..f60feb16c 100644 --- a/src/System.Management.Automation/project.json +++ b/src/System.Management.Automation/project.json @@ -197,9 +197,6 @@ "System.Management": "", "System.Configuration": "", "System.Configuration.Install": "" - }, - "dependencies": { - "Newtonsoft.Json": "7.0.1" } } } diff --git a/src/powershell-native/nativemsh/pwrshcommon/CMakeLists.txt b/src/powershell-native/nativemsh/pwrshcommon/CMakeLists.txt index 24fd4afe4..d8170fa41 100644 --- a/src/powershell-native/nativemsh/pwrshcommon/CMakeLists.txt +++ b/src/powershell-native/nativemsh/pwrshcommon/CMakeLists.txt @@ -11,14 +11,12 @@ add_library(pwrshcommon target_include_directories(pwrshcommon PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) if (BUILD_ONECORE) - message("Building OneCore version of pwrshcommon.lib") # Libraries to use when creating this binary for Windows on OneCore-based SKUs set(PWRSHCOMMON_WINDOWS_LIBS # onecore.lib ) set_target_properties(pwrshcommon PROPERTIES COMPILE_DEFINITIONS "CORECLR") else () # NOT BUILD_ONECORE - message("Building Full version of pwrshcommon.lib") # Libraries to use when creating this binary for Windows on full SKUs # Note: The appropriate libs get added automatically by VS set(PWRSHCOMMON_WINDOWS_LIBS From 8ffb717e14b4091f1d5d724d10049cf7519153ab Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Tue, 2 Aug 2016 21:36:26 -0700 Subject: [PATCH 08/12] Reducing exception catching within ConfigPropertyAccessor classes to provide greater control to callers regarding the most appropriate way to handle them. --- .../msh/ConsoleHostUserInterfaceSecurity.cs | 26 +- .../engine/PropertyAccessor.cs | 315 ++++++++---------- .../help/UpdatableHelpSystem.cs | 22 +- .../security/SecuritySupport.cs | 12 +- 4 files changed, 185 insertions(+), 190 deletions(-) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs index c5c88d67d..1bf437fd4 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs @@ -174,18 +174,30 @@ namespace Microsoft.PowerShell // on Nano there is no other way to prompt except by using console return true; #else - Exception exception = null; - bool promptUsingConsole = ConfigPropertyAccessor.Instance.GetConsolePrompting(ref exception); - if (null != exception) + bool promptUsingConsole = false; + // Get the configuration setting + try { - s_tracer.TraceError("Could not read CredUI registry key: " + exception.Message); - return false; + promptUsingConsole = ConfigPropertyAccessor.Instance.GetConsolePrompting(); } - else + catch (System.Security.SecurityException e) { - s_tracer.WriteLine("DetermineCredUIPolicy: policy == {0}", promptUsingConsole); + s_tracer.TraceError("Could not read CredUI registry key: " + e.Message); return promptUsingConsole; } + catch (InvalidCastException e) + { + s_tracer.TraceError("Could not parse CredUI registry key: " + e.Message); + return promptUsingConsole; + } + catch (FormatException e) + { + s_tracer.TraceError("Could not parse CredUI registry key: " + e.Message); + return promptUsingConsole; + } + + s_tracer.WriteLine("DetermineCredUIPolicy: policy == {0}", promptUsingConsole); + return promptUsingConsole; #endif } } diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index c83f737c8..027e054c2 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -111,7 +111,7 @@ namespace System.Management.Automation /// Proposed value = existing default. Probably "1" /// /// Whether console prompting should happen. - internal abstract bool GetConsolePrompting(ref Exception exception); + internal abstract bool GetConsolePrompting(); internal abstract void SetConsolePrompting(bool shouldPrompt); /// @@ -134,8 +134,6 @@ namespace System.Management.Automation } #if CORECLR - // TODO: JSON .Net should not be a required component for inbox PowerShell. We need to decide on the long term plan. - /// /// JSON configuration file accessor /// @@ -273,7 +271,7 @@ namespace System.Management.Automation /// } /// /// Whether console prompting should happen. If the value cannot be read it defaults to false. - internal override bool GetConsolePrompting(ref Exception exception) + internal override bool GetConsolePrompting() { string fileName = Path.Combine(psHomeConfigDirectory, configFileName); return ReadValueFromFile(fileName, "ConsolePrompting"); @@ -341,7 +339,8 @@ namespace System.Management.Automation { try { - using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + // Open file for reading, but allow multiple readers + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) using (StreamReader streamRdr = new StreamReader(fs)) using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) { @@ -353,12 +352,9 @@ namespace System.Management.Automation } } } - catch (ArgumentException) { } - catch (NotSupportedException) { } + // The file doesn't exist. Treat this the same way as if the + // key was not present in the file. catch (FileNotFoundException) { } - catch (IOException) { } - catch (System.Security.SecurityException) { } - catch (UnauthorizedAccessException) { } return default(T); } @@ -373,37 +369,26 @@ namespace System.Management.Automation /// Whether the key-value pair should be added to or removed from the file private void UpdateValueInFile(string fileName, string key, T value, bool addValue) { - try - { - // Since multiple properties can be in a single file, replacement - // is required instead of overwrite if a file already exists. - // Handling the read and write operations within a single FileStream - // prevents other processes from reading or writing the file while - // the update is in progress. - using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) - using (StreamReader streamRdr = new StreamReader(fs)) + // Since multiple properties can be in a single file, replacement + // is required instead of overwrite if a file already exists. + // Handling the read and write operations within a single FileStream + // prevents other processes from reading or writing the file while + // the update is in progress. It also locks out readers during write + // operations. + using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) + { + JObject jsonObject = null; + + // UTF8, BOM detection, and bufferSize are the same as the basic stream constructor. + // The most important parameter here is the last one, which keeps the StreamReader + // (and FileStream) open during Dispose so that it can be reused for the write + // operation. + using (StreamReader streamRdr = new StreamReader(fs, Encoding.UTF8, true, 1024, true)) using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) { - JObject jsonObject = null; - bool isReadSuccess = jsonReader.Read(); // Safely determines whether there is content to read from the file - - if ( ! isReadSuccess) - { - // The file doesn't already exist and we want to write to it or - // exists with no content. - // A new file will be created that contains only this value. - // If the file doesn't exist and a we don't want to write to it, no - // action is necessary. - if (addValue) - { - jsonObject = new JObject(new JProperty(key, value)); - } - else - { - return; - } - } - else + // Safely determines whether there is content to read from the file + bool isReadSuccess = jsonReader.Read(); + if (isReadSuccess) { // Read the stream into a root JObject for manipulation jsonObject = (JObject) JToken.ReadFrom(jsonReader); @@ -431,33 +416,43 @@ namespace System.Management.Automation } } } - - // Reset the stream position to the beginning so that the - // changes to the file can be written to disk - fs.Seek(0, SeekOrigin.Begin); - - // Update the file with new content - using (StreamWriter streamWriter = new StreamWriter(fs)) - using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) + else { - // The entire document exists within the root JObject. - // I just need to write that object to produce the document. - jsonObject.WriteTo(jsonWriter); - - // This trims the file if the file shrank. If the file grew, - // it is a no-op. The purpose is to trim extraneous characters - // from the file stream when the resultant JObject is smaller - // than the input JObject. - fs.SetLength(fs.Position); + // The file doesn't already exist and we want to write to it + // or it exists with no content. + // A new file will be created that contains only this value. + // If the file doesn't exist and a we don't want to write to it, no + // action is necessary. + if (addValue) + { + jsonObject = new JObject(new JProperty(key, value)); + } + else + { + return; + } } } + + // Reset the stream position to the beginning so that the + // changes to the file can be written to disk + fs.Seek(0, SeekOrigin.Begin); + + // Update the file with new content + using (StreamWriter streamWriter = new StreamWriter(fs)) + using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) + { + // The entire document exists within the root JObject. + // I just need to write that object to produce the document. + jsonObject.WriteTo(jsonWriter); + + // This trims the file if the file shrank. If the file grew, + // it is a no-op. The purpose is to trim extraneous characters + // from the file stream when the resultant JObject is smaller + // than the input JObject. + fs.SetLength(fs.Position); + } } - catch (ArgumentException) { } - catch (NotSupportedException) { } - catch (FileNotFoundException) { } - catch (IOException) { } - catch (System.Security.SecurityException) { } - catch (UnauthorizedAccessException) { } } /// @@ -576,15 +571,13 @@ namespace System.Management.Automation /// Proposed value = existing default. Probably "1" /// /// Whether console prompting should happen. - internal override bool GetConsolePrompting(ref Exception exception) + internal override bool GetConsolePrompting() { string policyKeyName = Utils.GetRegistryConfigurationPrefix(); - string tempPrompt = GetRegistryString(Registry.LocalMachine, policyKeyName, "ConsolePrompting", ref exception); + string tempPrompt = GetRegistryString(Registry.LocalMachine, policyKeyName, "ConsolePrompting"); if (null != tempPrompt) { - // TODO: It is difficult to tell from the original code how this value is actually stored in the registry. - // I am inferring that it is a "true" or "false" string based on the original code. return Convert.ToBoolean(tempPrompt, CultureInfo.InvariantCulture); } else @@ -638,26 +631,21 @@ namespace System.Management.Automation internal override void SetDisablePromptToUpdateHelp(bool prompt) { int valueToSet = prompt ? 1 : 0; - try + using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DisablePromptToUpdateHelpRegPath, true)) { - using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DisablePromptToUpdateHelpRegPath, true)) + if (hklm != null) { - if (hklm != null) - { - hklm.SetValue(DisablePromptToUpdateHelpRegKey, valueToSet, RegistryValueKind.DWord); - } + hklm.SetValue(DisablePromptToUpdateHelpRegKey, valueToSet, RegistryValueKind.DWord); } + } - using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DisablePromptToUpdateHelpRegPath32, true)) + using (RegistryKey hklm = Registry.LocalMachine.OpenSubKey(DisablePromptToUpdateHelpRegPath32, true)) + { + if (hklm != null) { - if (hklm != null) - { - hklm.SetValue(DisablePromptToUpdateHelpRegKey, valueToSet, RegistryValueKind.DWord); - } + hklm.SetValue(DisablePromptToUpdateHelpRegKey, valueToSet, RegistryValueKind.DWord); } } - catch (UnauthorizedAccessException) {} - catch (System.Security.SecurityException) {} } /// @@ -675,133 +663,114 @@ namespace System.Management.Automation SetRegistryString(Registry.LocalMachine, DefaultSourcePathRegPath, DefaultSourcePathRegKey, defaultPath); } + /// + /// Reads a DWORD from the Registry. Excpetions are intentionally allowed to pass through to + /// the caller because different classes and methods within the code base handle Registry + /// exceptions differently. Some suppress exceptions and others pass them to the user. + /// + /// + /// + /// + /// private int? GetRegistryDword(RegistryKey rootKey, string pathToKey, string valueName) { - try + using (RegistryKey regKey = rootKey.OpenSubKey(pathToKey)) { - using (RegistryKey regKey = rootKey.OpenSubKey(pathToKey)) + if (null == regKey) { - if (null == regKey) - { - // Key not found - return null; - } + // Key not found + return null; + } - // verify the value kind as a string - RegistryValueKind kind = regKey.GetValueKind(valueName); + // verify the value kind as a string + RegistryValueKind kind = regKey.GetValueKind(valueName); - if (kind == RegistryValueKind.DWord) - { - return regKey.GetValue(valueName) as int?; - } - else - { - // The function expected a DWORD, but got another type. This is a coding error or a registry key typing error. - return null; - } + if (kind == RegistryValueKind.DWord) + { + return regKey.GetValue(valueName) as int?; + } + else + { + // The function expected a DWORD, but got another type. This is a coding error or a registry key typing error. + return null; } } - catch (ObjectDisposedException) { } - catch (System.Security.SecurityException) { } - catch (ArgumentException) { } - catch (IOException) { } - catch (UnauthorizedAccessException) { } - catch (FormatException) { } - catch (OverflowException) { } - catch (InvalidCastException) { } - - return null; } + /// + /// Excpetions are intentionally allowed to pass through to + /// the caller because different classes and methods within the code base handle Registry + /// exceptions differently. Some suppress exceptions and others pass them to the user. + /// + /// + /// + /// + /// private void SetRegistryDword(RegistryKey rootKey, string pathToKey, string valueName, int value) { - try + using (RegistryKey regKey = rootKey.OpenSubKey(pathToKey)) { - using (RegistryKey regKey = rootKey.OpenSubKey(pathToKey)) + if (null != regKey) { - if (null != regKey) - { - regKey.SetValue(valueName, value, RegistryValueKind.DWord); - } + regKey.SetValue(valueName, value, RegistryValueKind.DWord); } } - catch (ObjectDisposedException) { } - catch (System.Security.SecurityException) { } - catch (ArgumentException) { } - catch (IOException) { } - catch (UnauthorizedAccessException) { } - catch (FormatException) { } - catch (OverflowException) { } - catch (InvalidCastException) { } } + /// + /// Excpetions are intentionally allowed to pass through to + /// the caller because different classes and methods within the code base handle Registry + /// exceptions differently. Some suppress exceptions and others pass them to the user. + /// + /// + /// + /// + /// private string GetRegistryString(RegistryKey rootKey, string pathToKey, string valueName) { - Exception e = null; - return GetRegistryString(rootKey, pathToKey, valueName, ref e); - } - - private string GetRegistryString(RegistryKey rootKey, string pathToKey, string valueName, ref Exception exception) - { - try + using (RegistryKey regKey = rootKey.OpenSubKey(pathToKey)) { - using (RegistryKey regKey = rootKey.OpenSubKey(pathToKey)) + if (null == regKey) { - if (null == regKey) - { - // Key not found - return null; - } + // Key not found + return null; + } - // verify the value kind as a string - RegistryValueKind kind = regKey.GetValueKind(valueName); + // verify the value kind as a string + RegistryValueKind kind = regKey.GetValueKind(valueName); - if (kind == RegistryValueKind.ExpandString || - kind == RegistryValueKind.String) - { - return regKey.GetValue(valueName) as string; - } - else - { - // The function expected a string, but got another type. This is a coding error or a registry key typing error. - return null; - } + if (kind == RegistryValueKind.ExpandString || + kind == RegistryValueKind.String) + { + return regKey.GetValue(valueName) as string; + } + else + { + // The function expected a string, but got another type. This is a coding error or a registry key typing error. + return null; } } - catch (ObjectDisposedException e) { exception = e; } - catch (System.Security.SecurityException e) { exception = e; } - catch (ArgumentException e) { exception = e; } - catch (IOException e) { exception = e; } - catch (UnauthorizedAccessException e) { exception = e; } - catch (FormatException e) { exception = e; } - catch (OverflowException e) { exception = e; } - catch (InvalidCastException e) { exception = e; } - - return null; } - private string SetRegistryString(RegistryKey rootKey, string pathToKey, string valueName, string value) + /// + /// Excpetions are intentionally allowed to pass through to + /// the caller because different classes and methods within the code base handle Registry + /// exceptions differently. Some suppress exceptions and others pass them to the user. + /// + /// + /// + /// + /// + /// + private void SetRegistryString(RegistryKey rootKey, string pathToKey, string valueName, string value) { - try + using (RegistryKey key = rootKey.CreateSubKey(pathToKey)) { - using (RegistryKey key = rootKey.CreateSubKey(pathToKey)) + if (null != key) { - if (null != key) - { - key.SetValue(valueName, value, RegistryValueKind.String); - } + key.SetValue(valueName, value, RegistryValueKind.String); } } - catch (ObjectDisposedException) { } - catch (System.Security.SecurityException) { } - catch (ArgumentException) { } - catch (IOException) { } - catch (UnauthorizedAccessException) { } - catch (FormatException) { } - catch (OverflowException) { } - catch (InvalidCastException) { } - - return null; } } } // Namespace System.Management.Automation diff --git a/src/System.Management.Automation/help/UpdatableHelpSystem.cs b/src/System.Management.Automation/help/UpdatableHelpSystem.cs index 4d7b43016..ad7d4c866 100644 --- a/src/System.Management.Automation/help/UpdatableHelpSystem.cs +++ b/src/System.Management.Automation/help/UpdatableHelpSystem.cs @@ -1593,7 +1593,14 @@ namespace System.Management.Automation.Help /// internal string GetDefaultSourcePath() { - return ConfigPropertyAccessor.Instance.GetDefaultSourcePath(); + try + { + return ConfigPropertyAccessor.Instance.GetDefaultSourcePath(); + } + catch (SecurityException) + { + return null; + } } /// @@ -1601,7 +1608,18 @@ namespace System.Management.Automation.Help /// internal static void SetDisablePromptToUpdateHelp() { - ConfigPropertyAccessor.Instance.SetDisablePromptToUpdateHelp(true); + try + { + ConfigPropertyAccessor.Instance.SetDisablePromptToUpdateHelp(true); + } + catch (UnauthorizedAccessException) + { + // Ignore AccessDenied related exceptions + } + catch (SecurityException) + { + // Ignore AccessDenied related exceptions + } } /// diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index 17e6851ff..a6481f709 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -178,14 +178,12 @@ namespace System.Management.Automation.Internal // They want to remove it if (policy == ExecutionPolicy.Undefined) { - ConfigPropertyAccessor.Instance.RemoveExecutionPolicy( - ConfigPropertyAccessor.PropertyScope.CurrentUser, shellId); + ConfigPropertyAccessor.Instance.RemoveExecutionPolicy(ConfigPropertyAccessor.PropertyScope.CurrentUser, shellId); CleanKeyParents(Registry.CurrentUser, preferenceKey); } else { - ConfigPropertyAccessor.Instance.SetExecutionPolicy( - ConfigPropertyAccessor.PropertyScope.CurrentUser, shellId, executionPolicy); + ConfigPropertyAccessor.Instance.SetExecutionPolicy(ConfigPropertyAccessor.PropertyScope.CurrentUser, shellId, executionPolicy); } break; } @@ -195,14 +193,12 @@ namespace System.Management.Automation.Internal // They want to remove it if (policy == ExecutionPolicy.Undefined) { - ConfigPropertyAccessor.Instance.RemoveExecutionPolicy( - ConfigPropertyAccessor.PropertyScope.SystemWide, shellId); + ConfigPropertyAccessor.Instance.RemoveExecutionPolicy(ConfigPropertyAccessor.PropertyScope.SystemWide, shellId); CleanKeyParents(Registry.LocalMachine, preferenceKey); } else { - ConfigPropertyAccessor.Instance.SetExecutionPolicy( - ConfigPropertyAccessor.PropertyScope.SystemWide, shellId, executionPolicy); + ConfigPropertyAccessor.Instance.SetExecutionPolicy(ConfigPropertyAccessor.PropertyScope.SystemWide, shellId, executionPolicy); } break; } From b291e2006cd8db3934113a4e45ba8dd27d8d16b5 Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Wed, 3 Aug 2016 11:52:07 -0700 Subject: [PATCH 09/12] Fixing implementation of GetModulePath to conform to the requirements of #1184. --- .../engine/Modules/ModuleIntrinsics.cs | 4 +- .../engine/PropertyAccessor.cs | 66 ++++++++++--------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index 3efd866e9..a1028dc53 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -886,8 +886,8 @@ namespace System.Management.Automation internal static string SetModulePath() { string currentModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.Process); - string systemWideModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.Machine); - string personalModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.User); + string systemWideModulePath = ConfigPropertyAccessor.Instance.GetModulePath(ConfigPropertyAccessor.PropertyScope.SystemWide); + string personalModulePath = ConfigPropertyAccessor.Instance.GetModulePath(ConfigPropertyAccessor.PropertyScope.CurrentUser); string newModulePathString = GetModulePath(currentModulePath, systemWideModulePath, personalModulePath); diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index 027e054c2..dbc8d10ce 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -72,16 +72,6 @@ namespace System.Management.Automation CurrentUser = 1 } - /// - /// Describes the desired module path during a GetModulePath call. - /// - internal enum ModulePathTarget - { - CurrentUser = 0, - SharedCommon = 1, - VersionSpecific = 2 - } - #endregion // Enums #region Interface Methods @@ -93,7 +83,7 @@ namespace System.Management.Automation /// Note: There is no setter because this value is immutable. /// /// Module path values from the config file. - internal abstract string GetModulePath(ModulePathTarget target); + internal abstract string GetModulePath(PropertyScope scope); /// /// Existing Key = HKCU and HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell @@ -172,27 +162,28 @@ namespace System.Management.Automation } /// - /// Note: This value is not writable, so it MUST be set during post-install configuration. - /// Also, it will only be written to the $PSHOME configuration file (not the per-user file). + /// This value is not writable via the API and must be set using a text editor. /// - /// - /// Value if found, null otherwise. The behavior matches Environment.GetEnvironmentVariable(). - internal override string GetModulePath(ModulePathTarget target) + /// + /// Value if found, null otherwise. The behavior matches ModuleIntrinsics.GetExpandedEnvironmentVariable(). + internal override string GetModulePath(PropertyScope scope) { - string fileName = Path.Combine(psHomeConfigDirectory, configFileName); - if (ModulePathTarget.CurrentUser == target) + string scopeDirectory = psHomeConfigDirectory; + + // Defaults to system wide. + if (PropertyScope.CurrentUser == scope) { - // TODO: This feels unnecessary since it can be calculated based on the current user... - return ReadValueFromFile(fileName, "UserModulePath"); + scopeDirectory = appDataConfigDirectory; } - else if (ModulePathTarget.SharedCommon == target) + + string fileName = Path.Combine(scopeDirectory, configFileName); + + string modulePath = ReadValueFromFile(fileName, "PsModulePath"); + if (!string.IsNullOrEmpty(modulePath)) { - return ReadValueFromFile(fileName, "SharedCommonModulePath"); - } - else - { - return null; + modulePath = Environment.ExpandEnvironmentVariables(modulePath); } + return modulePath; } /// @@ -497,12 +488,25 @@ namespace System.Management.Automation /// This method is not supported for systems when reading from the registry. Its value is /// known at compile time for inbox PowerShell. /// - /// - /// - internal override string GetModulePath(ModulePathTarget target) + /// + /// The specified module path. Null if not present. + internal override string GetModulePath(PropertyScope scope) { - // TODO: Better way to handle this? - throw new ArgumentException(); + RegistryKey scopedKey = Registry.LocalMachine; + string regKeyName = @"System\CurrentControlSet\Control\Session Manager\Environment"; + + if (scope == PropertyScope.CurrentUser) + { + scopedKey = Registry.CurrentUser; + regKeyName = "Environment"; + } + + string modulePath = GetRegistryString(scopedKey, regKeyName, "PSMODULEPATH"); + if ( ! string.IsNullOrEmpty(modulePath)) + { + modulePath = Environment.ExpandEnvironmentVariables(modulePath); + } + return modulePath; } /// From 6eb0f2d6c7d10f2fd0a42cda3407e2b2a31f25fc Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Wed, 3 Aug 2016 14:16:37 -0700 Subject: [PATCH 10/12] Addressing reader and writer synchronization to the config file within the process. --- .../engine/PropertyAccessor.cs | 156 ++++++++++-------- 1 file changed, 89 insertions(+), 67 deletions(-) diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index dbc8d10ce..db8c8147a 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -137,6 +137,13 @@ namespace System.Management.Automation private const string configDirectoryName = "Configuration"; private const string configFileName = "PowerShellProperties.json"; + /// + /// Lock used to enable multiple concurrent readers and singular write locks within a + /// single process. + /// TODO: This solution only works for IO from a single process. A more robust solution is needed to enable ReaderWriterLockSlim behavior between proceses. + /// + private ReaderWriterLockSlim fileLock = new ReaderWriterLockSlim(); + internal JsonConfigFileAccessor() { // @@ -328,6 +335,7 @@ namespace System.Management.Automation private T ReadValueFromFile(string fileName, string key) { + fileLock.EnterReadLock(); try { // Open file for reading, but allow multiple readers @@ -343,9 +351,15 @@ namespace System.Management.Automation } } } - // The file doesn't exist. Treat this the same way as if the - // key was not present in the file. - catch (FileNotFoundException) { } + catch (FileNotFoundException) + { + // The file doesn't exist. Treat this the same way as if the + // key was not present in the file. + } + finally + { + fileLock.ExitReadLock(); + } return default(T); } @@ -360,89 +374,97 @@ namespace System.Management.Automation /// Whether the key-value pair should be added to or removed from the file private void UpdateValueInFile(string fileName, string key, T value, bool addValue) { - // Since multiple properties can be in a single file, replacement - // is required instead of overwrite if a file already exists. - // Handling the read and write operations within a single FileStream - // prevents other processes from reading or writing the file while - // the update is in progress. It also locks out readers during write - // operations. - using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) - { - JObject jsonObject = null; - - // UTF8, BOM detection, and bufferSize are the same as the basic stream constructor. - // The most important parameter here is the last one, which keeps the StreamReader - // (and FileStream) open during Dispose so that it can be reused for the write - // operation. - using (StreamReader streamRdr = new StreamReader(fs, Encoding.UTF8, true, 1024, true)) - using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) + fileLock.EnterWriteLock(); + try + { + // Since multiple properties can be in a single file, replacement + // is required instead of overwrite if a file already exists. + // Handling the read and write operations within a single FileStream + // prevents other processes from reading or writing the file while + // the update is in progress. It also locks out readers during write + // operations. + using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) { - // Safely determines whether there is content to read from the file - bool isReadSuccess = jsonReader.Read(); - if (isReadSuccess) - { - // Read the stream into a root JObject for manipulation - jsonObject = (JObject) JToken.ReadFrom(jsonReader); - JProperty propertyToModify = jsonObject.Property(key); + JObject jsonObject = null; - if (null == propertyToModify) + // UTF8, BOM detection, and bufferSize are the same as the basic stream constructor. + // The most important parameter here is the last one, which keeps the StreamReader + // (and FileStream) open during Dispose so that it can be reused for the write + // operation. + using (StreamReader streamRdr = new StreamReader(fs, Encoding.UTF8, true, 1024, true)) + using (JsonTextReader jsonReader = new JsonTextReader(streamRdr)) + { + // Safely determines whether there is content to read from the file + bool isReadSuccess = jsonReader.Read(); + if (isReadSuccess) { - // The property doesn't exist, so add it - if (addValue) + // Read the stream into a root JObject for manipulation + jsonObject = (JObject) JToken.ReadFrom(jsonReader); + JProperty propertyToModify = jsonObject.Property(key); + + if (null == propertyToModify) { - jsonObject.Add(new JProperty(key, value)); + // The property doesn't exist, so add it + if (addValue) + { + jsonObject.Add(new JProperty(key, value)); + } + // else the property doesn't exist so there is nothing to remove + } + // The property exists + else + { + if (addValue) + { + propertyToModify.Replace(new JProperty(key, value)); + } + else + { + propertyToModify.Remove(); + } } - // else the property doesn't exist so there is nothing to remove } - // The property exists else { + // The file doesn't already exist and we want to write to it + // or it exists with no content. + // A new file will be created that contains only this value. + // If the file doesn't exist and a we don't want to write to it, no + // action is necessary. if (addValue) { - propertyToModify.Replace(new JProperty(key, value)); + jsonObject = new JObject(new JProperty(key, value)); } else { - propertyToModify.Remove(); + return; } } } - else + + // Reset the stream position to the beginning so that the + // changes to the file can be written to disk + fs.Seek(0, SeekOrigin.Begin); + + // Update the file with new content + using (StreamWriter streamWriter = new StreamWriter(fs)) + using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) { - // The file doesn't already exist and we want to write to it - // or it exists with no content. - // A new file will be created that contains only this value. - // If the file doesn't exist and a we don't want to write to it, no - // action is necessary. - if (addValue) - { - jsonObject = new JObject(new JProperty(key, value)); - } - else - { - return; - } + // The entire document exists within the root JObject. + // I just need to write that object to produce the document. + jsonObject.WriteTo(jsonWriter); + + // This trims the file if the file shrank. If the file grew, + // it is a no-op. The purpose is to trim extraneous characters + // from the file stream when the resultant JObject is smaller + // than the input JObject. + fs.SetLength(fs.Position); } } - - // Reset the stream position to the beginning so that the - // changes to the file can be written to disk - fs.Seek(0, SeekOrigin.Begin); - - // Update the file with new content - using (StreamWriter streamWriter = new StreamWriter(fs)) - using (JsonTextWriter jsonWriter = new JsonTextWriter(streamWriter)) - { - // The entire document exists within the root JObject. - // I just need to write that object to produce the document. - jsonObject.WriteTo(jsonWriter); - - // This trims the file if the file shrank. If the file grew, - // it is a no-op. The purpose is to trim extraneous characters - // from the file stream when the resultant JObject is smaller - // than the input JObject. - fs.SetLength(fs.Position); - } + } + finally + { + fileLock.ExitWriteLock(); } } From d3625f76622aad2a17acf4635997a20c79d8ecb3 Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Wed, 3 Aug 2016 21:04:53 -0700 Subject: [PATCH 11/12] Resolving issue with Registry reads where values are not present. --- .../engine/PropertyAccessor.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index db8c8147a..b28809dc2 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -762,19 +762,21 @@ namespace System.Management.Automation return null; } - // verify the value kind as a string - RegistryValueKind kind = regKey.GetValueKind(valueName); + object regValue = regKey.GetValue(valueName); + if (null != regValue) + { + // verify the value kind as a string + RegistryValueKind kind = regKey.GetValueKind(valueName); - if (kind == RegistryValueKind.ExpandString || - kind == RegistryValueKind.String) - { - return regKey.GetValue(valueName) as string; - } - else - { - // The function expected a string, but got another type. This is a coding error or a registry key typing error. - return null; + if (kind == RegistryValueKind.ExpandString || + kind == RegistryValueKind.String) + { + return regValue as string; + } } + + // The function expected a string, but got another type or the value doesn't exist. + return null; } } From 1e4615114ca1af153eaa684bcae4139aad5a86bb Mon Sep 17 00:00:00 2001 From: Mike Richmond Date: Thu, 4 Aug 2016 13:15:08 -0700 Subject: [PATCH 12/12] Reducing duplication of Environment variable extraction --- .../engine/Modules/ModuleIntrinsics.cs | 2 +- .../engine/PropertyAccessor.cs | 18 +++++------------- .../security/SecuritySupport.cs | 1 + 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index a1028dc53..5f205bce6 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -641,7 +641,7 @@ namespace System.Management.Automation return null; } - private static string GetExpandedEnvironmentVariable(string name, EnvironmentVariableTarget target) + internal static string GetExpandedEnvironmentVariable(string name, EnvironmentVariableTarget target) { string result = Environment.GetEnvironmentVariable(name, target); if (!string.IsNullOrEmpty(result)) diff --git a/src/System.Management.Automation/engine/PropertyAccessor.cs b/src/System.Management.Automation/engine/PropertyAccessor.cs index b28809dc2..6a63302ad 100644 --- a/src/System.Management.Automation/engine/PropertyAccessor.cs +++ b/src/System.Management.Automation/engine/PropertyAccessor.cs @@ -507,28 +507,20 @@ namespace System.Management.Automation } /// - /// This method is not supported for systems when reading from the registry. Its value is - /// known at compile time for inbox PowerShell. + /// Gets the specified module path from the appropriate Environment entry in the registry. /// /// /// The specified module path. Null if not present. internal override string GetModulePath(PropertyScope scope) { - RegistryKey scopedKey = Registry.LocalMachine; - string regKeyName = @"System\CurrentControlSet\Control\Session Manager\Environment"; - - if (scope == PropertyScope.CurrentUser) + if (PropertyScope.CurrentUser == scope) { - scopedKey = Registry.CurrentUser; - regKeyName = "Environment"; + return ModuleIntrinsics.GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.User); } - - string modulePath = GetRegistryString(scopedKey, regKeyName, "PSMODULEPATH"); - if ( ! string.IsNullOrEmpty(modulePath)) + else { - modulePath = Environment.ExpandEnvironmentVariables(modulePath); + return ModuleIntrinsics.GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.Machine); } - return modulePath; } /// diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index a6481f709..f2ec5051a 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -211,6 +211,7 @@ namespace System.Management.Automation.Internal private static void CleanKeyParents(RegistryKey baseKey, string keyPath) { #if CORECLR + // if ( ! Platform.Inbox) // Modify this to support registry checks for inbox CoreCLR return; #else // Windows && FullCLR using (RegistryKey key = baseKey.OpenSubKey(keyPath, true))