From d8d32d79c2d663f2033956d2862800c19ff151f9 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 15 Oct 2019 14:32:14 -0700 Subject: [PATCH] Implement `Get-Error` cmdlet as Experimental Feature (#10727) --- .../commands/utility/Get-Error.cs | 93 ++++++++ .../host/msh/ConsoleHost.cs | 103 +++++++-- .../host/msh/ConsoleHostUserInterface.cs | 3 + .../Microsoft.PowerShell.Utility.psd1 | 6 +- .../Microsoft.PowerShell.Utility.psd1 | 6 +- .../PowerShellCore_format_ps1xml.cs | 207 +++++++++++++++++- .../engine/InitialSessionState.cs | 1 + .../Get-Error.Tests.ps1 | 93 ++++++++ .../engine/Basic/DefaultCommands.Tests.ps1 | 2 + 9 files changed, 478 insertions(+), 36 deletions(-) create mode 100644 src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-Error.cs create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-Error.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-Error.cs new file mode 100644 index 0000000000..9c8d670e06 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-Error.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Class for Get-Error implementation. + /// + [Experimental("Microsoft.PowerShell.Utility.PSGetError", ExperimentAction.Show)] + [Cmdlet(VerbsCommon.Get, "Error", + HelpUri = "https://docs.microsoft.com/powershell/module/microsoft.powershell.utility/get-error?view=powershell-7&WT.mc_id=ps-gethelp", + DefaultParameterSetName = NewestParameterSetName)] + public sealed class GetErrorCommand : PSCmdlet + { + internal const string ErrorParameterSetName = "Error"; + internal const string NewestParameterSetName = "Newest"; + internal const string AliasNewest = "Last"; + + /// + /// Gets or sets the error object to resolve. + /// + [Parameter(Position = 0, ValueFromPipeline = true, ParameterSetName = ErrorParameterSetName)] + [ValidateNotNullOrEmpty] + public PSObject InputObject { get; set; } + + /// + /// Gets or sets the number of error objects to resolve starting with newest first. + /// + [Parameter(ParameterSetName = NewestParameterSetName)] + [Alias(AliasNewest)] + [ValidateRange(1, int.MaxValue)] + public int Newest { get; set; } = 1; + + /// + /// Process the error object. + /// + protected override void ProcessRecord() + { + var errorRecords = new List(); + var index = 0; + + if (InputObject != null) + { + if (InputObject.BaseObject is Exception || InputObject.BaseObject is ErrorRecord) + { + errorRecords.Add(InputObject); + } + } + else + { + var errorVariable = SessionState.PSVariable.Get("error"); + var count = Newest; + ArrayList errors = (ArrayList)errorVariable.Value; + if (count > errors.Count) + { + count = errors.Count; + } + + while (count > 0) + { + errorRecords.Add(errors[index]); + index++; + count--; + } + } + + index = 0; + bool addErrorIdentifier = errorRecords.Count > 1 ? true : false; + + foreach (object errorRecord in errorRecords) + { + PSObject obj = PSObject.AsPSObject(errorRecord); + obj.TypeNames.Insert(0, "PSExtendedError"); + + // Remove some types so they don't get rendered by those formats + obj.TypeNames.Remove("System.Management.Automation.ErrorRecord"); + obj.TypeNames.Remove("System.Exception"); + + if (addErrorIdentifier) + { + obj.Properties.Add(new PSNoteProperty("PSErrorIndex", index++)); + } + + WriteObject(obj); + } + } + } +} diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 393b1bd364..1089985ec0 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -735,125 +735,184 @@ namespace Microsoft.PowerShell _ui = ui; } + public ConsoleColor FormatAccentColor + { + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + get + { + return _ui.FormatAccentColor; + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + set + { + _ui.FormatAccentColor = value; + } + } + public ConsoleColor ErrorAccentColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.ErrorAccentColor; } + { + return _ui.ErrorAccentColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.ErrorAccentColor = value; } + { + _ui.ErrorAccentColor = value; + } } public ConsoleColor ErrorForegroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.ErrorForegroundColor; } + { + return _ui.ErrorForegroundColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.ErrorForegroundColor = value; } + { + _ui.ErrorForegroundColor = value; + } } public ConsoleColor ErrorBackgroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.ErrorBackgroundColor; } + { + return _ui.ErrorBackgroundColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.ErrorBackgroundColor = value; } + { + _ui.ErrorBackgroundColor = value; + } } public ConsoleColor WarningForegroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.WarningForegroundColor; } + { + return _ui.WarningForegroundColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.WarningForegroundColor = value; } + { + _ui.WarningForegroundColor = value; + } } public ConsoleColor WarningBackgroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.WarningBackgroundColor; } + { + return _ui.WarningBackgroundColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.WarningBackgroundColor = value; } + { + _ui.WarningBackgroundColor = value; + } } public ConsoleColor DebugForegroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.DebugForegroundColor; } + { + return _ui.DebugForegroundColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.DebugForegroundColor = value; } + { + _ui.DebugForegroundColor = value; + } } public ConsoleColor DebugBackgroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.DebugBackgroundColor; } + { + return _ui.DebugBackgroundColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.DebugBackgroundColor = value; } + { + _ui.DebugBackgroundColor = value; + } } public ConsoleColor VerboseForegroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.VerboseForegroundColor; } + { + return _ui.VerboseForegroundColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.VerboseForegroundColor = value; } + { + _ui.VerboseForegroundColor = value; + } } public ConsoleColor VerboseBackgroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.VerboseBackgroundColor; } + { + return _ui.VerboseBackgroundColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.VerboseBackgroundColor = value; } + { + _ui.VerboseBackgroundColor = value; + } } public ConsoleColor ProgressForegroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.ProgressForegroundColor; } + { + return _ui.ProgressForegroundColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.ProgressForegroundColor = value; } + { + _ui.ProgressForegroundColor = value; + } } public ConsoleColor ProgressBackgroundColor { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] get - { return _ui.ProgressBackgroundColor; } + { + return _ui.ProgressBackgroundColor; + } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] set - { _ui.ProgressBackgroundColor = value; } + { + _ui.ProgressBackgroundColor = value; + } } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index 5dd59cb770..3862df694e 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -1355,6 +1355,9 @@ namespace Microsoft.PowerShell } } + // Format colors + public ConsoleColor FormatAccentColor { get; set; } = ConsoleColor.Green; + // Error colors public ConsoleColor ErrorAccentColor { get; set; } = ConsoleColor.Cyan; public ConsoleColor ErrorForegroundColor { get; set; } = ConsoleColor.Red; 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 34e7cc94ca..d6cf7978b1 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -25,7 +25,7 @@ CmdletsToExport = @( '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' + 'Format-Wide', 'ConvertTo-Xml', 'Select-Xml', 'Get-Error' ) FunctionsToExport = @() AliasesToExport = @('fhx') @@ -38,6 +38,10 @@ PrivateData = @{ Name = 'Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace' Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger.' } + @{ + Name = 'Microsoft.PowerShell.Utility.PSGetError' + Description = 'Enable Get-Error cmdlet that displays detailed information about ErrorRecords included nested objects' + } ) } } 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 a020aa17ea..c2fb554824 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -23,7 +23,7 @@ CmdletsToExport = @( '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' + 'Write-Warning', 'Invoke-WebRequest', 'Format-Wide', 'ConvertTo-Xml', 'Select-Xml', 'Get-Error' ) FunctionsToExport = @() AliasesToExport = @('fhx') @@ -36,6 +36,10 @@ PrivateData = @{ Name = 'Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace' Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger.' } + @{ + Name = 'Microsoft.PowerShell.Utility.PSGetError' + Description = 'Enable Get-Error cmdlet that displays detailed information about ErrorRecords included nested objects' + } ) } } diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index 30118933b9..21f529e67a 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -137,6 +137,10 @@ namespace System.Management.Automation.Runspaces "System.Management.Automation.ScriptBlock", ViewsOf_System_Management_Automation_ScriptBlock()); + yield return new ExtendedTypeDefinition( + "PSExtendedError", + ViewsOf_System_Management_Automation_GetError()); + yield return new ExtendedTypeDefinition( "System.Management.Automation.ErrorRecord", ViewsOf_System_Management_Automation_ErrorRecord()); @@ -722,6 +726,197 @@ namespace System.Management.Automation.Runspaces .EndControl()); } + // This generates a custom view for ErrorRecords and Exceptions making + // specific nested types defined in $expandTypes visible. It also handles + // IEnumerable types. Nested types are indented by 4 spaces. + private static IEnumerable ViewsOf_System_Management_Automation_GetError() + { + yield return new FormatViewDefinition("GetErrorInstance", + CustomControl.Create() + .GroupByProperty("PSErrorIndex", label: "ErrorIdentifier") + .StartEntry() + .AddScriptBlockExpressionBinding(@" + $maxDepth = 10 + $ellipsis = ""`u{2026}"" + $resetColor = '' + if ($Host.UI.SupportsVirtualTerminal) { + $resetColor = ""`e[0m"" + } + + function Get-VT100Color([string] $color) { + if (! $Host.UI.SupportsVirtualTerminal) { + return '' + } + + $colors = @{ + 'Black' = ""`e[2;30m"" + 'DarkRed' = ""`e[2;31m"" + 'DarkGreen' = ""`e[2;32m"" + 'DarkYellow' = ""`e[2;33m"" + 'DarkBlue' = ""`e[2;34m"" + 'DarkMagenta' = ""`e[2;35m"" + 'DarkCyan' = ""`e[2;36m"" + 'Gray' = ""`e[2;37m"" + 'DarkGray' = ""`e[1;30m"" + 'Red' = ""`e[1;31m"" + 'Green' = ""`e[1;32m"" + 'Yellow' = ""`e[1;33m"" + 'Blue' = ""`e[1;34m"" + 'Magenta' = ""`e[1;35m"" + 'Cyan' = ""`e[1;36m"" + 'White' = ""`e[1;37m"" + } + + return $colors[$color] + } + + function Show-ErrorRecord($obj, [int]$indent = 0, [int]$depth = 1) { + $newline = [Environment]::Newline + $output = [System.Text.StringBuilder]::new() + $prefix = ' ' * $indent + $accentColor = Get-VT100Color $Host.PrivateData.FormatAccentColor + $expandTypes = @( + 'Microsoft.Rest.HttpRequestMessageWrapper' + 'Microsoft.Rest.HttpResponseMessageWrapper' + 'System.Management.Automation.InvocationInfo' + ) + + # first find the longest property so we can indent properly + $propLength = 0 + foreach ($prop in $obj.PSObject.Properties) { + if ($prop.Value -ne $null -and $prop.Value -ne [string]::Empty -and $prop.Name.Length -gt $propLength) { + $propLength = $prop.Name.Length + } + } + + $addedProperty = $false + foreach ($prop in $obj.PSObject.Properties) { + + # don't show empty properties or our added property for $error[index] + if ($prop.Value -ne $null -and $prop.Value -ne [string]::Empty -and $prop.Value.count -gt 0 -and $prop.Name -ne 'PSErrorIndex') { + $addedProperty = $true + $null = $output.Append($prefix) + $null = $output.Append($accentColor) + $null = $output.Append($prop.Name) + $propNameIndent = ' ' * ($propLength - $prop.Name.Length) + $null = $output.Append($propNameIndent) + $null = $output.Append(' : ') + $null = $output.Append($resetColor) + + $newIndent = $indent + 4 + + # only show nested objects that are Exceptions, ErrorRecords, or types defined in $expandTypes and types not in $ignoreTypes + if ($prop.Value -is [Exception] -or $prop.Value -is [System.Management.Automation.ErrorRecord] -or + $expandTypes -contains $prop.TypeNameOfValue -or ($prop.TypeNames -ne $null -and $expandTypes -contains $prop.TypeNames[0])) { + + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } + else { + $null = $output.Append($newline) + $null = $output.Append((Show-ErrorRecord $prop.Value $newIndent ($depth + 1))) + } + } + # `TargetSite` has many members that are not useful visually, so we have a reduced view of the relevant members + elseif ($prop.Name -eq 'TargetSite' -and $prop.Value.GetType().Name -eq 'RuntimeMethodInfo') { + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } + else { + $targetSite = [PSCustomObject]@{ + Name = $prop.Value.Name + DeclaringType = $prop.Value.DeclaringType + MemberType = $prop.Value.MemberType + Module = $prop.Value.Module + } + + $null = $output.Append($newline) + $null = $output.Append((Show-ErrorRecord $targetSite $newIndent ($depth + 1))) + } + } + # `StackTrace` is handled specifically because the lines are typically long but necessary so they are left justified without additional indentation + elseif ($prop.Name -eq 'StackTrace') { + # for a stacktrace which is usually quite wide with info, we left justify it + $null = $output.Append($newline) + $null = $output.Append($prop.Value) + } + # Dictionary and Hashtable we want to show as Key/Value pairs, we don't do the extra whitespace alignment here + elseif ($prop.Value.GetType().Name.StartsWith('Dictionary') -or $prop.Value.GetType().Name -eq 'Hashtable') { + $isFirstElement = $true + foreach ($key in $prop.Value.Keys) { + if ($isFirstElement) { + $null = $output.Append($newline) + } + + if ($key -eq 'Authorization') { + $null = $output.Append(""${prefix} ${accentColor}${key} : ${resetColor}${ellipsis}${newline}"") + } + else { + $null = $output.Append(""${prefix} ${accentColor}${key} : ${resetColor}$($prop.Value[$key])${newline}"") + } + + $isFirstElement = $false + } + } + # if the object implements IEnumerable and not a string, we try to show each object + # We ignore the `Data` property as it can contain lots of type information by the interpreter that isn't useful here + elseif (!($prop.Value -is [System.String]) -and $prop.Value.GetType().GetInterface('IEnumerable') -ne $null -and $prop.Name -ne 'Data') { + + if ($depth -ge $maxDepth) { + $null = $output.Append($ellipsis) + } + else { + $isFirstElement = $true + foreach ($value in $prop.Value) { + $null = $output.Append($newline) + if (!$isFirstElement) { + $null = $output.Append($newline) + } + $null = $output.Append((Show-ErrorRecord $value $newIndent ($depth + 1))) + $isFirstElement = $false + } + } + } + # anything else, we use ToString() + else { + $value = $prop.Value.ToString().Trim() + + $isFirstLine = $true + if ($value.Contains($newline)) { + # the 3 is to account for ' : ' + $valueIndent = ' ' * ($propLength + 3) + # need to trim any extra whitespace already in the text + foreach ($line in $value.Split($newline)) { + if (!$isFirstLine) { + $null = $output.Append(""${newline}${prefix}${valueIndent}"") + } + $null = $output.Append($line.Trim()) + $isFirstLine = $false + } + } + else { + $null = $output.Append($value) + } + } + + $null = $output.Append($newline) + } + } + + # if we had added nested properties, we need to remove the last newline + if ($addedProperty) { + $null = $output.Remove($output.Length - $newline.Length, $newline.Length) + } + + $output.ToString() + } + + Show-ErrorRecord $_ + ") + .EndEntry() + .EndControl()); + } + private static IEnumerable ViewsOf_System_Management_Automation_ErrorRecord() { yield return new FormatViewDefinition("ErrorInstance", @@ -837,18 +1032,6 @@ namespace System.Management.Automation.Runspaces } return ($string.Substring(0,$length) -split '\s',-2)[0] - - #if (-not $string.Contains(' ')) { - # return $string.Substring(0, $length) - #} - - #$split = $string.Substring(0, $length).Split(' ') - #if ($split.Count -gt 1) { - # return [string]::Join(' ', $split, 0, $split.Count - 1) - #} - #else { - # return $split[0] - #} } $errorColor = '' diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index f9c0f74fbc..8c99230c93 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -4557,6 +4557,7 @@ end { new SessionStateAliasEntry("rp", "Remove-ItemProperty", string.Empty, ReadOnly), new SessionStateAliasEntry("rmo", "Remove-Module", string.Empty, ReadOnly), new SessionStateAliasEntry("rv", "Remove-Variable", string.Empty, ReadOnly), + new SessionStateAliasEntry("gerr", "Get-Error", string.Empty, ReadOnly), new SessionStateAliasEntry("rvpa", "Resolve-Path", string.Empty, ReadOnly), new SessionStateAliasEntry("sal", "Set-Alias", string.Empty, ReadOnly), new SessionStateAliasEntry("sbp", "Set-PSBreakpoint", string.Empty, ReadOnly), diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 new file mode 100644 index 0000000000..07f91e161e --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 @@ -0,0 +1,93 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe 'Get-Error tests' -Tag CI { + BeforeAll { + $skipTest = -not $EnabledExperimentalFeatures.Contains('Microsoft.PowerShell.Utility.PSGetError') + if ($skipTest) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'Microsoft.PowerShell.Utility.PSGetError' to be enabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + } + } + + AfterAll { + if ($skipTest) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + } + + It 'Get-Error resolves $Error[0] and includes InnerException' { + try { + 1/0 + } + catch { + } + + $out = Get-Error | Out-String + $out | Should -BeLikeExactly '*InnerException*' + } + + It 'Get-Error -Newest `` works: ' -TestCases @( + @{ scenario = 'less than total'; count = 1; paramname = 'Newest' } + @{ scenario = 'equal to total'; count = 2; paramname = 'Last' } + @{ scenario = 'greater than total'; count = 9999; paramname = 'Newest' } + ){ + param ($count, $paramname) + + try { + 1/0 + } + catch { + } + + try { + get-item (new-guid) -ErrorAction SilentlyContinue + } + catch { + } + + $params = @{ $paramname = $count } + + $out = Get-Error @params + + $expected = $count + if ($count -eq 9999) { + $expected = $error.Count + } + + $out.Count | Should -Be $expected + } + + It 'Get-Error -Newest with invalid value `` should fail' -TestCases @( + @{ value = 0 } + @{ value = -2 } + ){ + param($value) + + { Get-Error -Newest $value } | Should -Throw -ErrorId 'ParameterArgumentValidationError,Microsoft.PowerShell.Commands.GetErrorCommand' + } + + It 'Get-Error will accept pipeline input' { + try { + 1/0 + } + catch { + } + + $out = $error[0] | Get-Error | Out-String + $out | Should -BeLikeExactly '*-2146233087*' + } + + It 'Get-Error will handle Exceptions' { + try { + Invoke-Expression '1/d' + } + catch { + } + + $out = Get-Error | Out-String + $out | Should -BeLikeExactly '*ExpectedValueExpression*' + $out | Should -BeLikeExactly '*UnexpectedToken*' + } +} diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 index b2c69a648f..6748622587 100644 --- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 @@ -73,6 +73,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Alias", "gcm", "Get-Command", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "gcs", "Get-PSCallStack", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "gdr", "Get-PSDrive", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" +"Alias", "gerr", "Get-Error", $( $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "ghy", "Get-History", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "gi", "Get-Item", $($FullCLR -or $CoreWindows -or $CoreUnix), "ReadOnly", "", "" "Alias", "gin", "Get-ComputerInfo", $($FullCLR -or $CoreWindows ), "", "", "" @@ -269,6 +270,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Get-Credential", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-Culture", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-Date", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" +"Cmdlet", "Get-Error", "", $( $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-Event", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-EventLog", "", $($FullCLR ), "", "", "" "Cmdlet", "Get-EventSubscriber", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"