diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs index 13a2aa4ee40..b82d47952da 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs @@ -75,6 +75,12 @@ public int MaximumFollowRelLink [Alias("RHV")] public string ResponseHeadersVariable { get; set; } + /// + /// Gets or sets the variable name to use for storing the status code from the response. + /// + [Parameter] + public string StatusCodeVariable { get; set; } + #endregion Parameters #region Helper Methods @@ -454,6 +460,12 @@ internal override void ProcessResponse(HttpResponseMessage response) StreamHelper.SaveStreamToFile(responseStream, QualifiedOutFile, this); } + if (!string.IsNullOrEmpty(StatusCodeVariable)) + { + PSVariableIntrinsics vi = SessionState.PSVariable; + vi.Set(StatusCodeVariable, (int)response.StatusCode); + } + if (!string.IsNullOrEmpty(ResponseHeadersVariable)) { PSVariableIntrinsics vi = SessionState.PSVariable; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs index 6cdb5ecfbe6..d2d4417ab30 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs @@ -381,6 +381,12 @@ public virtual string CustomMethod [Parameter] public virtual SwitchParameter Resume { get; set; } + /// + /// Gets or sets whether to skip checking HTTP status for error codes. + /// + [Parameter] + public virtual SwitchParameter SkipHttpErrorCheck { get; set; } + #endregion #endregion Virtual Properties @@ -690,6 +696,11 @@ internal bool ShouldWriteToPipeline get { return (!ShouldSaveToOutFile || PassThru); } } + internal bool ShouldCheckHttpStatus + { + get { return !SkipHttpErrorCheck; } + } + /// /// Determines whether writing to a file should Resume and append rather than overwrite. /// @@ -1242,7 +1253,7 @@ internal virtual void FillRequestStream(HttpRequestMessage request) // Add the content headers if (request.Content == null) - { + { request.Content = new StringContent(string.Empty); request.Content.Headers.Clear(); } @@ -1513,7 +1524,7 @@ protected override void ProcessRecord() OutFile = null; } - if (!_isSuccess) + if (ShouldCheckHttpStatus && !_isSuccess) { string message = string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.ResponseStatusCodeFailure, (int)response.StatusCode, response.ReasonPhrase); diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index b6d77ad4eff..d4ab2ff5009 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -370,6 +370,13 @@ $redirectTests = @( Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { BeforeAll { $WebListener = Start-WebListener + $NotFoundQuery = @{ + statuscode = 404 + responsephrase = 'Not Found' + contenttype = 'text/plain' + body = 'oops' + headers = "{}" + } } # Validate the output of Invoke-WebRequest @@ -780,6 +787,23 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { $result.Output.RelationLink["next"] | Should -BeExactly "${baseUri}?maxlinks=3&linknumber=2&type=${type}" } + It "Verify Invoke-WebRequest supresses terminating errors with -SkipHttpErrorCheck" { + $uri = Get-WebListenerUrl -Test 'Response' -Query $NotFoundQuery + $command = "Invoke-WebRequest -SkipHttpErrorCheck -Uri '$uri'" + $result = ExecuteWebCommand -Command $command + $result.output.StatusCode | Should -Be 404 + $result.output.Content | Should -BeExactly "oops" + $result.error | Should -BeNullOrEmpty + } + + It "Verify Invoke-WebRequest terminates without -SkipHttpErrorCheck" { + $uri = Get-WebListenerUrl -Test 'Response' -Query $NotFoundQuery + $command = "Invoke-WebRequest -Uri '$uri'" + $result = ExecuteWebCommand -Command $command + $result.output | Should -BeNullOrEmpty + $result.error | Should -Not -BeNullOrEmpty + } + Context "Redirect" { It "Validates Invoke-WebRequest with -PreserveAuthorizationOnRedirect preserves the authorization header on redirect: " -TestCases $redirectTests { param($redirectType, $redirectedMethod) @@ -1930,6 +1954,14 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { BeforeAll { $WebListener = Start-WebListener + + $NotFoundQuery = @{ + statuscode = 404 + responsephrase = 'Not Found' + contenttype = 'application/json' + body = '{"message": "oops"}' + headers = "{}" + } } #User-Agent changes on different platforms, so tests should only be run if on the correct platform @@ -2259,6 +2291,64 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { 1..3 | ForEach-Object { $result.Output[$_ - 1].linknumber | Should -BeExactly $_ } } + It "Verify Invoke-RestMethod supresses terminating errors with -SkipHttpErrorCheck" { + $uri = Get-WebListenerUrl -Test 'Response' -Query $NotFoundQuery + $command = "Invoke-RestMethod -SkipHttpErrorCheck -Uri '$uri'" + $result = ExecuteWebCommand -Command $command + $result.output.message | Should -BeExactly "oops" + $result.output.error | Should -BeNullOrEmpty + } + + It "Verify Invoke-RestMethod terminates without -SkipHttpErrorCheck" { + $uri = Get-WebListenerUrl -Test 'Response' -Query $NotFoundQuery + $command = "Invoke-RestMethod -Uri '$uri'" + $result = ExecuteWebCommand -Command $command + $result.output | Should -BeNullOrEmpty + $result.error | Should -Not -BeNullOrEmpty + } + + It "Verify Invoke-RestMethod assigns 200 status code with -StatusCodeVariable" { + $query = @{ + statuscode = 200 + responsephrase = 'OK' + contenttype = 'application/json' + body = '{"message": "works"}' + headers = "{}" + } + + $uri = Get-WebListenerUrl -Test 'Response' -Query $query + Invoke-RestMethod -StatusCodeVariable code -Uri "$uri" + $code | Should -Be 200 + } + + It "Verify Invoke-RestMethod assigns 404 status code with -StatusCodeVariable" { + $query = @{ + statuscode = 404 + responsephrase = 'Not Found' + contenttype = 'application/json' + body = '{"message": "oops"}' + headers = "{}" + } + + $uri = Get-WebListenerUrl -Test 'Response' -Query $query + Invoke-RestMethod -SkipHttpErrorCheck -StatusCodeVariable code -Uri "$uri" + $code | Should -Be 404 + } + + It "Verify Invoke-RestMethod assigns 500 status code with -StatusCodeVariable" { + $query = @{ + statuscode = 500 + responsephrase = 'Internal Server Error' + contenttype = 'application/json' + body = '{"message": "oops"}' + headers = "{}" + } + + $uri = Get-WebListenerUrl -Test 'Response' -Query $query + Invoke-RestMethod -SkipHttpErrorCheck -StatusCodeVariable code -Uri "$uri" + $code | Should -Be 500 + } + #region Redirect tests It "Validates Invoke-RestMethod with -PreserveAuthorizationOnRedirect preserves the authorization header on redirect: " -TestCases $redirectTests { @@ -3387,4 +3477,3 @@ Describe "Web cmdlets tests using the cmdlet's aliases" -Tags "CI", "RequireAdmi $result.Hello | Should -Be "world" } } -