From 92fad9393aa7aad33e7d131e1cef7d04e060fd87 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 26 Nov 2018 11:04:27 -0800 Subject: [PATCH 01/11] Avoid 'SecuritySupport.IsProductBinary' and unnecessary AMSI/suspicious code scan at startup time for a regular session --- .../commands/utility/Import-LocalizedData.cs | 1 + .../engine/InitialSessionState.cs | 16 ++----- .../engine/Utils.cs | 15 ------- .../engine/runtime/CompiledScriptBlock.cs | 44 ++++++++++++++----- .../security/SecuritySupport.cs | 11 ++++- 5 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs index cc3fa79cb19..f266c6fdc51 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs @@ -167,6 +167,7 @@ protected override void ProcessRecord() Context.LanguageMode = PSLanguageMode.RestrictedLanguage; try { + scriptBlock.ScriptBlockData.ForceMaliciousCodeScan = true; result = scriptBlock.InvokeReturnAsIs(); if (result == AutomationNull.Value) { diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index e18f794ab44..9d05a65085e 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -41,15 +41,13 @@ internal static void Init() // * have high disk cost // We shouldn't create too many tasks. - - // This task takes awhile, so it gets it's own task +#if !UNIX + // Amsi initialize can be a little slow Task.Run(() => { - // Building the catalog is expensive, so force that to happen early on a background thread, and do so - // on a file we are very likely to read anyway. - var pshome = Utils.DefaultPowerShellAppBase; - var unused = SecuritySupport.IsProductBinary(Path.Combine(pshome, "Modules", "Microsoft.PowerShell.Utility", "Microsoft.PowerShell.Utility.psm1")); + AmsiUtils.WinScanContent(content: string.Empty, sourceMetadata: string.Empty, warmUp: true); }); +#endif // One other task for other stuff that's faster, but still a little slow. Task.Run(() => @@ -58,12 +56,6 @@ internal static void Init() // happen early on a background thread. var unused0 = RunspaceInit.OutputEncodingDescription; - // Amsi initialize can also be a little slow - if (Platform.IsWindows) - { - AmsiUtils.Init(); - } - // This will init some tables and could load some assemblies. var unused1 = TypeAccelerators.builtinTypeAccelerators; diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 8f1c971f5aa..a62c63da984 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -258,7 +258,6 @@ private static string[] GetProductFolderDirectories() baseDirectories.Add(appBase); } #if !UNIX - // Win8: 454976 // Now add the two variations of System32 baseDirectories.Add(Environment.GetFolderPath(Environment.SpecialFolder.System)); string systemX86 = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86); @@ -267,20 +266,6 @@ private static string[] GetProductFolderDirectories() baseDirectories.Add(systemX86); } #endif - // And built-in modules - string progFileDir; - // TODO: #1184 will resolve this work-around - // Side-by-side versions of PowerShell use modules from their application base, not - // the system installation path. - progFileDir = Path.Combine(appBase, "Modules"); - - if (!string.IsNullOrEmpty(progFileDir)) - { - baseDirectories.Add(Path.Combine(progFileDir, "PackageManagement")); - baseDirectories.Add(Path.Combine(progFileDir, "PowerShellGet")); - baseDirectories.Add(Path.Combine(progFileDir, "Pester")); - baseDirectories.Add(Path.Combine(progFileDir, "PSReadLine")); - } Interlocked.CompareExchange(ref s_productFolderDirectories, baseDirectories.ToArray(), null); } diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 01398dcb0e6..866cffc5fef 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Management.Automation.Configuration; using System.Management.Automation.Internal; @@ -49,7 +50,10 @@ internal CompiledScriptBlockData(IParameterMetadataProvider ast, bool isFilter) internal CompiledScriptBlockData(string scriptText, bool isProductCode) { - this.IsProductCode = isProductCode; + if (isProductCode) + { + _isProductCode = true; + } _scriptText = scriptText; this.Id = Guid.NewGuid(); } @@ -163,11 +167,6 @@ private void ReallyCompile(bool optimize) var sw = new Stopwatch(); sw.Start(); #endif - if (!IsProductCode && SecuritySupport.IsProductBinary(((Ast)_ast).Extent.File)) - { - this.IsProductCode = true; - } - bool etwEnabled = ParserEventSource.Log.IsEnabled(); if (etwEnabled) { @@ -193,15 +192,28 @@ private void ReallyCompile(bool optimize) private void PerformSecurityChecks() { var scriptBlockAst = Ast as ScriptBlockAst; - if (scriptBlockAst == null) + if (scriptBlockAst == null || _isProductCode == true) { // Checks are only needed at the top level. + // Skip the check for built-in scripts in PowerShell engine, such as the built-in functions. return; } - // Call the AMSI API to determine if the script block has malicious content var scriptExtent = scriptBlockAst.Extent; - var amsiResult = AmsiUtils.ScanContent(scriptExtent.Text, scriptExtent.File); + var scriptFile = scriptExtent.File; + + if (scriptFile != null && + StringLiterals.PowerShellDataFileExtension.Equals( + Path.GetExtension(scriptFile), StringComparison.OrdinalIgnoreCase) + && !ForceMaliciousCodeScan) + { + // Skip the check for .psd1 files, unless it's being executed by 'Import-LocalizedData', + // in which case the 'ForceScan' property will be set to true. + return; + } + + // Call the AMSI API to determine if the script block has malicious content + var amsiResult = AmsiUtils.ScanContent(scriptExtent.Text, scriptFile); if (amsiResult == AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED) { @@ -269,12 +281,24 @@ private IParameterMetadataProvider DelayParseScriptText() private bool _compiledOptimized; private bool _compiledUnoptimized; private bool _hasSuspiciousContent; + private bool? _isProductCode; internal bool DebuggerHidden { get; set; } internal bool DebuggerStepThrough { get; set; } internal Guid Id { get; private set; } internal bool HasLogged { get; set; } internal bool IsFilter { get; private set; } - internal bool IsProductCode { get; private set; } + internal bool ForceMaliciousCodeScan { get; set; } + internal bool IsProductCode + { + get + { + if (_isProductCode == null) + { + _isProductCode = SecuritySupport.IsProductBinary(((Ast)_ast).Extent.File); + } + return _isProductCode.Value; + } + } internal bool GetIsConfiguration() { diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index 124307db9d9..dd89eb4ee65 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -1531,11 +1531,11 @@ internal static AmsiNativeMethods.AMSI_RESULT ScanContent(string content, string #if UNIX return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; #else - return WinScanContent(content, sourceMetadata); + return WinScanContent(content, sourceMetadata, warmUp: false); #endif } - internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, string sourceMetadata) + internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, string sourceMetadata, bool warmUp) { if (String.IsNullOrEmpty(sourceMetadata)) { @@ -1595,6 +1595,13 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str } } + if (warmUp) + { + // We are warming up the AMSI component in console startup, and that means we initialize AMSI + // and create a AMSI session, but don't really scan anything. + return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; + } + AmsiNativeMethods.AMSI_RESULT result = AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_CLEAN; // Run AMSI content scan From 86c2157f2a000da42b804e11721e95f0857d2d17 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 26 Nov 2018 11:22:15 -0800 Subject: [PATCH 02/11] Use customized 'ReadOnlyHashSet' instead of 'ImmutableHashSet' --- .../ExperimentalFeature.cs | 208 +++++++++++++++++- 1 file changed, 201 insertions(+), 7 deletions(-) diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 574186721d3..cf55db21142 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Collections; using System.Collections.Generic; -using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation.Configuration; @@ -73,7 +73,7 @@ internal ExperimentalFeature(string name, string description, string source, boo /// /// Experimental feature names that are enabled in the config file. /// - internal static readonly ImmutableHashSet EnabledExperimentalFeatureNames; + internal static readonly ReadOnlyHashSet EnabledExperimentalFeatureNames; /// /// Type initializer. Initialize the engine experimental feature list. @@ -99,7 +99,7 @@ static ExperimentalFeature() var engineExpFeatureMap = engineFeatures.ToDictionary(f => f.Name, StringComparer.OrdinalIgnoreCase); EngineExperimentalFeatureMap = new ReadOnlyDictionary(engineExpFeatureMap); - // Initialize the immutable hashset 'EnabledExperimentalFeatureNames'. + // Initialize the readonly hashset 'EnabledExperimentalFeatureNames'. // The initialization of 'EnabledExperimentalFeatureNames' is deliberately made in the type initializer so that: // 1. 'EnabledExperimentalFeatureNames' can be declared as readonly; // 2. No need to deal with initialization from multiple threads; @@ -119,11 +119,11 @@ static ExperimentalFeature() /// /// Process the array of enabled feature names retrieved from configuration. /// Ignore invalid feature names and unavailable engine feature names, and - /// return an ImmutableHashSet of the valid enabled feature names. + /// return an ReadOnlyHashSet of the valid enabled feature names. /// - private static ImmutableHashSet ProcessEnabledFeatures(string[] enabledFeatures) + private static ReadOnlyHashSet ProcessEnabledFeatures(string[] enabledFeatures) { - if (enabledFeatures.Length == 0) { return ImmutableHashSet.Empty; } + if (enabledFeatures.Length == 0) { return ReadOnlyHashSet.Empty; } var list = new List(enabledFeatures.Length); foreach (string name in enabledFeatures) @@ -151,7 +151,7 @@ private static ImmutableHashSet ProcessEnabledFeatures(string[] enabledF LogError(PSEventId.ExperimentalFeature_InvalidName, name, message); } } - return ImmutableHashSet.CreateRange(StringComparer.OrdinalIgnoreCase, list); + return new ReadOnlyHashSet(new HashSet(list, StringComparer.OrdinalIgnoreCase)); } /// @@ -339,4 +339,198 @@ private ExperimentAction EffectiveAction } private ExperimentAction _effectiveAction = ExperimentAction.None; } + + /// + /// A readonly Hashset + /// + public sealed class ReadOnlyHashSet : ISet + { + private HashSet _hashset; + + /// + /// Constructor for the readonly Hashset. + /// + internal ReadOnlyHashSet(HashSet hashset) + { + if (hashset == null) + { + throw new ArgumentNullException(nameof(hashset)); + } + _hashset = hashset; + } + + /// + /// Get an empty singleton. + /// + public static readonly ReadOnlyHashSet Empty = new ReadOnlyHashSet(new HashSet(capacity: 0)); + + /// + /// Get the count of the Hashset. + /// + public int Count => _hashset.Count; + + /// + /// Indicate if it's a readonly Hashset. + /// + public bool IsReadOnly => true; + + /// + /// Check if the set contains an item. + /// + public bool Contains(T item) + { + return _hashset.Contains(item); + } + + /// + /// Copy items to an array. + /// + public void CopyTo(T[] array, int arrayIndex) + { + _hashset.CopyTo(array, arrayIndex); + } + + /// + /// GetEnumerator method. + /// + public IEnumerator GetEnumerator() + { + return _hashset.GetEnumerator(); + } + + /// + /// IsProperSubsetOf method. + /// + public bool IsProperSubsetOf(IEnumerable other) + { + return _hashset.IsProperSubsetOf(other); + } + + /// + /// IsProperSupersetOf method. + /// + public bool IsProperSupersetOf(IEnumerable other) + { + return _hashset.IsProperSupersetOf(other); + } + + /// + /// IsSubsetOf method. + /// + public bool IsSubsetOf(IEnumerable other) + { + return _hashset.IsSubsetOf(other); + } + + /// + /// IsSupersetOf method. + /// + public bool IsSupersetOf(IEnumerable other) + { + return _hashset.IsSupersetOf(other); + } + + /// + /// Overlaps method. + /// + public bool Overlaps(IEnumerable other) + { + return _hashset.Overlaps(other); + } + + /// + /// SetEquals method. + /// + public bool SetEquals(IEnumerable other) + { + return _hashset.SetEquals(other); + } + + /// + /// Add method. + /// + /// + /// Not supported for a readonly Hashset. + /// + public bool Add(T item) + { + throw new NotSupportedException(); + } + + /// + /// Remove method. + /// + /// + /// Not supported for a readonly Hashset. + /// + public bool Remove(T item) + { + throw new NotSupportedException(); + } + + /// + /// Clear method. + /// + /// + /// Not supported for a readonly Hashset. + /// + public void Clear() + { + throw new NotSupportedException(); + } + + /// + /// ExceptWith method. + /// + /// + /// Not supported for a readonly Hashset. + /// + public void ExceptWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + /// + /// IntersectWith method. + /// + /// + /// Not supported for a readonly Hashset. + /// + public void IntersectWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + /// + /// SymmetricExceptWith method. + /// + /// + /// Not supported for a readonly Hashset. + /// + public void SymmetricExceptWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + /// + /// UnionWith method. + /// + /// + /// Not supported for a readonly Hashset. + /// + public void UnionWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + void ICollection.Add(T item) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _hashset.GetEnumerator(); + } + } } From 82baef003b37a623fafdb492db471b887a5f12c0 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 26 Nov 2018 11:24:05 -0800 Subject: [PATCH 03/11] Replace SHA1 with CRC32 when generating module analysis cache file name --- .../engine/Modules/AnalysisCache.cs | 59 ++++++++----------- .../utils/PsUtils.cs | 57 ++++++++++++++++++ 2 files changed, 82 insertions(+), 34 deletions(-) diff --git a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs index 483c33bb60f..35e5e6dc3dd 100644 --- a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs +++ b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs @@ -10,7 +10,6 @@ using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; -using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -1071,45 +1070,37 @@ static AnalysisCacheData() } string cacheFileName = "ModuleAnalysisCache"; + // When multiple copies of pwsh are on the system, they should use their own copy of the cache. // Append hash of `$PSHOME` to cacheFileName. - byte[] hashBytes; - using (var sha1 = SHA1.Create()) - { - hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(Utils.DefaultPowerShellAppBase)); - - string hashString = BitConverter.ToString(hashBytes, startIndex: 0, length: 4).Replace("-", string.Empty); - cacheFileName = string.Format(CultureInfo.InvariantCulture, "{0}-{1}", cacheFileName, hashString); + string hashString = CRC32Hash.ComputeHash(Utils.DefaultPowerShellAppBase); + cacheFileName = string.Format(CultureInfo.InvariantCulture, "{0}-{1}", cacheFileName, hashString); - if (ExperimentalFeature.EnabledExperimentalFeatureNames.Count > 0) + if (ExperimentalFeature.EnabledExperimentalFeatureNames.Count > 0) + { + // If any experimental features are enabled, we cannot use the default cache file because those + // features may expose commands that are not available in a regular powershell session, and we + // should not cache those commands in the default cache file because that will result in wrong + // auto-completion suggestions when the default cache file is used in another powershell session. + // + // Here we will generate a cache file name that represent the combination of enabled feature names. + // We first convert enabled feature names to lower case, then we sort the feature names, and then + // compute an CRC32 hash from the sorted feature names. We will use the CRC32 hash to generate the + // cache file name. + int index = 0; + string[] featureNames = new string[ExperimentalFeature.EnabledExperimentalFeatureNames.Count]; + foreach (string featureName in ExperimentalFeature.EnabledExperimentalFeatureNames) { - // If any experimental features are enabled, we cannot use the default cache file because those - // features may expose commands that are not available in a regular powershell session, and we - // should not cache those commands in the default cache file because that will result in wrong - // auto-completion suggestions when the default cache file is used in another powershell session. - // - // Here we will generate a cache file name that represent the combination of enabled feature names. - // We first convert enabled feature names to lower case, then we sort the feature names, and then - // compute an SHA1 hash from the sorted feature names. We will use a short SHA name (first 8 chars) - // to generate the cache file name. - int index = 0; - string[] featureNames = new string[ExperimentalFeature.EnabledExperimentalFeatureNames.Count]; - foreach (string featureName in ExperimentalFeature.EnabledExperimentalFeatureNames) - { - featureNames[index++] = featureName.ToLowerInvariant(); - } - - Array.Sort(featureNames); - string allNames = string.Join(Environment.NewLine, featureNames); + featureNames[index++] = featureName.ToLowerInvariant(); + } - // Use SHA1 because it's faster. - // It's very unlikely to get collision from hashing the combinations of enabled features names. - hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(allNames)); + Array.Sort(featureNames); + string allNames = string.Join(Environment.NewLine, featureNames); - // Use the first 8 characters of the hash string for a short SHA. - hashString = BitConverter.ToString(hashBytes, startIndex: 0, length: 4).Replace("-", string.Empty); - cacheFileName = string.Format(CultureInfo.InvariantCulture, "{0}-{1}", cacheFileName, hashString); - } + // Use CRC32 because it's faster. + // It's very unlikely to get collision from hashing the combinations of enabled features names. + hashString = CRC32Hash.ComputeHash(allNames); + cacheFileName = string.Format(CultureInfo.InvariantCulture, "{0}-{1}", cacheFileName, hashString); } #if UNIX diff --git a/src/System.Management.Automation/utils/PsUtils.cs b/src/System.Management.Automation/utils/PsUtils.cs index 7e2d3af8403..33951ef1ea3 100644 --- a/src/System.Management.Automation/utils/PsUtils.cs +++ b/src/System.Management.Automation/utils/PsUtils.cs @@ -624,6 +624,63 @@ internal static object[] Base64ToArgsConverter(string base64) } } + /// + /// A simple implementation of CRC32. + /// See "CRC-32 algorithm" in https://en.wikipedia.org/wiki/Cyclic_redundancy_check. + /// + internal class CRC32Hash + { + // CRC-32C polynomial representations + private const uint polynomial = 0x1EDC6F41; + private static uint[] table; + + static CRC32Hash() + { + uint temp = 0; + table = new uint[256]; + + for (int i = 0; i < table.Length; i++) + { + temp = (uint)i; + for (int j = 0; j < 8; j++) + { + if ((temp & 1) == 1) + { + temp = (temp >> 1) ^ polynomial; + } + else + { + temp >>= 1; + } + } + table[i] = temp; + } + } + + private static uint Compute(byte[] buffer) + { + uint crc = 0xFFFFFFFF; + for (int i = 0; i < buffer.Length; ++i) + { + var index = (byte)(crc ^ buffer[i] & 0xff); + crc = (crc >> 8) ^ table[index]; + } + return ~crc; + } + + internal static byte[] ComputeHash(byte[] buffer) + { + uint crcResult = Compute(buffer); + return BitConverter.GetBytes(crcResult); + } + + internal static string ComputeHash(string input) + { + byte[] hashBytes = ComputeHash(Encoding.UTF8.GetBytes(input)); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + } + #region ReferenceEqualityComparer /// From 518ab4fcaa245c4120ec550417bdf1287ba44062 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 26 Nov 2018 13:57:37 -0800 Subject: [PATCH 04/11] Move 'ConvertFrom-SddlString' to C# to avoid Utility.psm1 completely --- ...crosoft.PowerShell.Commands.Utility.csproj | 1 + .../utility/ConvertFrom-SddlString.cs | 261 ++++++++++++++++++ .../commands/utility/GetHash.cs | 4 +- .../utility/ImportPowerShellDataFile.cs | 4 +- .../commands/utility/UtilityCommon.cs | 29 -- .../resources/UtilityCommonStrings.resx | 7 +- .../Microsoft.PowerShell.SDK.csproj | 1 - .../Microsoft.PowerShell.Utility.psm1 | 176 ------------ .../Microsoft.PowerShell.Utility.psd1 | 58 ++-- .../Microsoft.PowerShell.Utility.psd1 | 56 ++-- .../security/Authenticode.cs | 2 +- .../ConvertFrom-SddlString.ps1 | 50 ++++ .../engine/Basic/DefaultCommands.Tests.ps1 | 1 + 13 files changed, 378 insertions(+), 272 deletions(-) create mode 100644 src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs delete mode 100644 src/Modules/Shared/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-SddlString.ps1 diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index 9bbce557c78..845cf89d4d9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -67,6 +67,7 @@ + diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs new file mode 100644 index 00000000000..4db979b1bcf --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs @@ -0,0 +1,261 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#if !UNIX + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Security.AccessControl; +using System.Security.Principal; +using System.Text; +using System.ComponentModel; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Converts a SDDL string into an object-based representation of a security descriptor. + /// + [Cmdlet(VerbsData.ConvertFrom, "SddlString", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=623636", RemotingCapability = RemotingCapability.None)] + [OutputType(typeof(SecurityDescriptorInfo))] + public sealed class ConvertFromSddlStringCommand : PSCmdlet + { + /// + /// Gets and sets the string representing the security descriptor in SDDL syntax. + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)] + public string Sddl { get; set; } + + /// + /// Gets and sets type of rights that this SDDL string represents. + /// + [Parameter] + public AccessRightTypeNames Type + { + get { return _type; } + set + { + _isTypeSet = true; + _type = value; + } + } + private AccessRightTypeNames _type; + private bool _isTypeSet = false; + + private string ConvertToNTAccount(SecurityIdentifier securityIdentifier) + { + try + { + return securityIdentifier?.Translate(typeof(NTAccount)).Value; + } + catch + { + return null; + } + } + + private List GetApplicableAccessRights(int accessMask, AccessRightTypeNames? typeName) + { + List typesToExamine = new List(); + List foundAccessRightNames = new List(); + HashSet foundAccessRightValues = new HashSet(); + + if (typeName != null) + { + typesToExamine.Add(GetRealAccessRightType(typeName.Value)); + } + else + { + foreach (AccessRightTypeNames member in Enum.GetValues(typeof(AccessRightTypeNames))) + { + typesToExamine.Add(GetRealAccessRightType(member)); + } + } + + foreach (Type accessRightType in typesToExamine) + { + foreach (string memberName in Enum.GetNames(accessRightType)) + { + int memberValue = (int) Enum.Parse(accessRightType, memberName); + if (!foundAccessRightValues.Contains(memberValue)) + { + foundAccessRightValues.Add(memberValue); + if ((accessMask & memberValue) == memberValue) + { + foundAccessRightNames.Add(memberName); + } + } + } + } + + foundAccessRightNames.Sort(StringComparer.OrdinalIgnoreCase); + return foundAccessRightNames; + } + + private Type GetRealAccessRightType(AccessRightTypeNames typeName) + { + switch (typeName) + { + case AccessRightTypeNames.FileSystemRights: + return typeof(FileSystemRights); + case AccessRightTypeNames.RegistryRights: + return typeof(RegistryRights); + case AccessRightTypeNames.ActiveDirectoryRights: + return typeof(System.DirectoryServices.ActiveDirectoryRights); + case AccessRightTypeNames.MutexRights: + return typeof(MutexRights); + case AccessRightTypeNames.SemaphoreRights: + return typeof(SemaphoreRights); + case AccessRightTypeNames.EventWaitHandleRights: + return typeof(EventWaitHandleRights); + default: + throw new InvalidOperationException(); + } + } + + private string[] ConvertAccessControlListToStrings(CommonAcl acl, AccessRightTypeNames? typeName) + { + if (acl == null || acl.Count == 0) + { + return Array.Empty(); + } + + List aceStringList = new List(acl.Count); + foreach (CommonAce ace in acl) + { + StringBuilder aceString = new StringBuilder(); + string ntAccount = ConvertToNTAccount(ace.SecurityIdentifier); + aceString.Append($"{ntAccount}: {ace.AceQualifier}"); + + if (ace.AceFlags != AceFlags.None) + { + aceString.Append($" {ace.AceFlags}"); + } + + List accessRightList = GetApplicableAccessRights(ace.AccessMask, typeName); + if (accessRightList.Count > 0) + { + string accessRights = String.Join(", ", accessRightList); + aceString.Append($" ({accessRights})"); + } + aceStringList.Add(aceString.ToString()); + } + + return aceStringList.ToArray(); + } + + /// + /// ProcessRecord method. + /// + protected override void ProcessRecord() + { + CommonSecurityDescriptor rawSecurityDescriptor = null; + try + { + rawSecurityDescriptor = new CommonSecurityDescriptor(isContainer: false, isDS: false, Sddl); + } + catch (Exception e) + { + var ioe = PSTraceSource.NewInvalidOperationException(e, UtilityCommonStrings.InvalidSDDL, e.Message); + ThrowTerminatingError(new ErrorRecord(ioe, "InvalidSDDL", ErrorCategory.InvalidArgument, Sddl)); + } + + string owner = ConvertToNTAccount(rawSecurityDescriptor.Owner); + string group = ConvertToNTAccount(rawSecurityDescriptor.Group); + + AccessRightTypeNames? typeToUse = _isTypeSet ? _type : (AccessRightTypeNames?) null; + string[] discretionaryAcl = ConvertAccessControlListToStrings(rawSecurityDescriptor.DiscretionaryAcl, typeToUse); + string[] systemAcl = ConvertAccessControlListToStrings(rawSecurityDescriptor.SystemAcl, typeToUse); + + var outObj = new SecurityDescriptorInfo(owner, group, discretionaryAcl, systemAcl, rawSecurityDescriptor); + WriteObject(outObj); + } + + /// + /// AccessRight type names. + /// + public enum AccessRightTypeNames + { + /// + /// FileSystemRights. + /// + FileSystemRights, + + /// + /// RegistryRights. + /// + RegistryRights, + + /// + /// ActiveDirectoryRights. + /// + ActiveDirectoryRights, + + /// + /// MutexRights. + /// + MutexRights, + + /// + /// SemaphoreRights. + /// + SemaphoreRights, + + // We have 'CryptoKeyRights' in the list for Windows PowerShell, but that type is not available in .NET Core. + // CryptoKeyRights, + + /// + /// EventWaitHandleRights. + /// + EventWaitHandleRights + } + } + + /// + /// Representation of a security descriptor. + /// + public class SecurityDescriptorInfo + { + internal SecurityDescriptorInfo( + string owner, + string group, + string[] discretionaryAcl, + string[] systemAcl, + CommonSecurityDescriptor rawDescriptor) + { + Owner = owner; + Group = group; + DiscretionaryAcl = discretionaryAcl; + SystemAcl = systemAcl; + RawDescriptor = rawDescriptor; + } + + /// + /// EventWaitHandle rights. + /// + public readonly string Owner; + + /// + /// EventWaitHandle rights. + /// + public readonly string Group; + + /// + /// EventWaitHandle rights. + /// + public readonly string[] DiscretionaryAcl; + + /// + /// EventWaitHandle rights. + /// + public readonly string[] SystemAcl; + + /// + /// EventWaitHandle rights. + /// + public readonly CommonSecurityDescriptor RawDescriptor; + } +} + +#endif diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs index 3b309abab2a..4355e5be3e7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs @@ -19,7 +19,7 @@ public class GetFileHashCommand : HashCmdletBase { /// /// Path parameter. - /// The paths of the files to calculate a hashs. + /// The paths of the files to calculate hash values. /// Resolved wildcards. /// /// @@ -266,7 +266,7 @@ protected void InitHasher(String Algorithm) catch { // Seems it will never throw! Remove? - Exception exc = new NotSupportedException(UtilityResources.AlgorithmTypeNotSupported); + Exception exc = new NotSupportedException(UtilityCommonStrings.AlgorithmTypeNotSupported); ThrowTerminatingError(new ErrorRecord(exc, "AlgorithmTypeNotSupported", ErrorCategory.NotImplemented, null)); } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs index 6cf92b28a5a..a1ac032c6ed 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs @@ -79,7 +79,7 @@ private void WritePathNotFoundError(string path) { var errorId = "PathNotFound"; var errorCategory = ErrorCategory.InvalidArgument; - var errorMessage = string.Format(UtilityResources.PathDoesNotExist, path); + var errorMessage = string.Format(UtilityCommonStrings.PathDoesNotExist, path); var exception = new ArgumentException(errorMessage); var errorRecord = new ErrorRecord(exception, errorId, errorCategory, path); WriteError(errorRecord); @@ -88,7 +88,7 @@ private void WritePathNotFoundError(string path) private void WriteInvalidDataFileError(string resolvedPath, string errorId) { var errorCategory = ErrorCategory.InvalidData; - var errorMessage = string.Format(UtilityResources.CouldNotParseAsPowerShellDataFile, resolvedPath); + var errorMessage = string.Format(UtilityCommonStrings.CouldNotParseAsPowerShellDataFile, resolvedPath); var exception = new InvalidOperationException(errorMessage); var errorRecord = new ErrorRecord(exception, errorId, errorCategory, resolvedPath); WriteError(errorRecord); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs index f44b616e562..08d244051ce 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs @@ -59,35 +59,6 @@ public enum TextEncodingType Ascii, } - /// - /// Utility class to contain resources for the Microsoft.PowerShell.Utility module. - /// - public static class UtilityResources - { - /// - /// - public static string PathDoesNotExist { get { return UtilityCommonStrings.PathDoesNotExist; } } - - /// - /// - public static string FileReadError { get { return UtilityCommonStrings.FileReadError; } } - - /// - /// The resource string used to indicate 'PATH:' in the formating header. - /// - public static string FormatHexPathPrefix { get { return UtilityCommonStrings.FormatHexPathPrefix; } } - - /// - /// Error message to indicate that requested algorithm is not supported on the target platform. - /// - public static string AlgorithmTypeNotSupported { get { return UtilityCommonStrings.AlgorithmTypeNotSupported; } } - - /// - /// The file '{0}' could not be parsed as a PowerShell Data File. - /// - public static string CouldNotParseAsPowerShellDataFile { get { return UtilityCommonStrings.CouldNotParseAsPowerShellDataFile; } } - } - /// /// ByteCollection is used as a wrapper class for the collection of bytes. /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx index f8e5623d8fc..daebf2d6cfd 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx @@ -150,9 +150,6 @@ The given path '{0}' is not supported. This command only supports the FileSystem Provider paths. - - Path: - The command cannot be run because the AsString parameter requires that you specify the AsHashtable parameter. @@ -171,7 +168,7 @@ The file '{0}' could not be parsed as a PowerShell Data File. - - '{0}' is not supported in this system. + + Cannot construct a security descriptor from the given SDDL due to the following error: {0} diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index e4152f09f68..057c0639d9f 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -21,7 +21,6 @@ - diff --git a/src/Modules/Shared/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 b/src/Modules/Shared/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 deleted file mode 100644 index c55839fd440..00000000000 --- a/src/Modules/Shared/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psm1 +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -## Converts a SDDL string into an object-based representation of a security -## descriptor -function ConvertFrom-SddlString -{ - [CmdletBinding(HelpUri = "https://go.microsoft.com/fwlink/?LinkId=623636")] - param( - ## The string representing the security descriptor in SDDL syntax - [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] - [String] $Sddl, - - ## The type of rights that this SDDL string represents, if any. - [Parameter()] - [ValidateSet( - "FileSystemRights", "RegistryRights", "ActiveDirectoryRights", - "MutexRights", "SemaphoreRights", "CryptoKeyRights", - "EventWaitHandleRights")] - $Type - ) - - Begin - { - # On CoreCLR CryptoKeyRights and ActiveDirectoryRights are not supported. - if ($PSEdition -eq "Core" -and ($Type -eq "CryptoKeyRights" -or $Type -eq "ActiveDirectoryRights")) - { - $errorId = "TypeNotSupported" - $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument - $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::TypeNotSupported -f $Type - $exception = [System.ArgumentException]::New($errorMessage) - $errorRecord = [System.Management.Automation.ErrorRecord]::New($exception, $errorId, $errorCategory, $null) - $PSCmdlet.ThrowTerminatingError($errorRecord) - } - - ## Translates a SID into a NT Account - function ConvertTo-NtAccount - { - param($Sid) - - if($Sid) - { - $securityIdentifier = [System.Security.Principal.SecurityIdentifier] $Sid - - try - { - $ntAccount = $securityIdentifier.Translate([System.Security.Principal.NTAccount]).ToString() - } - catch{} - - $ntAccount - } - } - - ## Gets the access rights that apply to an access mask, preferring right types - ## of 'Type' if specified. - function Get-AccessRights - { - param($AccessMask, $Type) - - if ($PSEdition -eq "Core") - { - ## All the types of access rights understood by .NET Core - $rightTypes = [Ordered] @{ - "FileSystemRights" = [System.Security.AccessControl.FileSystemRights] - "RegistryRights" = [System.Security.AccessControl.RegistryRights] - "MutexRights" = [System.Security.AccessControl.MutexRights] - "SemaphoreRights" = [System.Security.AccessControl.SemaphoreRights] - "EventWaitHandleRights" = [System.Security.AccessControl.EventWaitHandleRights] - } - } - else - { - ## All the types of access rights understood by .NET - $rightTypes = [Ordered] @{ - "FileSystemRights" = [System.Security.AccessControl.FileSystemRights] - "RegistryRights" = [System.Security.AccessControl.RegistryRights] - "ActiveDirectoryRights" = [System.DirectoryServices.ActiveDirectoryRights] - "MutexRights" = [System.Security.AccessControl.MutexRights] - "SemaphoreRights" = [System.Security.AccessControl.SemaphoreRights] - "CryptoKeyRights" = [System.Security.AccessControl.CryptoKeyRights] - "EventWaitHandleRights" = [System.Security.AccessControl.EventWaitHandleRights] - } - } - $typesToExamine = $rightTypes.Values - - ## If they know the access mask represents a certain type, prefer its names - ## (i.e.: CreateLink for the registry over CreateDirectories for the filesystem) - if($Type) - { - $typesToExamine = @($rightTypes[$Type]) + $typesToExamine - } - - - ## Stores the access types we've found that apply - $foundAccess = @() - - ## Store the access types we've already seen, so that we don't report access - ## flags that are essentially duplicate. Many of the access values in the different - ## enumerations have the same value but with different names. - $foundValues = @{} - - ## Go through the entries in the different right types, and see if they apply to the - ## provided access mask. If they do, then add that to the result. - foreach($rightType in $typesToExamine) - { - foreach($accessFlag in [Enum]::GetNames($rightType)) - { - $longKeyValue = [long] $rightType::$accessFlag - if(-not $foundValues.ContainsKey($longKeyValue)) - { - $foundValues[$longKeyValue] = $true - if(($AccessMask -band $longKeyValue) -eq ($longKeyValue)) - { - $foundAccess += $accessFlag - } - } - } - } - - $foundAccess | Sort-Object - } - - ## Converts an ACE into a string representation - function ConvertTo-AceString - { - param( - [Parameter(ValueFromPipeline)] - $Ace, - $Type - ) - - process - { - foreach($aceEntry in $Ace) - { - $AceString = (ConvertTo-NtAccount $aceEntry.SecurityIdentifier) + ": " + $aceEntry.AceQualifier - if($aceEntry.AceFlags -ne "None") - { - $AceString += " " + $aceEntry.AceFlags - } - - if($aceEntry.AccessMask) - { - $foundAccess = Get-AccessRights $aceEntry.AccessMask $Type - - if($foundAccess) - { - $AceString += " ({0})" -f ($foundAccess -join ", ") - } - } - - $AceString - } - } - } - } - - Process - { - $rawSecurityDescriptor = [Security.AccessControl.CommonSecurityDescriptor]::new($false,$false,$Sddl) - - $owner = ConvertTo-NtAccount $rawSecurityDescriptor.Owner - $group = ConvertTo-NtAccount $rawSecurityDescriptor.Group - $discretionaryAcl = ConvertTo-AceString $rawSecurityDescriptor.DiscretionaryAcl $Type - $systemAcl = ConvertTo-AceString $rawSecurityDescriptor.SystemAcl $Type - - [PSCustomObject] @{ - Owner = $owner - Group = $group - DiscretionaryAcl = @($discretionaryAcl) - SystemAcl = @($systemAcl) - RawDescriptor = $rawSecurityDescriptor - } - } -} diff --git a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index e901694f4f9..863b38f7498 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -1,32 +1,34 @@ @{ -GUID="1DA87E53-152B-403E-98DC-74D7B4D63D59" -Author="PowerShell" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="6.1.0.0" +GUID = "1DA87E53-152B-403E-98DC-74D7B4D63D59" +Author = "PowerShell" +CompanyName = "Microsoft Corporation" +Copyright = "Copyright (c) Microsoft Corporation. All rights reserved." +ModuleVersion = "6.1.0.0" CompatiblePSEditions = @("Core") -PowerShellVersion="3.0" -CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide", - "Out-File", "Out-String", "Get-FormatData", "Export-FormatData", "ConvertFrom-Json", "ConvertTo-Json", - "Invoke-RestMethod", "Invoke-WebRequest", "Register-ObjectEvent", "Register-EngineEvent", - "Wait-Event", "Get-Event", "Remove-Event", "Get-EventSubscriber", "Unregister-Event", "New-Guid", - "New-Event", "Add-Member", "Add-Type", "Compare-Object", "ConvertTo-Html", "ConvertFrom-StringData", - "Export-Csv", "Import-Csv", "ConvertTo-Csv", "ConvertFrom-Csv", "Export-Alias", "Invoke-Expression", - "Get-Alias", "Get-Culture", "Get-Date", "Get-Host", "Get-Member", "Get-Random", "Get-UICulture", - "Get-Unique", "Export-PSSession", "Import-PSSession", "Import-Alias", "Import-LocalizedData", - "Join-String", "Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date", - "Start-Sleep", "Tee-Object", "Measure-Command", "Update-TypeData", "Update-FormatData", - "Remove-TypeData", "Get-TypeData", "Write-Host", "Write-Progress", "New-Object", "Select-Object", - "Group-Object", "Sort-Object", "Get-Variable", "New-Variable", "Set-Variable", "Remove-Variable", - "Clear-Variable", "Export-Clixml", "Import-Clixml", "Import-PowerShellDataFile", "ConvertTo-Xml", "Select-Xml", "Write-Debug", - "Write-Verbose", "Write-Warning", "Write-Error", "Write-Information", "Write-Output", "Set-PSBreakpoint", - "Get-PSBreakpoint", "Remove-PSBreakpoint", "Enable-PSBreakpoint", "Disable-PSBreakpoint", "Get-PSCallStack", - "Send-MailMessage", "Get-TraceSource", "Set-TraceSource", "Trace-Command", "Get-FileHash", - "Get-Runspace", "Debug-Runspace", "Enable-RunspaceDebug", "Disable-RunspaceDebug", - "Get-RunspaceDebug", "Wait-Debugger" , "Get-Uptime", "New-TemporaryFile", "Get-Verb", "Format-Hex", - "Test-Json", "Remove-Alias", "ConvertFrom-Markdown", "Show-Markdown", "Set-MarkdownOption", "Get-MarkdownOption" -FunctionsToExport= "Import-PowerShellDataFile" -AliasesToExport= "fhx" -NestedModules="Microsoft.PowerShell.Commands.Utility.dll","Microsoft.PowerShell.Utility.psm1" +PowerShellVersion = "3.0" +CmdletsToExport = @( + 'Export-Alias', 'Get-Alias', 'Import-Alias', 'New-Alias', 'Remove-Alias', 'Set-Alias', 'Export-Clixml', + 'Import-Clixml', 'Measure-Command', 'Trace-Command', 'ConvertFrom-Csv', 'ConvertTo-Csv', 'Export-Csv', + 'Import-Csv', 'Get-Culture', 'Format-Custom', 'Get-Date', 'Set-Date', 'Write-Debug', 'Wait-Debugger', + 'Register-EngineEvent', 'Write-Error', 'Get-Event', 'New-Event', 'Remove-Event', 'Unregister-Event', + 'Wait-Event', 'Get-EventSubscriber', 'Invoke-Expression', 'Out-File', 'Get-FileHash', 'Export-FormatData', + 'Get-FormatData', 'Update-FormatData', 'New-Guid', 'Format-Hex', 'Get-Host', 'Read-Host', 'Write-Host', + 'ConvertTo-Html', 'Write-Information', 'ConvertFrom-Json', 'ConvertTo-Json', 'Test-Json', 'Format-List', + 'Import-LocalizedData', 'Send-MailMessage', 'ConvertFrom-Markdown', 'Show-Markdown', 'Get-MarkdownOption', + 'Set-MarkdownOption', 'Add-Member', 'Get-Member', 'Compare-Object', 'Group-Object', 'Measure-Object', + 'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output', + 'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint', + 'Get-PSBreakpoint', 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', + 'Import-PSSession', 'Get-Random', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', + 'Disable-RunspaceDebug', 'Enable-RunspaceDebug', 'Get-RunspaceDebug', 'Start-Sleep', 'Join-String', + 'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', + 'Get-TraceSource', 'Set-TraceSource', 'Add-Type', 'Get-TypeData', 'Remove-TypeData', 'Update-TypeData', + 'Get-UICulture', 'Get-Unique', 'Get-Uptime', 'Clear-Variable', 'Get-Variable', 'New-Variable', + 'Remove-Variable', 'Set-Variable', 'Get-Verb', 'Write-Verbose', 'Write-Warning', 'Invoke-WebRequest', + 'Format-Wide', 'ConvertTo-Xml', 'Select-Xml' +) +FunctionsToExport = @() +AliasesToExport = @('fhx') +NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855960' } diff --git a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index 316852c1728..2db43e68531 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -1,32 +1,32 @@ @{ -GUID="1DA87E53-152B-403E-98DC-74D7B4D63D59" -Author="PowerShell" -CompanyName="Microsoft Corporation" -Copyright="Copyright (c) Microsoft Corporation. All rights reserved." -ModuleVersion="6.1.0.0" +GUID = "1DA87E53-152B-403E-98DC-74D7B4D63D59" +Author = "PowerShell" +CompanyName = "Microsoft Corporation" +Copyright = "Copyright (c) Microsoft Corporation. All rights reserved." +ModuleVersion = "6.1.0.0" CompatiblePSEditions = @("Core") -PowerShellVersion="3.0" -CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide", - "Out-File", "Out-String", "Get-FormatData", "Export-FormatData", "ConvertFrom-Json", "ConvertTo-Json", - "Invoke-RestMethod", "Invoke-WebRequest", "Register-ObjectEvent", "Register-EngineEvent", - "Wait-Event", "Get-Event", "Remove-Event", "Get-EventSubscriber", "Unregister-Event", "New-Guid", - "New-Event", "Add-Member", "Add-Type", "Compare-Object", "ConvertTo-Html", "ConvertFrom-StringData", - "Export-Csv", "Import-Csv", "ConvertTo-Csv", "ConvertFrom-Csv", "Export-Alias", "Invoke-Expression", - "Get-Alias", "Get-Culture", "Get-Date", "Get-Host", "Get-Member", "Get-Random", "Get-UICulture", - "Get-Unique", "Export-PSSession", "Import-PSSession", "Import-Alias", "Import-LocalizedData", - "Join-String", "Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date", - "Start-Sleep", "Tee-Object", "Measure-Command", "Update-TypeData", "Update-FormatData", - "Remove-TypeData", "Get-TypeData", "Write-Host", "Write-Progress", "New-Object", "Select-Object", - "Group-Object", "Sort-Object", "Get-Variable", "New-Variable", "Set-Variable", "Remove-Variable", - "Clear-Variable", "Export-Clixml", "Import-Clixml", "Import-PowerShellDataFile","ConvertTo-Xml", "Select-Xml", "Write-Debug", - "Write-Verbose", "Write-Warning", "Write-Error", "Write-Information", "Write-Output", "Set-PSBreakpoint", - "Get-PSBreakpoint", "Remove-PSBreakpoint", "New-TemporaryFile", "Enable-PSBreakpoint", "Disable-PSBreakpoint", "Get-PSCallStack", - "Send-MailMessage", "Get-TraceSource", "Set-TraceSource", "Trace-Command", "Get-FileHash", - "Unblock-File", "Get-Runspace", "Debug-Runspace", "Enable-RunspaceDebug", "Disable-RunspaceDebug", - "Get-RunspaceDebug", "Wait-Debugger" , "Get-Uptime", "Get-Verb", "Format-Hex", - "Test-Json", "Remove-Alias", "ConvertFrom-Markdown", "Show-Markdown", "Set-MarkdownOption", "Get-MarkdownOption" -FunctionsToExport= "ConvertFrom-SddlString" -AliasesToExport= "fhx" -NestedModules="Microsoft.PowerShell.Commands.Utility.dll","Microsoft.PowerShell.Utility.psm1" +PowerShellVersion = "3.0" +CmdletsToExport = @( + 'Export-Alias', 'Get-Alias', 'Import-Alias', 'New-Alias', 'Remove-Alias', 'Set-Alias', 'Export-Clixml', 'Import-Clixml', + 'Measure-Command', 'Trace-Command', 'ConvertFrom-Csv', 'ConvertTo-Csv', 'Export-Csv', 'Import-Csv', 'Get-Culture', + 'Format-Custom', 'Get-Date', 'Set-Date', 'Write-Debug', 'Wait-Debugger', 'Register-EngineEvent', 'Write-Error', + 'Get-Event', 'New-Event', 'Remove-Event', 'Unregister-Event', 'Wait-Event', 'Get-EventSubscriber', 'Invoke-Expression', + 'Out-File', 'Unblock-File', 'Get-FileHash', 'Export-FormatData', 'Get-FormatData', 'Update-FormatData', 'New-Guid', + 'Format-Hex', 'Get-Host', 'Read-Host', 'Write-Host', 'ConvertTo-Html', 'Write-Information', 'ConvertFrom-Json', + 'ConvertTo-Json', 'Test-Json', 'Format-List', 'Import-LocalizedData', 'Send-MailMessage', 'ConvertFrom-Markdown', + 'Show-Markdown', 'Get-MarkdownOption', 'Set-MarkdownOption', 'Add-Member', 'Get-Member', 'Compare-Object', 'Group-Object', + 'Measure-Object', 'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output', + 'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint', 'Get-PSBreakpoint', + 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', + 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', 'Disable-RunspaceDebug', 'Enable-RunspaceDebug', + 'Get-RunspaceDebug', 'ConvertFrom-SddlString', 'Start-Sleep', 'Join-String', 'Out-String', 'Select-String', + 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', 'Get-TraceSource', 'Set-TraceSource', + 'Add-Type', 'Get-TypeData', 'Remove-TypeData', 'Update-TypeData', 'Get-UICulture', 'Get-Unique', 'Get-Uptime', + 'Clear-Variable', 'Get-Variable', 'New-Variable', 'Remove-Variable', 'Set-Variable', 'Get-Verb', 'Write-Verbose', + 'Write-Warning', 'Invoke-WebRequest', 'Format-Wide', 'ConvertTo-Xml', 'Select-Xml' +) +FunctionsToExport = @() +AliasesToExport = @('fhx') +NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=855960' } diff --git a/src/System.Management.Automation/security/Authenticode.cs b/src/System.Management.Automation/security/Authenticode.cs index 5bef9917017..d9850133bdf 100644 --- a/src/System.Management.Automation/security/Authenticode.cs +++ b/src/System.Management.Automation/security/Authenticode.cs @@ -366,7 +366,7 @@ private static Signature GetSignatureFromCatalog(string filename) if (!Signature.CatalogApiAvailable.HasValue) { - string productFile = Path.Combine(Utils.DefaultPowerShellAppBase, "Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psm1"); + string productFile = Path.Combine(Utils.DefaultPowerShellAppBase, "Modules\\PSDiagnostics\\PSDiagnostics.psm1"); if (signature.Status != SignatureStatus.Valid) { if (string.Equals(filename, productFile, StringComparison.OrdinalIgnoreCase)) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-SddlString.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-SddlString.ps1 new file mode 100644 index 00000000000..0940c4bcffb --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-SddlString.ps1 @@ -0,0 +1,50 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe "ConvertFrom-SddlString Tests" -Tags "CI", "RequireAdminOnWindows" { + + BeforeAll { + if (-not $IsWindows) { return } + $sddl = (Get-Item -Path WSMan:\localhost\Service\RootSDDL).Value + $testCases = @( + @{ Type = "_UNSPECIFIED_" } + @{ Type = "FileSystemRights" } + @{ Type = "RegistryRights" } + @{ Type = "ActiveDirectoryRights" } + @{ Type = "MutexRights" } + @{ Type = "SemaphoreRights" } + @{ Type = "EventWaitHandleRights" } + ) + $expectedProperties = @('Owner', 'Group', 'DiscretionaryAcl', 'SystemAcl', 'RawDescriptor') + } + + It "Validate ConvertFrom-SddlString with type " -Skip:(!$IsWindows) -TestCases $testCases { + param($Type) + + $arguments = @{ Sddl = $sddl; } + if ($Type -ne "_UNSPECIFIED_") { + $arguments.Add("Type", $Type) + } + + $result = ConvertFrom-SddlString @arguments + foreach ($property in $expectedProperties) + { + $result.$property | Should Not Be $null + } + } + + It "Validate that ConvertFrom-SddlString with type via ValueFromPipeline" -Skip:(!$IsWindows) -TestCases $testCases { + param($Type) + + $arguments = @{ } + if ($Type -ne "_UNSPECIFIED_") { + $arguments.Add("Type", $Type) + } + + $result = $sddl | ConvertFrom-SddlString @arguments + foreach ($property in $expectedProperties) + { + $result.$property | Should Not Be $null + } + } +} diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 index 5fd62c5f895..a35281c1497 100644 --- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 @@ -195,6 +195,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "ConvertFrom-SecureString", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "ConvertFrom-String", , $($FullCLR ) "Cmdlet", "ConvertFrom-StringData", , $($FullCLR -or $CoreWindows -or $CoreUnix) +"Cmdlet", "ConvertFrom-SddlString", , $( $CoreWindows ) "Cmdlet", "Convert-Path", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "Convert-String", , $($FullCLR ) "Cmdlet", "ConvertTo-Csv", , $($FullCLR -or $CoreWindows -or $CoreUnix) From 1770925758cae314768553d478f10e40d2f6572a Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 26 Nov 2018 16:09:03 -0800 Subject: [PATCH 05/11] Update the packaging/signing metadata due to removal of utility.psm1 --- assets/files.wxs | 4 ---- tools/releaseBuild/signing.xml | 1 - 2 files changed, 5 deletions(-) diff --git a/assets/files.wxs b/assets/files.wxs index dee0720a3d0..6359a9cc638 100644 --- a/assets/files.wxs +++ b/assets/files.wxs @@ -1641,9 +1641,6 @@ - - - @@ -2733,7 +2730,6 @@ - diff --git a/tools/releaseBuild/signing.xml b/tools/releaseBuild/signing.xml index b93c543b4f7..7fc788b7912 100644 --- a/tools/releaseBuild/signing.xml +++ b/tools/releaseBuild/signing.xml @@ -35,7 +35,6 @@ - From bdd0acba536c723221afe9d974f5ed93cc196833 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 26 Nov 2018 10:01:47 -0800 Subject: [PATCH 06/11] Crossgen 'Microsoft.ApplicationInsights' and enable tiered compilation --- build.psm1 | 1 + .../engine/InitialSessionState.cs | 3 -- .../engine/TypeTable.cs | 12 +++---- .../engine/hostifaces/LocalConnection.cs | 35 +++++++++---------- src/powershell-unix/powershell-unix.csproj | 1 + .../powershell-win-core.csproj | 1 + 6 files changed, 25 insertions(+), 28 deletions(-) diff --git a/build.psm1 b/build.psm1 index 866cc51de4a..2c2d05079f4 100644 --- a/build.psm1 +++ b/build.psm1 @@ -2197,6 +2197,7 @@ function Start-CrossGen { "System.IO.Pipes.dll" "System.Diagnostics.FileVersionInfo.dll" "System.Collections.Specialized.dll" + "Microsoft.ApplicationInsights.dll" ) # Common PowerShell libraries to crossgen diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 9d05a65085e..6c83d545b5a 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -2382,9 +2382,6 @@ internal Exception BindRunspace(Runspace initializedRunspace, PSTraceSource runs // Setting the module to null fixes that. initializedRunspace.ExecutionContext.EngineSessionState.Module = null; - // Set the SessionStateDrive here since we have all the provider information at this point - SetSessionStateDrive(initializedRunspace.ExecutionContext, true); - Exception moduleImportException = ProcessImportModule(initializedRunspace, ModuleSpecificationsToImport, string.Empty, publicCommands, unresolvedCmdsToExpose); if (moduleImportException != null) { diff --git a/src/System.Management.Automation/engine/TypeTable.cs b/src/System.Management.Automation/engine/TypeTable.cs index 1d626186283..0070b0e6892 100644 --- a/src/System.Management.Automation/engine/TypeTable.cs +++ b/src/System.Management.Automation/engine/TypeTable.cs @@ -3201,11 +3201,10 @@ private void ProcessTypeDataToAdd(ConcurrentBag errors, TypeData typeDat return; } + PSMemberInfoInternalCollection typeMembers = null; if (typeData.Members.Count > 0) { - PSMemberInfoInternalCollection typeMembers - = _extendedMembers.GetOrAdd(typeName, k => new PSMemberInfoInternalCollection()); - + typeMembers = _extendedMembers.GetOrAdd(typeName, k => new PSMemberInfoInternalCollection()); ProcessMembersData(errors, typeName, typeData.Members.Values, typeMembers, typeData.IsOverride); foreach (var memberName in typeData.Members.Keys) @@ -3216,9 +3215,10 @@ PSMemberInfoInternalCollection typeMembers if (typeData.StandardMembers.Count > 0 || propertySets.Count > 0) { - PSMemberInfoInternalCollection typeMembers - = _extendedMembers.GetOrAdd(typeName, k => new PSMemberInfoInternalCollection()); - + if (typeMembers == null) + { + typeMembers = _extendedMembers.GetOrAdd(typeName, k => new PSMemberInfoInternalCollection()); + } ProcessStandardMembers(errors, typeName, typeData.StandardMembers.Values, propertySets, typeMembers, typeData.IsOverride); } diff --git a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs index 3d93afc65e7..e42ec1a6399 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs @@ -678,30 +678,27 @@ private void DoOpenHelper() s_runspaceInitTracer.WriteLine("runspace opened successfully"); // Now do initial state configuration that requires an active runspace - if (InitialSessionState != null) + Exception initError = InitialSessionState.BindRunspace(this, s_runspaceInitTracer); + if (initError != null) { - Exception initError = InitialSessionState.BindRunspace(this, s_runspaceInitTracer); - if (initError != null) - { - // Log engine health event - LogEngineHealthEvent(initError); + // Log engine health event + LogEngineHealthEvent(initError); - // Log engine for end of engine life - Debug.Assert(_engine.Context != null, - "if startLifeCycleEventWritten is true, ExecutionContext must be present"); - MshLog.LogEngineLifecycleEvent(_engine.Context, EngineState.Stopped); + // Log engine for end of engine life + Debug.Assert(_engine.Context != null, + "if startLifeCycleEventWritten is true, ExecutionContext must be present"); + MshLog.LogEngineLifecycleEvent(_engine.Context, EngineState.Stopped); - // Open failed. Set the RunspaceState to Broken. - SetRunspaceState(RunspaceState.Broken, initError); + // Open failed. Set the RunspaceState to Broken. + SetRunspaceState(RunspaceState.Broken, initError); - // Raise the event - RaiseRunspaceStateEvents(); + // Raise the event + RaiseRunspaceStateEvents(); - // Throw the exception. For asynchronous execution, - // OpenThreadProc will catch it. For synchronous execution - // caller of open will catch it. - throw initError; - } + // Throw the exception. For asynchronous execution, + // OpenThreadProc will catch it. For synchronous execution + // caller of open will catch it. + throw initError; } #if LEGACYTELEMETRY diff --git a/src/powershell-unix/powershell-unix.csproj b/src/powershell-unix/powershell-unix.csproj index f9ccc3451d6..d2619f9990a 100644 --- a/src/powershell-unix/powershell-unix.csproj +++ b/src/powershell-unix/powershell-unix.csproj @@ -6,6 +6,7 @@ PowerShell top-level project with .NET CLI host pwsh Exe + true linux-x64;osx-x64; diff --git a/src/powershell-win-core/powershell-win-core.csproj b/src/powershell-win-core/powershell-win-core.csproj index 63486744e9a..93f3a9446f0 100644 --- a/src/powershell-win-core/powershell-win-core.csproj +++ b/src/powershell-win-core/powershell-win-core.csproj @@ -4,6 +4,7 @@ PowerShell Core on Windows top-level project pwsh Exe + true win7-x86;win7-x64 Microsoft.PowerShell From 91362829c2b99bbc90de7479a4d64aa28eefbb46 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 26 Nov 2018 10:06:48 -0800 Subject: [PATCH 07/11] [Feature] Update '.gitignore' to ignore .vscode folders from the sub directories --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index cb49aabbaec..af78e9fa5b3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ dotnet-uninstall-debian-packages.sh # Visual Studio IDE directory .vs/ +# VS Code directory +/**/.vscode/ + # Project Rider IDE files .idea.powershell/ From 35abe344bfa3f86f1b94c2f5e5bd317e2fcd3cdc Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 27 Nov 2018 18:45:26 -0800 Subject: [PATCH 08/11] [Feature] fix macOS build + 1st attempt to address review comments --- .gitignore | 2 +- .../utility/ConvertFrom-SddlString.cs | 2 +- .../commands/utility/UtilityCommon.cs | 26 +++ .../resources/UtilityCommonStrings.resx | 3 + src/ResGen/ResGen.csproj | 1 + .../ExperimentalFeature.cs | 169 +++--------------- .../engine/runtime/CompiledScriptBlock.cs | 9 +- src/TypeCatalogGen/TypeCatalogGen.csproj | 1 + test/powershell/Host/Logging.Tests.ps1 | 4 +- .../ConvertFrom-SddlString.ps1 | 4 +- test/tools/Modules/PSSysLog/PSSysLog.psm1 | 2 +- test/tools/TestExe/TestExe.csproj | 1 + test/tools/TestService/TestService.csproj | 1 + 13 files changed, 65 insertions(+), 160 deletions(-) diff --git a/.gitignore b/.gitignore index af78e9fa5b3..87ec2702b4d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ dotnet-uninstall-debian-packages.sh # Visual Studio IDE directory .vs/ -# VS Code directory +# VSCode directories that are not at the repository root /**/.vscode/ # Project Rider IDE files diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs index 4db979b1bcf..7cbfdfca5a7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs @@ -77,7 +77,7 @@ private List GetApplicableAccessRights(int accessMask, AccessRightTypeNa { foreach (string memberName in Enum.GetNames(accessRightType)) { - int memberValue = (int) Enum.Parse(accessRightType, memberName); + int memberValue = (int)Enum.Parse(accessRightType, memberName); if (!foundAccessRightValues.Contains(memberValue)) { foundAccessRightValues.Add(memberValue); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs index 08d244051ce..30188c345fd 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs @@ -59,6 +59,32 @@ public enum TextEncodingType Ascii, } + /// + /// Utility class to contain resources for the Microsoft.PowerShell.Utility module. + /// + [Obsolete("This class is obsolete", true)] + public static class UtilityResources + { + /// + /// + public static string PathDoesNotExist { get { return UtilityCommonStrings.PathDoesNotExist; } } + /// + /// + public static string FileReadError { get { return UtilityCommonStrings.FileReadError; } } + /// + /// The resource string used to indicate 'PATH:' in the formating header. + /// + public static string FormatHexPathPrefix { get { return UtilityCommonStrings.FormatHexPathPrefix; } } + /// + /// Error message to indicate that requested algorithm is not supported on the target platform. + /// + public static string AlgorithmTypeNotSupported { get { return UtilityCommonStrings.AlgorithmTypeNotSupported; } } + /// + /// The file '{0}' could not be parsed as a PowerShell Data File. + /// + public static string CouldNotParseAsPowerShellDataFile { get { return UtilityCommonStrings.CouldNotParseAsPowerShellDataFile; } } + } + /// /// ByteCollection is used as a wrapper class for the collection of bytes. /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx index daebf2d6cfd..551f0d1fcb8 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx @@ -150,6 +150,9 @@ The given path '{0}' is not supported. This command only supports the FileSystem Provider paths. + + Path: + The command cannot be run because the AsString parameter requires that you specify the AsHashtable parameter. diff --git a/src/ResGen/ResGen.csproj b/src/ResGen/ResGen.csproj index 2596f346664..1283eb4b85b 100644 --- a/src/ResGen/ResGen.csproj +++ b/src/ResGen/ResGen.csproj @@ -5,6 +5,7 @@ netcoreapp2.0 resgen Exe + true win7-x86;win7-x64;osx-x64;linux-x64 diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index cf55db21142..f54dc44e122 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -341,9 +341,9 @@ private ExperimentAction EffectiveAction } /// - /// A readonly Hashset + /// A readonly Hashset. /// - public sealed class ReadOnlyHashSet : ISet + internal sealed class ReadOnlyHashSet : ISet { private HashSet _hashset; @@ -362,7 +362,7 @@ internal ReadOnlyHashSet(HashSet hashset) /// /// Get an empty singleton. /// - public static readonly ReadOnlyHashSet Empty = new ReadOnlyHashSet(new HashSet(capacity: 0)); + internal static readonly ReadOnlyHashSet Empty = new ReadOnlyHashSet(new HashSet(capacity: 0)); /// /// Get the count of the Hashset. @@ -377,160 +377,37 @@ internal ReadOnlyHashSet(HashSet hashset) /// /// Check if the set contains an item. /// - public bool Contains(T item) - { - return _hashset.Contains(item); - } + public bool Contains(T item) => _hashset.Contains(item); /// /// Copy items to an array. /// - public void CopyTo(T[] array, int arrayIndex) - { - _hashset.CopyTo(array, arrayIndex); - } + public void CopyTo(T[] array, int arrayIndex) => _hashset.CopyTo(array, arrayIndex); /// /// GetEnumerator method. /// - public IEnumerator GetEnumerator() - { - return _hashset.GetEnumerator(); - } - - /// - /// IsProperSubsetOf method. - /// - public bool IsProperSubsetOf(IEnumerable other) - { - return _hashset.IsProperSubsetOf(other); - } - - /// - /// IsProperSupersetOf method. - /// - public bool IsProperSupersetOf(IEnumerable other) - { - return _hashset.IsProperSupersetOf(other); - } - - /// - /// IsSubsetOf method. - /// - public bool IsSubsetOf(IEnumerable other) - { - return _hashset.IsSubsetOf(other); - } - - /// - /// IsSupersetOf method. - /// - public bool IsSupersetOf(IEnumerable other) - { - return _hashset.IsSupersetOf(other); - } - - /// - /// Overlaps method. - /// - public bool Overlaps(IEnumerable other) - { - return _hashset.Overlaps(other); - } - - /// - /// SetEquals method. - /// - public bool SetEquals(IEnumerable other) - { - return _hashset.SetEquals(other); - } - - /// - /// Add method. - /// - /// - /// Not supported for a readonly Hashset. - /// - public bool Add(T item) - { - throw new NotSupportedException(); - } + public IEnumerator GetEnumerator() => _hashset.GetEnumerator(); - /// - /// Remove method. - /// - /// - /// Not supported for a readonly Hashset. - /// - public bool Remove(T item) - { - throw new NotSupportedException(); - } + #region NonPublic Interface Member Implementation - /// - /// Clear method. - /// - /// - /// Not supported for a readonly Hashset. - /// - public void Clear() - { - throw new NotSupportedException(); - } + bool ISet.IsProperSubsetOf(IEnumerable other) => _hashset.IsProperSubsetOf(other); + bool ISet.IsProperSupersetOf(IEnumerable other) => _hashset.IsProperSupersetOf(other); + bool ISet.IsSubsetOf(IEnumerable other) => _hashset.IsSubsetOf(other); + bool ISet.IsSupersetOf(IEnumerable other) => _hashset.IsSupersetOf(other); + bool ISet.Overlaps(IEnumerable other) => _hashset.Overlaps(other); + bool ISet.SetEquals(IEnumerable other) => _hashset.SetEquals(other); + IEnumerator IEnumerable.GetEnumerator() => _hashset.GetEnumerator(); - /// - /// ExceptWith method. - /// - /// - /// Not supported for a readonly Hashset. - /// - public void ExceptWith(IEnumerable other) - { - throw new NotSupportedException(); - } + bool ISet.Add(T item) => throw new NotSupportedException(); + void ISet.ExceptWith(IEnumerable other) => throw new NotSupportedException(); + void ISet.IntersectWith(IEnumerable other) => throw new NotSupportedException(); + void ISet.SymmetricExceptWith(IEnumerable other) => throw new NotSupportedException(); + void ISet.UnionWith(IEnumerable other) => throw new NotSupportedException(); + void ICollection.Add(T item) => throw new NotSupportedException(); + void ICollection.Clear() => throw new NotSupportedException(); + bool ICollection.Remove(T item) => throw new NotSupportedException(); - /// - /// IntersectWith method. - /// - /// - /// Not supported for a readonly Hashset. - /// - public void IntersectWith(IEnumerable other) - { - throw new NotSupportedException(); - } - - /// - /// SymmetricExceptWith method. - /// - /// - /// Not supported for a readonly Hashset. - /// - public void SymmetricExceptWith(IEnumerable other) - { - throw new NotSupportedException(); - } - - /// - /// UnionWith method. - /// - /// - /// Not supported for a readonly Hashset. - /// - public void UnionWith(IEnumerable other) - { - throw new NotSupportedException(); - } - - void ICollection.Add(T item) - { - throw new NotSupportedException(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _hashset.GetEnumerator(); - } + #endregion } } diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 866cffc5fef..22744428164 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Management.Automation.Configuration; using System.Management.Automation.Internal; @@ -50,10 +49,7 @@ internal CompiledScriptBlockData(IParameterMetadataProvider ast, bool isFilter) internal CompiledScriptBlockData(string scriptText, bool isProductCode) { - if (isProductCode) - { - _isProductCode = true; - } + _isProductCode = isProductCode; _scriptText = scriptText; this.Id = Guid.NewGuid(); } @@ -203,8 +199,7 @@ private void PerformSecurityChecks() var scriptFile = scriptExtent.File; if (scriptFile != null && - StringLiterals.PowerShellDataFileExtension.Equals( - Path.GetExtension(scriptFile), StringComparison.OrdinalIgnoreCase) + scriptFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase) && !ForceMaliciousCodeScan) { // Skip the check for .psd1 files, unless it's being executed by 'Import-LocalizedData', diff --git a/src/TypeCatalogGen/TypeCatalogGen.csproj b/src/TypeCatalogGen/TypeCatalogGen.csproj index d5aaa7462a9..9102abcb768 100644 --- a/src/TypeCatalogGen/TypeCatalogGen.csproj +++ b/src/TypeCatalogGen/TypeCatalogGen.csproj @@ -5,6 +5,7 @@ netcoreapp2.0 TypeCatalogGen Exe + true win7-x86;win7-x64;osx-x64;linux-x64 diff --git a/test/powershell/Host/Logging.Tests.ps1 b/test/powershell/Host/Logging.Tests.ps1 index 42f637e4fa1..32900e44b18 100644 --- a/test/powershell/Host/Logging.Tests.ps1 +++ b/test/powershell/Host/Logging.Tests.ps1 @@ -347,7 +347,7 @@ $pid $script | Out-File -FilePath $testScriptPath -Force $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath - Export-PSOsLog -After $after -LogPid $testPid -TimeoutInMilliseconds 30000 -IntervalInMilliseconds 3000 -MinimumCount 18 | + Export-PSOsLog -After $after -LogPid $testPid -TimeoutInMilliseconds 30000 -IntervalInMilliseconds 3000 -MinimumCount 17 | Set-Content -Path $contentFile $items = @(Get-PSOsLog -Path $contentFile -Id $logId -After $after -Verbose) @@ -385,7 +385,7 @@ $pid $script | Out-File -FilePath $testScriptPath -Force $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath - Export-PSOsLog -After $after -LogPid $testPid -TimeoutInMilliseconds 30000 -IntervalInMilliseconds 3000 -MinimumCount 18 | + Export-PSOsLog -After $after -LogPid $testPid -TimeoutInMilliseconds 30000 -IntervalInMilliseconds 3000 -MinimumCount 17 | Set-Content -Path $contentFile $items = @(Get-PSOsLog -Path $contentFile -Id $logId -After $after -Verbose) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-SddlString.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-SddlString.ps1 index 0940c4bcffb..c7e302ad29f 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-SddlString.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-SddlString.ps1 @@ -29,7 +29,7 @@ Describe "ConvertFrom-SddlString Tests" -Tags "CI", "RequireAdminOnWindows" { $result = ConvertFrom-SddlString @arguments foreach ($property in $expectedProperties) { - $result.$property | Should Not Be $null + $result.$property | Should -Not -Be $null } } @@ -44,7 +44,7 @@ Describe "ConvertFrom-SddlString Tests" -Tags "CI", "RequireAdminOnWindows" { $result = $sddl | ConvertFrom-SddlString @arguments foreach ($property in $expectedProperties) { - $result.$property | Should Not Be $null + $result.$property | Should -Not -Be $null } } } diff --git a/test/tools/Modules/PSSysLog/PSSysLog.psm1 b/test/tools/Modules/PSSysLog/PSSysLog.psm1 index 8c0c5779a98..1b62e4d2c56 100644 --- a/test/tools/Modules/PSSysLog/PSSysLog.psm1 +++ b/test/tools/Modules/PSSysLog/PSSysLog.psm1 @@ -901,7 +901,7 @@ function Export-PSOsLog Write-Output $log } else { - throw "did not recieve at least $MinimumCount records but $($log.Count) instead." + throw "did not recieve at least $MinimumCount records but $($logToCount.Count) instead." } } -TimeoutInMilliseconds $TimeoutInMilliseconds -IntervalInMilliseconds $IntervalInMilliseconds -LogErrorSb { $log = Start-NativeExecution -command {log show --info @extraParams} diff --git a/test/tools/TestExe/TestExe.csproj b/test/tools/TestExe/TestExe.csproj index 66a1238b489..d1e58bb8534 100644 --- a/test/tools/TestExe/TestExe.csproj +++ b/test/tools/TestExe/TestExe.csproj @@ -6,6 +6,7 @@ Very simple little console class that you can use to for testing PowerShell interaction with native commands testexe Exe + true win7-x86;win7-x64;osx-x64;linux-x64 diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj index 2544875784f..f414855d473 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -6,6 +6,7 @@ Very tiny windows service to do service testing TestService Exe + true win7-x86;win7-x64 From 9f186af3a23e2bf316aa7c71040716dcb38cb493 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 28 Nov 2018 18:18:48 -0800 Subject: [PATCH 09/11] [Feature] 2nd attempt to address review comments Major change is about how we decide to skip AMSI/malicious code scan. A script block may comes from a .psd1 file but still has arbitrary code in it. An attacker can parse an .psd1 file to get an Ast and call Ast.GetScriptBlock() to create a script block. That script block comes from a .psd1 file that may contain arbitrary content. The skip-scan condition is changed to: - the script block should come from a .psd1 file - the script block is essentially a HashtableAst - the HashtableAst is safe. --- .../utility/ConvertFrom-SddlString.cs | 2 +- .../commands/utility/Import-LocalizedData.cs | 1 - .../ExperimentalFeature.cs | 71 ------------------- .../engine/Utils.cs | 47 ++++++++++++ .../engine/runtime/CompiledScriptBlock.cs | 43 +++++++++-- 5 files changed, 85 insertions(+), 79 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs index 7cbfdfca5a7..6962f8beb38 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs @@ -215,7 +215,7 @@ public enum AccessRightTypeNames /// /// Representation of a security descriptor. /// - public class SecurityDescriptorInfo + public sealed class SecurityDescriptorInfo { internal SecurityDescriptorInfo( string owner, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs index f266c6fdc51..cc3fa79cb19 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs @@ -167,7 +167,6 @@ protected override void ProcessRecord() Context.LanguageMode = PSLanguageMode.RestrictedLanguage; try { - scriptBlock.ScriptBlockData.ForceMaliciousCodeScan = true; result = scriptBlock.InvokeReturnAsIs(); if (result == AutomationNull.Value) { diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index f54dc44e122..98d6c7f00e8 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -339,75 +339,4 @@ private ExperimentAction EffectiveAction } private ExperimentAction _effectiveAction = ExperimentAction.None; } - - /// - /// A readonly Hashset. - /// - internal sealed class ReadOnlyHashSet : ISet - { - private HashSet _hashset; - - /// - /// Constructor for the readonly Hashset. - /// - internal ReadOnlyHashSet(HashSet hashset) - { - if (hashset == null) - { - throw new ArgumentNullException(nameof(hashset)); - } - _hashset = hashset; - } - - /// - /// Get an empty singleton. - /// - internal static readonly ReadOnlyHashSet Empty = new ReadOnlyHashSet(new HashSet(capacity: 0)); - - /// - /// Get the count of the Hashset. - /// - public int Count => _hashset.Count; - - /// - /// Indicate if it's a readonly Hashset. - /// - public bool IsReadOnly => true; - - /// - /// Check if the set contains an item. - /// - public bool Contains(T item) => _hashset.Contains(item); - - /// - /// Copy items to an array. - /// - public void CopyTo(T[] array, int arrayIndex) => _hashset.CopyTo(array, arrayIndex); - - /// - /// GetEnumerator method. - /// - public IEnumerator GetEnumerator() => _hashset.GetEnumerator(); - - #region NonPublic Interface Member Implementation - - bool ISet.IsProperSubsetOf(IEnumerable other) => _hashset.IsProperSubsetOf(other); - bool ISet.IsProperSupersetOf(IEnumerable other) => _hashset.IsProperSupersetOf(other); - bool ISet.IsSubsetOf(IEnumerable other) => _hashset.IsSubsetOf(other); - bool ISet.IsSupersetOf(IEnumerable other) => _hashset.IsSupersetOf(other); - bool ISet.Overlaps(IEnumerable other) => _hashset.Overlaps(other); - bool ISet.SetEquals(IEnumerable other) => _hashset.SetEquals(other); - IEnumerator IEnumerable.GetEnumerator() => _hashset.GetEnumerator(); - - bool ISet.Add(T item) => throw new NotSupportedException(); - void ISet.ExceptWith(IEnumerable other) => throw new NotSupportedException(); - void ISet.IntersectWith(IEnumerable other) => throw new NotSupportedException(); - void ISet.SymmetricExceptWith(IEnumerable other) => throw new NotSupportedException(); - void ISet.UnionWith(IEnumerable other) => throw new NotSupportedException(); - void ICollection.Add(T item) => throw new NotSupportedException(); - void ICollection.Clear() => throw new NotSupportedException(); - bool ICollection.Remove(T item) => throw new NotSupportedException(); - - #endregion - } } diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index a62c63da984..26017d61a9b 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -15,6 +15,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Collections; using System.Collections.ObjectModel; using System.Collections.Generic; using System.Collections.Concurrent; @@ -1912,4 +1913,50 @@ internal T Pop() return item; } } + + /// + /// A readonly Hashset. + /// + internal sealed class ReadOnlyHashSet : IEnumerable + { + private HashSet _hashset; + + /// + /// Constructor for the readonly Hashset. + /// + internal ReadOnlyHashSet(HashSet hashset) + { + if (hashset == null) + { + throw new ArgumentNullException(nameof(hashset)); + } + + _hashset = hashset; + } + + /// + /// Get the count of the Hashset. + /// + public int Count => _hashset.Count; + + /// + /// Indicate if it's a readonly Hashset. + /// + public bool IsReadOnly => true; + + /// + /// Check if the set contains an item. + /// + public bool Contains(T item) => _hashset.Contains(item); + + /// + /// GetEnumerator method. + /// + public IEnumerator GetEnumerator() => _hashset.GetEnumerator(); + + /// + /// Get an empty singleton. + /// + internal static readonly ReadOnlyHashSet Empty = new ReadOnlyHashSet(new HashSet(capacity: 0)); + } } diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 22744428164..3f556eafdcc 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -188,10 +188,9 @@ private void ReallyCompile(bool optimize) private void PerformSecurityChecks() { var scriptBlockAst = Ast as ScriptBlockAst; - if (scriptBlockAst == null || _isProductCode == true) + if (scriptBlockAst == null) { // Checks are only needed at the top level. - // Skip the check for built-in scripts in PowerShell engine, such as the built-in functions. return; } @@ -200,10 +199,9 @@ private void PerformSecurityChecks() if (scriptFile != null && scriptFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase) - && !ForceMaliciousCodeScan) + && CanSkipCodeScan()) { - // Skip the check for .psd1 files, unless it's being executed by 'Import-LocalizedData', - // in which case the 'ForceScan' property will be set to true. + // Skip the scan for .psd1 files if their content is essentially a safe HashtableAst. return; } @@ -228,6 +226,40 @@ private void PerformSecurityChecks() { HasSuspiciousContent = true; } + + // A local function to check if the ScriptBlockAst is essentially a safe HashtableAst. + bool CanSkipCodeScan() + { + if (scriptBlockAst.BeginBlock != null || scriptBlockAst.ProcessBlock != null || + scriptBlockAst.ParamBlock != null || scriptBlockAst.DynamicParamBlock != null || + scriptBlockAst.ScriptRequirements != null || scriptBlockAst.UsingStatements.Count > 0 || + scriptBlockAst.Attributes.Count > 0) + { + return false; + } + + NamedBlockAst endBlock = scriptBlockAst.EndBlock; + if (!endBlock.Unnamed || endBlock.Traps != null || endBlock.Statements.Count != 1) + { + return false; + } + + PipelineAst pipelineAst = endBlock.Statements[0] as PipelineAst; + if (pipelineAst == null) + { + return false; + } + + HashtableAst hashtableAst = pipelineAst.GetPureExpression() as HashtableAst; + if (hashtableAst == null) + { + return false; + } + + // After the above steps, we know the ScriptBlockAst is essentially just a HashtableAst, + // now we need to check if the HashtableAst is safe. + return IsSafeValueVisitor.IsAstSafe(hashtableAst, GetSafeValueVisitor.SafeValueContext.Default); + } } // We delay parsing scripts loaded on startup, so we save the text. @@ -282,7 +314,6 @@ private IParameterMetadataProvider DelayParseScriptText() internal Guid Id { get; private set; } internal bool HasLogged { get; set; } internal bool IsFilter { get; private set; } - internal bool ForceMaliciousCodeScan { get; set; } internal bool IsProductCode { get From 2a455d8b067bf06ac1e424e2a8443fb728a2ee96 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 29 Nov 2018 18:08:40 -0800 Subject: [PATCH 10/11] Update comments based on Paul's feedback --- .../commands/utility/UtilityCommon.cs | 12 ++++++++---- .../engine/runtime/CompiledScriptBlock.cs | 6 +++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs index 30188c345fd..f8c1bd2b3d9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs @@ -68,18 +68,22 @@ public static class UtilityResources /// /// public static string PathDoesNotExist { get { return UtilityCommonStrings.PathDoesNotExist; } } - /// + + /// /// public static string FileReadError { get { return UtilityCommonStrings.FileReadError; } } - /// + + /// /// The resource string used to indicate 'PATH:' in the formating header. /// public static string FormatHexPathPrefix { get { return UtilityCommonStrings.FormatHexPathPrefix; } } - /// + + /// /// Error message to indicate that requested algorithm is not supported on the target platform. /// public static string AlgorithmTypeNotSupported { get { return UtilityCommonStrings.AlgorithmTypeNotSupported; } } - /// + + /// /// The file '{0}' could not be parsed as a PowerShell Data File. /// public static string CouldNotParseAsPowerShellDataFile { get { return UtilityCommonStrings.CouldNotParseAsPowerShellDataFile; } } diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 3f556eafdcc..6e28e01b4bc 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -201,7 +201,7 @@ private void PerformSecurityChecks() scriptFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase) && CanSkipCodeScan()) { - // Skip the scan for .psd1 files if their content is essentially a safe HashtableAst. + // Skip the scan for .psd1 files if their content is in fact a safe HashtableAst. return; } @@ -227,7 +227,7 @@ private void PerformSecurityChecks() HasSuspiciousContent = true; } - // A local function to check if the ScriptBlockAst is essentially a safe HashtableAst. + // We can skip code scan if the ScriptBlockAst is from a .psd1 file and it's in fact a safe HashtableAst. bool CanSkipCodeScan() { if (scriptBlockAst.BeginBlock != null || scriptBlockAst.ProcessBlock != null || @@ -256,7 +256,7 @@ bool CanSkipCodeScan() return false; } - // After the above steps, we know the ScriptBlockAst is essentially just a HashtableAst, + // After the above steps, we know the ScriptBlockAst is in fact just a HashtableAst, // now we need to check if the HashtableAst is safe. return IsSafeValueVisitor.IsAstSafe(hashtableAst, GetSafeValueVisitor.SafeValueContext.Default); } From 51733c44377983c7800188f16c37ba6efd6c804e Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Fri, 30 Nov 2018 11:02:21 -0800 Subject: [PATCH 11/11] [Feature] Address additional comments from Jason --- .../engine/ExperimentalFeature/ExperimentalFeature.cs | 10 +++++----- src/System.Management.Automation/engine/Utils.cs | 6 +++--- .../engine/runtime/CompiledScriptBlock.cs | 11 ++++++++--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 98d6c7f00e8..86d25b94026 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -73,7 +73,7 @@ internal ExperimentalFeature(string name, string description, string source, boo /// /// Experimental feature names that are enabled in the config file. /// - internal static readonly ReadOnlyHashSet EnabledExperimentalFeatureNames; + internal static readonly ReadOnlyBag EnabledExperimentalFeatureNames; /// /// Type initializer. Initialize the engine experimental feature list. @@ -119,11 +119,11 @@ static ExperimentalFeature() /// /// Process the array of enabled feature names retrieved from configuration. /// Ignore invalid feature names and unavailable engine feature names, and - /// return an ReadOnlyHashSet of the valid enabled feature names. + /// return an ReadOnlyBag of the valid enabled feature names. /// - private static ReadOnlyHashSet ProcessEnabledFeatures(string[] enabledFeatures) + private static ReadOnlyBag ProcessEnabledFeatures(string[] enabledFeatures) { - if (enabledFeatures.Length == 0) { return ReadOnlyHashSet.Empty; } + if (enabledFeatures.Length == 0) { return ReadOnlyBag.Empty; } var list = new List(enabledFeatures.Length); foreach (string name in enabledFeatures) @@ -151,7 +151,7 @@ private static ReadOnlyHashSet ProcessEnabledFeatures(string[] enabledFe LogError(PSEventId.ExperimentalFeature_InvalidName, name, message); } } - return new ReadOnlyHashSet(new HashSet(list, StringComparer.OrdinalIgnoreCase)); + return new ReadOnlyBag(new HashSet(list, StringComparer.OrdinalIgnoreCase)); } /// diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 26017d61a9b..c17782be1a0 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -1917,14 +1917,14 @@ internal T Pop() /// /// A readonly Hashset. /// - internal sealed class ReadOnlyHashSet : IEnumerable + internal sealed class ReadOnlyBag : IEnumerable { private HashSet _hashset; /// /// Constructor for the readonly Hashset. /// - internal ReadOnlyHashSet(HashSet hashset) + internal ReadOnlyBag(HashSet hashset) { if (hashset == null) { @@ -1957,6 +1957,6 @@ internal ReadOnlyHashSet(HashSet hashset) /// /// Get an empty singleton. /// - internal static readonly ReadOnlyHashSet Empty = new ReadOnlyHashSet(new HashSet(capacity: 0)); + internal static readonly ReadOnlyBag Empty = new ReadOnlyBag(new HashSet(capacity: 0)); } } diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 6e28e01b4bc..113ae5457ff 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -199,7 +199,7 @@ private void PerformSecurityChecks() if (scriptFile != null && scriptFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase) - && CanSkipCodeScan()) + && IsScriptBlockInFactASafeHashtable()) { // Skip the scan for .psd1 files if their content is in fact a safe HashtableAst. return; @@ -227,9 +227,14 @@ private void PerformSecurityChecks() HasSuspiciousContent = true; } - // We can skip code scan if the ScriptBlockAst is from a .psd1 file and it's in fact a safe HashtableAst. - bool CanSkipCodeScan() + // A local function to check if the ScriptBlockAst is in fact a safe HashtableAst. + bool IsScriptBlockInFactASafeHashtable() { + // NOTE: The code below depends on the current member structure of 'ScriptBlockAst' + // to determine if the ScriptBlockAst is in fact just a HashtableAst. If AST types + // are enhanced, such as new members added to 'ScriptBlockAst', the code here needs + // to be reviewed and changed accordingly. + if (scriptBlockAst.BeginBlock != null || scriptBlockAst.ProcessBlock != null || scriptBlockAst.ParamBlock != null || scriptBlockAst.DynamicParamBlock != null || scriptBlockAst.ScriptRequirements != null || scriptBlockAst.UsingStatements.Count > 0 ||