mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Support HTTP persistent connections in Web Cmdlets (#19249)
This commit is contained in:
+79
-65
@@ -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
|
||||
|
||||
+18
-1
@@ -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;
|
||||
|
||||
+202
-19
@@ -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" {
|
||||
|
||||
Reference in New Issue
Block a user