diff --git a/.gitignore b/.gitignore index cb49aabbaec..87ec2702b4d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ dotnet-uninstall-debian-packages.sh # Visual Studio IDE directory .vs/ +# VSCode directories that are not at the repository root +/**/.vscode/ + # Project Rider IDE files .idea.powershell/ 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/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/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..6962f8beb38 --- /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 sealed 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..f8c1bd2b3d9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs @@ -62,6 +62,7 @@ public enum TextEncodingType /// /// Utility class to contain resources for the Microsoft.PowerShell.Utility module. /// + [Obsolete("This class is obsolete", true)] public static class UtilityResources { /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx index f8e5623d8fc..551f0d1fcb8 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx @@ -171,7 +171,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/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 574186721d3..86d25b94026 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 ReadOnlyBag 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 ReadOnlyBag of the valid enabled feature names. /// - private static ImmutableHashSet ProcessEnabledFeatures(string[] enabledFeatures) + private static ReadOnlyBag ProcessEnabledFeatures(string[] enabledFeatures) { - if (enabledFeatures.Length == 0) { return ImmutableHashSet.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 ImmutableHashSet ProcessEnabledFeatures(string[] enabledF LogError(PSEventId.ExperimentalFeature_InvalidName, name, message); } } - return ImmutableHashSet.CreateRange(StringComparer.OrdinalIgnoreCase, list); + return new ReadOnlyBag(new HashSet(list, StringComparer.OrdinalIgnoreCase)); } /// diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index e18f794ab44..6c83d545b5a 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; @@ -2390,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/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/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/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 8f1c971f5aa..c17782be1a0 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; @@ -258,7 +259,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 +267,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); } @@ -1927,4 +1913,50 @@ internal T Pop() return item; } } + + /// + /// A readonly Hashset. + /// + internal sealed class ReadOnlyBag : IEnumerable + { + private HashSet _hashset; + + /// + /// Constructor for the readonly Hashset. + /// + internal ReadOnlyBag(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 ReadOnlyBag Empty = new ReadOnlyBag(new HashSet(capacity: 0)); + } } 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/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 01398dcb0e6..113ae5457ff 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -49,7 +49,7 @@ internal CompiledScriptBlockData(IParameterMetadataProvider ast, bool isFilter) internal CompiledScriptBlockData(string scriptText, bool isProductCode) { - this.IsProductCode = isProductCode; + _isProductCode = isProductCode; _scriptText = scriptText; this.Id = Guid.NewGuid(); } @@ -163,11 +163,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) { @@ -199,9 +194,19 @@ private void PerformSecurityChecks() 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 && + scriptFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase) + && IsScriptBlockInFactASafeHashtable()) + { + // Skip the scan for .psd1 files if their content is in fact a safe HashtableAst. + 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) { @@ -221,6 +226,45 @@ private void PerformSecurityChecks() { HasSuspiciousContent = true; } + + // 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 || + 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 in fact 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. @@ -269,12 +313,23 @@ 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 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/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/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 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 /// 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/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 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 new file mode 100644 index 00000000000..c7e302ad29f --- /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) 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 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 @@ -