Support HTTP persistent connections in Web Cmdlets (#19249)

This commit is contained in:
stevenebutler
2023-03-07 09:42:08 -08:00
committed by GitHub
parent 6824d489bb
commit 9e341148f4
5 changed files with 444 additions and 85 deletions
@@ -88,7 +88,7 @@ namespace Microsoft.PowerShell.Commands
/// <summary>
/// Base class for Invoke-RestMethod and Invoke-WebRequest commands.
/// </summary>
public abstract class WebRequestPSCmdlet : PSCmdlet
public abstract class WebRequestPSCmdlet : PSCmdlet, IDisposable
{
#region Fields
@@ -132,6 +132,11 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
private bool _resumeSuccess = false;
/// <summary>
/// True if the Dispose() method has already been called to cleanup Disposable fields.
/// </summary>
private bool _disposed = false;
#endregion Fields
#region Virtual Properties
@@ -522,7 +527,7 @@ namespace Microsoft.PowerShell.Commands
bool handleRedirect = keepAuthorizationOnRedirect || AllowInsecureRedirect || PreserveHttpMethodOnRedirect;
using HttpClient client = GetHttpClient(handleRedirect);
HttpClient client = GetHttpClient(handleRedirect);
int followedRelLink = 0;
Uri uri = Uri;
@@ -639,7 +644,7 @@ namespace Microsoft.PowerShell.Commands
// Errors with redirection counts of greater than 0 are handled automatically by .NET, but are
// impossible to detect programmatically when we hit this limit. By handling this ourselves
// (and still writing out the result), users can debug actual HTTP redirect problems.
if (WebSession.MaximumRedirection == 0 && IsRedirectCode(response.StatusCode))
if (_maximumRedirection == 0 && IsRedirectCode(response.StatusCode))
{
ErrorRecord er = new(new InvalidOperationException(), "MaximumRedirectExceeded", ErrorCategory.InvalidOperation, request);
er.ErrorDetails = new ErrorDetails(WebCmdletStrings.MaximumRedirectionCountExceeded);
@@ -688,6 +693,33 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
protected override void StopProcessing() => _cancelToken?.Cancel();
/// <summary>
/// Disposes the associated WebSession if it is not being used as part of a persistent session.
/// </summary>
/// <param name="disposing">True when called from Dispose() and false when called from finalizer.</param>
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing && !IsPersistentSession())
{
WebSession?.Dispose();
WebSession = null;
}
_disposed = true;
}
}
/// <summary>
/// Disposes the associated WebSession if it is not being used as part of a persistent session.
/// </summary>
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion Overrides
#region Virtual Methods
@@ -900,20 +932,41 @@ namespace Microsoft.PowerShell.Commands
WebSession.UserAgent = UserAgent;
}
if (Proxy is not null)
// Proxy and NoProxy parameters are mutually exclusive.
// If NoProxy is provided, WebSession will turn off the proxy
// and if Proxy is provided NoProxy will be turned off.
if (NoProxy.IsPresent)
{
WebProxy webProxy = new(Proxy);
webProxy.BypassProxyOnLocal = false;
if (ProxyCredential is not null)
WebSession.NoProxy = true;
}
else
{
if (Proxy is not null)
{
webProxy.Credentials = ProxyCredential.GetNetworkCredential();
}
else
{
webProxy.UseDefaultCredentials = ProxyUseDefaultCredentials;
}
WebProxy webProxy = new(Proxy);
webProxy.BypassProxyOnLocal = false;
if (ProxyCredential is not null)
{
webProxy.Credentials = ProxyCredential.GetNetworkCredential();
}
else
{
webProxy.UseDefaultCredentials = ProxyUseDefaultCredentials;
}
WebSession.Proxy = webProxy;
// We don't want to update the WebSession unless the proxies are different
// as that will require us to create a new HttpClientHandler and lose connection
// persistence.
if (!webProxy.Equals(WebSession.Proxy))
{
WebSession.Proxy = webProxy;
}
}
}
if (MyInvocation.BoundParameters.ContainsKey(nameof(SslProtocol)))
{
WebSession.SslProtocol = SslProtocol;
}
if (MaximumRedirection > -1)
@@ -921,6 +974,8 @@ namespace Microsoft.PowerShell.Commands
WebSession.MaximumRedirection = MaximumRedirection;
}
WebSession.SkipCertificateCheck = SkipCertificateCheck.IsPresent;
// Store the other supplied headers
if (Headers is not null)
{
@@ -945,63 +1000,20 @@ namespace Microsoft.PowerShell.Commands
// Only set retry interval if retry count is set.
WebSession.RetryIntervalInSeconds = RetryIntervalSec;
}
WebSession.TimeoutSec = TimeoutSec;
}
internal virtual HttpClient GetHttpClient(bool handleRedirect)
{
HttpClientHandler handler = new();
handler.CookieContainer = WebSession.Cookies;
handler.AutomaticDecompression = DecompressionMethods.All;
HttpClient client = WebSession.GetHttpClient(handleRedirect, out bool clientWasReset);
// Set the credentials used by this request
if (WebSession.UseDefaultCredentials)
if (clientWasReset)
{
// The UseDefaultCredentials flag overrides other supplied credentials
handler.UseDefaultCredentials = true;
}
else if (WebSession.Credentials is not null)
{
handler.Credentials = WebSession.Credentials;
WriteVerbose(WebCmdletStrings.WebSessionConnectionRecreated);
}
if (NoProxy)
{
handler.UseProxy = false;
}
else if (WebSession.Proxy is not null)
{
handler.Proxy = WebSession.Proxy;
}
if (WebSession.Certificates is not null)
{
handler.ClientCertificates.AddRange(WebSession.Certificates);
}
if (SkipCertificateCheck)
{
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
}
// This indicates GetResponse will handle redirects.
if (handleRedirect || WebSession.MaximumRedirection == 0)
{
handler.AllowAutoRedirect = false;
}
else if (WebSession.MaximumRedirection > 0)
{
handler.MaxAutomaticRedirections = WebSession.MaximumRedirection;
}
handler.SslProtocols = (SslProtocols)SslProtocol;
HttpClient httpClient = new(handler);
// Check timeout setting (in seconds instead of milliseconds as in HttpWebRequest)
httpClient.Timeout = TimeoutSec is 0 ? TimeSpan.FromMilliseconds(Timeout.Infinite) : new TimeSpan(0, 0, TimeoutSec);
return httpClient;
return client;
}
internal virtual HttpRequestMessage GetRequest(Uri uri)
@@ -1459,6 +1471,8 @@ namespace Microsoft.PowerShell.Commands
}
}
private bool IsPersistentSession() => MyInvocation.BoundParameters.ContainsKey(nameof(WebSession)) || MyInvocation.BoundParameters.ContainsKey(nameof(SessionVariable));
/// <summary>
/// Sets the ContentLength property of the request and writes the specified content to the request's RequestStream.
/// </summary>
@@ -1686,7 +1700,7 @@ namespace Microsoft.PowerShell.Commands
private static StringContent GetMultipartStringContent(object fieldName, object fieldValue)
{
ContentDispositionHeaderValue contentDisposition = new("form-data");
// .NET does not enclose field names in quotes, however, modern browsers and curl do.
contentDisposition.Name = "\"" + LanguagePrimitives.ConvertTo<string>(fieldName) + "\"";
@@ -1773,7 +1787,7 @@ namespace Microsoft.PowerShell.Commands
{
// Ignore errors
}
if (string.IsNullOrEmpty(formattedError))
{
// Remove HTML tags making it easier to read
@@ -6,7 +6,7 @@ using System.Net;
namespace Microsoft.PowerShell.Commands
{
internal class WebProxy : IWebProxy
internal class WebProxy : IWebProxy, IEquatable<WebProxy>
{
private ICredentials _credentials;
private readonly Uri _proxyAddress;
@@ -18,6 +18,23 @@ namespace Microsoft.PowerShell.Commands
_proxyAddress = address;
}
public override bool Equals(object obj) => Equals(obj as WebProxy);
public override int GetHashCode() => HashCode.Combine(_proxyAddress, _credentials, BypassProxyOnLocal);
public bool Equals(WebProxy other)
{
if (other is null)
{
return false;
}
// _proxyAddress cannot be null as it is set in the constructor
return other._credentials == _credentials
&& _proxyAddress.Equals(other._proxyAddress)
&& BypassProxyOnLocal == other.BypassProxyOnLocal;
}
public ICredentials Credentials
{
get => _credentials;
@@ -4,15 +4,37 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// WebRequestSession for holding session infos.
/// </summary>
public class WebRequestSession
public class WebRequestSession : IDisposable
{
private HttpClient _client;
private CookieContainer _cookies;
private bool _useDefaultCredentials;
private ICredentials _credentials;
private X509CertificateCollection _certificates;
private IWebProxy _proxy;
private int _maximumRedirection;
private WebSslProtocol _sslProtocol;
private bool _allowAutoRedirect;
private bool _skipCertificateCheck;
private bool _noProxy;
private bool _disposed;
private int _timeoutSec;
/// <summary>
/// Contains true if an existing HttpClient had to be disposed and recreated since the WebSession was last used.
/// </summary>
private bool _disposedClient;
/// <summary>
/// Gets or sets the Header property.
/// </summary>
@@ -27,25 +49,25 @@ namespace Microsoft.PowerShell.Commands
/// <summary>
/// Gets or sets the Cookies property.
/// </summary>
public CookieContainer Cookies { get; set; }
public CookieContainer Cookies { get => _cookies; set => SetClassVar(ref _cookies, value); }
#region Credentials
/// <summary>
/// Gets or sets the UseDefaultCredentials property.
/// </summary>
public bool UseDefaultCredentials { get; set; }
public bool UseDefaultCredentials { get => _useDefaultCredentials; set => SetStructVar(ref _useDefaultCredentials, value); }
/// <summary>
/// Gets or sets the Credentials property.
/// </summary>
public ICredentials Credentials { get; set; }
public ICredentials Credentials { get => _credentials; set => SetClassVar(ref _credentials, value); }
/// <summary>
/// Gets or sets the Certificates property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public X509CertificateCollection Certificates { get; set; }
public X509CertificateCollection Certificates { get => _certificates; set => SetClassVar(ref _certificates, value); }
#endregion
@@ -57,12 +79,23 @@ namespace Microsoft.PowerShell.Commands
/// <summary>
/// Gets or sets the Proxy property.
/// </summary>
public IWebProxy Proxy { get; set; }
public IWebProxy Proxy
{
get => _proxy;
set
{
SetClassVar(ref _proxy, value);
if (_proxy is not null)
{
NoProxy = false;
}
}
}
/// <summary>
/// Gets or sets the RedirectMax property.
/// Gets or sets the MaximumRedirection property.
/// </summary>
public int MaximumRedirection { get; set; }
public int MaximumRedirection { get => _maximumRedirection; set => SetStructVar(ref _maximumRedirection, value); }
/// <summary>
/// Gets or sets the count of retries for request failures.
@@ -79,23 +112,42 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
public WebRequestSession()
{
// build the headers collection
// Build the headers collection
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
ContentHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
// build the cookie jar
Cookies = new CookieContainer();
// Build the cookie jar
_cookies = new CookieContainer();
// initialize the credential and certificate caches
UseDefaultCredentials = false;
Credentials = null;
Certificates = null;
// Initialize the credential and certificate caches
_useDefaultCredentials = false;
_credentials = null;
_certificates = null;
// setup the default UserAgent
// Setup the default UserAgent
UserAgent = PSUserAgent.UserAgent;
Proxy = null;
MaximumRedirection = -1;
_proxy = null;
_maximumRedirection = -1;
_allowAutoRedirect = true;
}
internal WebSslProtocol SslProtocol { set => SetStructVar(ref _sslProtocol, value); }
internal bool SkipCertificateCheck { set => SetStructVar(ref _skipCertificateCheck, value); }
internal int TimeoutSec { set => SetStructVar(ref _timeoutSec, value); }
internal bool NoProxy
{
set
{
SetStructVar(ref _noProxy, value);
if (_noProxy)
{
Proxy = null;
}
}
}
/// <summary>
@@ -105,8 +157,139 @@ namespace Microsoft.PowerShell.Commands
internal void AddCertificate(X509Certificate certificate)
{
Certificates ??= new X509CertificateCollection();
if (!Certificates.Contains(certificate))
{
ResetClient();
Certificates.Add(certificate);
}
}
Certificates.Add(certificate);
/// <summary>
/// Gets an existing or creates a new HttpClient for this WebRequest session if none currently exists (either because it was never
/// created, or because changes to the WebSession properties required the existing HttpClient to be disposed).
/// </summary>
/// <param name="suppressHttpClientRedirects">True if the caller does not want the HttpClient to ever handle redirections automatically.</param>
/// <param name="clientWasReset">Contains true if an existing HttpClient had to be disposed and recreated since the WebSession was last used.</param>
/// <returns>The HttpClient cached in the WebSession, based on all current settings.</returns>
internal HttpClient GetHttpClient(bool suppressHttpClientRedirects, out bool clientWasReset)
{
// Do not auto redirect if the the caller does not want it, or maximum redirections is 0
SetStructVar(ref _allowAutoRedirect, !(suppressHttpClientRedirects || MaximumRedirection == 0));
clientWasReset = _disposedClient;
if (_client is null)
{
_client = CreateHttpClient();
_disposedClient = false;
}
return _client;
}
private HttpClient CreateHttpClient()
{
HttpClientHandler handler = new();
handler.CookieContainer = Cookies;
handler.AutomaticDecompression = DecompressionMethods.All;
if (Credentials is not null)
{
handler.Credentials = Credentials;
}
else
{
handler.UseDefaultCredentials = UseDefaultCredentials;
}
if (_noProxy)
{
handler.UseProxy = false;
}
else if (Proxy is not null)
{
handler.Proxy = Proxy;
}
if (Certificates is not null)
{
handler.ClientCertificates.AddRange(Certificates);
}
if (_skipCertificateCheck)
{
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
}
handler.AllowAutoRedirect = _allowAutoRedirect;
if (_allowAutoRedirect && MaximumRedirection > 0)
{
handler.MaxAutomaticRedirections = MaximumRedirection;
}
handler.SslProtocols = (SslProtocols)_sslProtocol;
// Check timeout setting (in seconds instead of milliseconds as in HttpWebRequest)
return new HttpClient(handler)
{
Timeout = _timeoutSec is 0 ? TimeSpan.FromMilliseconds(Timeout.Infinite) : TimeSpan.FromSeconds(_timeoutSec)
};
}
private void SetClassVar<T>(ref T oldValue, T newValue) where T : class
{
if (oldValue != newValue)
{
ResetClient();
oldValue = newValue;
}
}
private void SetStructVar<T>(ref T oldValue, T newValue) where T : struct
{
if (!oldValue.Equals(newValue))
{
ResetClient();
oldValue = newValue;
}
}
private void ResetClient()
{
if (_client is not null)
{
_disposedClient = true;
_client.Dispose();
_client = null;
}
}
/// <summary>
/// Dispose the WebRequestSession.
/// </summary>
/// <param name="disposing">True when called from Dispose() and false when called from finalizer.</param>
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_client?.Dispose();
}
_disposed = true;
}
}
/// <summary>
/// Dispose the WebRequestSession.
/// </summary>
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
@@ -249,4 +249,7 @@
<data name="JsonMaxDepthReached" xml:space="preserve">
<value>Resulting JSON is truncated as serialization has exceeded the set depth of {0}.</value>
</data>
<data name="WebSessionConnectionRecreated" xml:space="preserve">
<value>The WebSession properties were changed between requests forcing all HTTP connections in the session to be recreated.</value>
</data>
</root>
@@ -2250,6 +2250,148 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" {
$pathologicalRatio | Should -BeGreaterThan 5
}
}
Context 'Invoke-WebSession: Connection persistence in a WebSession' {
# Match verbose message from resource name WebSessionConnectionRecreated with message:
# The WebSession properties were changed between requests forcing all HTTP connections in the session to be recreated.
$matchConnRecreatedMessage = [regex]::new('WebSession.+HTTP')
function RunCheckingPersistence {
param(
[uri]$Uri,
[string]$Command,
[object]$Session,
[switch]$ExpectConnectionRecreated,
[switch]$CaptureSession
)
$pwsh = [PowerShell]::Create()
$pwsh.Runspace.SessionStateProxy.SetVariable('uri', $Uri)
if ($Session) {
$pwsh.Runspace.SessionStateProxy.SetVariable('Session', $Session)
$command = "$command -WebSession `$Session"
}
if ($CaptureSession) {
$command = "$command -SessionVariable Session"
}
$script = "`$null = $command -Verbose"
$pwsh.AddScript($script).Invoke()
$session = $pwsh.Runspace.SessionStateProxy.GetVariable('Session')
$expectedConnRecreatedCount = if ($ExpectConnectionRecreated) { 1 } else { 0 }
($pwsh.Streams.Verbose | Where-Object { $matchConnRecreatedMessage.Matches($_.Message) }).Count | Should -Be $expectedConnRecreatedCount
$pwsh.Dispose()
return $session
}
It 'Connection persistence maintained' {
$uri = Get-WebListenerUrl
$Session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -CaptureSession
1 .. 3 | ForEach-Object {
RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -Session $Session
}
}
It 'Connection persistence impacted by changing SkipCertificateCheck' {
$uri = Get-WebListenerUrl -Https
# This first request will throw because the certificate is invalid
$session = $null
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -CaptureSession
# No change in setting
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -SkipCertificateCheck:$false' -Session $session
# Skipping cert check changes persistence
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -SkipCertificateCheck' -Session $session -ExpectConnectionRecreated
# Same settings won't lose persistence
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -SkipCertificateCheck' -Session $session
# Lose persistence due to changing cert check - this will also throw
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -SkipCertificateCheck:$false' -Session $session -ExpectConnectionRecreated
}
It 'Connection persistence is not impacted by changing request headers' {
$uri = Get-WebListenerUrl
$session = $null
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -CaptureSession
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -Headers @{ A = "B" }' -Session $session
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -Headers @{}' -Session $session
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -Headers @{ A = "C"; B = "D"}' -Session $session
}
It 'Connection persistence is impacted by changing the session cookie jar' {
$uri = Get-WebListenerUrl
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -CaptureSession
$session.Cookies = New-Object System.Net.CookieContainer
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -Session $session -ExpectConnectionRecreated
# Adding a cookie to the container does not lose persistence
$Session.Cookies.Add('http://localhost', [system.net.cookie]::new('cookie', 'value'))
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -Session $session
}
It 'Connection persistence is not impacted by changing the user agent' {
$uri = Get-WebListenerUrl
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -UserAgent Powershell' -CaptureSession
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -UserAgent "PowerShell Core"' -Session $session
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -UserAgent "PowerShell Core with HttpClient"' -Session $session
# Ensure persistence is lost when we change a different setting
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -NoProxy -UserAgent "PowerShell Core"' -Session $session -ExpectConnectionRecreated
}
It 'Connection persistence is not impacted when NoProxy parameter is not supplied' {
$uri = Get-WebListenerUrl
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -CaptureSession
# Explicitly prevent proxy - connection lost
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -NoProxy' -Session $session -ExpectConnectionRecreated
# Provide explicit switch value - connection maintained
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -NoProxy:$true' -Session $session
# No spec for proxy - connection maintained
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -Session $session
# Two follow up calls without altering anything do not lose connection
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -Session $session
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -Session $session
}
It 'Connection persistence is not impacted when SslProtocol parameter is not supplied' {
$uri = Get-WebListenerUrl
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -SslProtocol Tls12' -CaptureSession
# No SslProtocol provided - keeps last value - connection retained
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -Session $session
# Explicit default - loses connection
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -SslProtocol Default' -Session $session -ExpectConnectionRecreated
# No SslProtocol provided - keeps last value - connection retained
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -Session $session
# Explicitly set to same value as last time it was set - connection retained
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri -SslProtocol Default' -Session $session
}
It 'Connection persistence is not impacted by Proxy and NoProxy unless changed parameters are used between invocations' {
$uri = Get-WebListenerUrl
$session = RunCheckingPersistence -Uri $uri -Command 'Invoke-WebRequest -Uri $uri' -CaptureSession
$proxy = 'http://127.0.0.1:8080'
$session = RunCheckingPersistence -Uri $uri -Command "Invoke-WebRequest -Uri $uri -proxy $proxy" -Session $session -ExpectConnectionRecreated
# same proxy - do not lose persistence
$session = RunCheckingPersistence -Uri $uri -Command "Invoke-WebRequest -Uri $uri -proxy $proxy" -Session $session
# No proxy at all - use previous setting and don't lose connection
$session = RunCheckingPersistence -Uri $uri -Command "Invoke-WebRequest -Uri $uri" -Session $session
# NoProxy toggles proxy off - loses connection
$session = RunCheckingPersistence -Uri $uri -Command "Invoke-WebRequest -Uri $uri -NoProxy" -Session $session -ExpectConnectionRecreated
# No setting at all - retains NoProxy setting
$session = RunCheckingPersistence -Uri $uri -Command "Invoke-WebRequest -Uri $uri" -Session $session
# Use proxy again - lose connection
$session = RunCheckingPersistence -Uri $uri -Command "Invoke-WebRequest -Uri $uri -proxy $proxy" -Session $session -ExpectConnectionRecreated
# No proxy specified - connection retained
$session = RunCheckingPersistence -Uri $uri -Command "Invoke-WebRequest -Uri $uri" -Session $session
# Proxy changed - lose connection
$proxy = 'http://localhost:8080'
$session = RunCheckingPersistence -Uri $uri -Command "Invoke-WebRequest -Uri $uri -proxy $proxy" -Session $session -ExpectConnectionRecreated
# No proxy specified - connection retained
$session = RunCheckingPersistence -Uri $uri -Command "Invoke-WebRequest -Uri $uri" -Session $session
}
}
}
Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" {