@@ -93,6 +93,11 @@ public abstract class WebRequestPSCmdlet : PSCmdlet, IDisposable
9393 {
9494 #region Fields
9595
96+ /// <summary>
97+ /// Used to prefix the headers in debug and verbose messaging.
98+ /// </summary>
99+ internal const string DebugHeaderPrefix = "--- " ;
100+
96101 /// <summary>
97102 /// Cancellation token source.
98103 /// </summary>
@@ -1280,40 +1285,27 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM
12801285 _cancelToken = new CancellationTokenSource ( ) ;
12811286 try
12821287 {
1283- long requestContentLength = request . Content is null ? 0 : request . Content . Headers . ContentLength . Value ;
1284-
1285- string reqVerboseMsg = string . Format (
1286- CultureInfo . CurrentCulture ,
1287- WebCmdletStrings . WebMethodInvocationVerboseMsg ,
1288- request . Version ,
1289- request . Method ,
1290- requestContentLength ) ;
1291-
1292- WriteVerbose ( reqVerboseMsg ) ;
1293-
1294- string reqDebugMsg = string . Format (
1295- CultureInfo . CurrentCulture ,
1296- WebCmdletStrings . WebRequestDebugMsg ,
1297- request . ToString ( ) ) ;
1288+ if ( IsWriteVerboseEnabled ( ) )
1289+ {
1290+ WriteWebRequestVerboseInfo ( currentRequest ) ;
1291+ }
12981292
1299- WriteDebug ( reqDebugMsg ) ;
1293+ if ( IsWriteDebugEnabled ( ) )
1294+ {
1295+ WriteWebRequestDebugInfo ( currentRequest ) ;
1296+ }
13001297
13011298 response = client . SendAsync ( currentRequest , HttpCompletionOption . ResponseHeadersRead , _cancelToken . Token ) . GetAwaiter ( ) . GetResult ( ) ;
13021299
1303- string contentType = ContentHelper . GetContentType ( response ) ;
1304- long ? contentLength = response . Content . Headers . ContentLength ;
1305- string respVerboseMsg = contentLength is null
1306- ? string . Format ( CultureInfo . CurrentCulture , WebCmdletStrings . WebResponseNoSizeVerboseMsg , response . Version , contentType )
1307- : string . Format ( CultureInfo . CurrentCulture , WebCmdletStrings . WebResponseVerboseMsg , response . Version , contentLength , contentType ) ;
1308-
1309- WriteVerbose ( respVerboseMsg ) ;
1310-
1311- string resDebugMsg = string . Format (
1312- CultureInfo . CurrentCulture ,
1313- WebCmdletStrings . WebResponseDebugMsg ,
1314- response . ToString ( ) ) ;
1300+ if ( IsWriteVerboseEnabled ( ) )
1301+ {
1302+ WriteWebResponseVerboseInfo ( response ) ;
1303+ }
13151304
1316- WriteDebug ( resDebugMsg ) ;
1305+ if ( IsWriteDebugEnabled ( ) )
1306+ {
1307+ WriteWebResponseDebugInfo ( response ) ;
1308+ }
13171309 }
13181310 catch ( TaskCanceledException ex )
13191311 {
@@ -1437,13 +1429,206 @@ internal virtual void UpdateSession(HttpResponseMessage response)
14371429 {
14381430 ArgumentNullException . ThrowIfNull ( response ) ;
14391431 }
1440-
14411432 #endregion Virtual Methods
14421433
14431434 #region Helper Methods
1444-
1435+ #nullable enable
14451436 internal static TimeSpan ConvertTimeoutSecondsToTimeSpan ( int timeout ) => timeout > 0 ? TimeSpan . FromSeconds ( timeout ) : Timeout . InfiniteTimeSpan ;
14461437
1438+ private void WriteWebRequestVerboseInfo ( HttpRequestMessage request )
1439+ {
1440+ try
1441+ {
1442+ // Typical Basic Example: 'WebRequest: v1.1 POST https://httpstat.us/200 with query length 6'
1443+ StringBuilder verboseBuilder = new ( 128 ) ;
1444+
1445+ // "Redact" the query string from verbose output, the details will be visible in Debug output
1446+ string uriWithoutQuery = request . RequestUri ? . GetLeftPart ( UriPartial . Path ) ?? string . Empty ;
1447+ verboseBuilder . Append ( $ "WebRequest: v{ request . Version } { request . Method } { uriWithoutQuery } ") ;
1448+ if ( request . RequestUri ? . Query is not null && request . RequestUri . Query . Length > 1 )
1449+ {
1450+ verboseBuilder . Append ( $ " with query length { request . RequestUri . Query . Length - 1 } ") ;
1451+ }
1452+
1453+ string ? requestContentType = ContentHelper . GetContentType ( request ) ;
1454+ if ( requestContentType is not null )
1455+ {
1456+ verboseBuilder . Append ( $ " with { requestContentType } payload") ;
1457+ }
1458+
1459+ long ? requestContentLength = request . Content ? . Headers ? . ContentLength ;
1460+ if ( requestContentLength is not null )
1461+ {
1462+ verboseBuilder . Append ( $ " with body size { ContentHelper . GetFriendlyContentLength ( requestContentLength ) } ") ;
1463+ }
1464+ if ( OutFile is not null )
1465+ {
1466+ verboseBuilder . Append ( $ " output to { QualifyFilePath ( OutFile ) } ") ;
1467+ }
1468+
1469+ WriteVerbose ( verboseBuilder . ToString ( ) . Trim ( ) ) ;
1470+ }
1471+ catch ( Exception ex )
1472+ {
1473+ // Just in case there are any edge cases we missed, we don't break workflows with an exception
1474+ WriteVerbose ( $ "Failed to Write WebRequest Verbose Info: { ex } { ex . StackTrace } ") ;
1475+ }
1476+ }
1477+
1478+ private void WriteWebRequestDebugInfo ( HttpRequestMessage request )
1479+ {
1480+ try
1481+ {
1482+ // Typical basic example:
1483+ // WebRequest Detail
1484+ // ---QUERY
1485+ // test = 5
1486+ // --- HEADERS
1487+ // User - Agent: Mozilla / 5.0, (Linux;Ubuntu 24.04.2 LTS;en - US), PowerShell / 7.6.0
1488+ StringBuilder debugBuilder = new ( "WebRequest Detail" + Environment . NewLine , 512 ) ;
1489+
1490+ if ( ! string . IsNullOrEmpty ( request . RequestUri ? . Query ) )
1491+ {
1492+ debugBuilder . Append ( DebugHeaderPrefix ) . AppendLine ( "QUERY" ) ;
1493+ string [ ] queryParams = request . RequestUri . Query . TrimStart ( '?' ) . Split ( '&' ) ;
1494+ debugBuilder
1495+ . AppendJoin ( Environment . NewLine , queryParams )
1496+ . AppendLine ( )
1497+ . AppendLine ( ) ;
1498+ }
1499+
1500+ debugBuilder . Append ( DebugHeaderPrefix ) . AppendLine ( "HEADERS" ) ;
1501+
1502+ foreach ( var headerSet in new HttpHeaders ? [ ] { request . Headers , request . Content ? . Headers } )
1503+ {
1504+ if ( headerSet is null )
1505+ {
1506+ continue ;
1507+ }
1508+
1509+ debugBuilder . AppendLine ( headerSet . ToString ( ) ) ;
1510+ }
1511+
1512+ if ( request . Content is not null )
1513+ {
1514+ debugBuilder
1515+ . Append ( DebugHeaderPrefix ) . AppendLine ( "BODY" )
1516+ . AppendLine ( request . Content switch
1517+ {
1518+ StringContent stringContent => stringContent
1519+ . ReadAsStringAsync ( _cancelToken . Token )
1520+ . GetAwaiter ( ) . GetResult ( ) ,
1521+ MultipartFormDataContent multipartContent => "=> Multipart Form Content"
1522+ + Environment . NewLine
1523+ + multipartContent . ReadAsStringAsync ( _cancelToken . Token )
1524+ . GetAwaiter ( ) . GetResult ( ) ,
1525+ ByteArrayContent byteContent => InFile is not null
1526+ ? "[Binary content: "
1527+ + ContentHelper . GetFriendlyContentLength ( byteContent . Headers . ContentLength )
1528+ + "]"
1529+ : byteContent . ReadAsStringAsync ( _cancelToken . Token ) . GetAwaiter ( ) . GetResult ( ) ,
1530+ StreamContent streamContent =>
1531+ "[Stream content: " + ContentHelper . GetFriendlyContentLength ( streamContent . Headers . ContentLength ) + "]" ,
1532+ _ => "[Unknown content type]" ,
1533+ } )
1534+ . AppendLine ( ) ;
1535+ }
1536+
1537+ WriteDebug ( debugBuilder . ToString ( ) . Trim ( ) ) ;
1538+ }
1539+ catch ( Exception ex )
1540+ {
1541+ // Just in case there are any edge cases we missed, we don't break workflows with an exception
1542+ WriteVerbose ( $ "Failed to Write WebRequest Debug Info: { ex } { ex . StackTrace } ") ;
1543+ }
1544+ }
1545+
1546+ private void WriteWebResponseVerboseInfo ( HttpResponseMessage response )
1547+ {
1548+ try
1549+ {
1550+ // Typical basic example: WebResponse: 200 OK with text/plain payload body size 6 B (6 bytes)
1551+ StringBuilder verboseBuilder = new ( 128 ) ;
1552+ verboseBuilder . Append ( $ "WebResponse: { ( int ) response . StatusCode } { response . ReasonPhrase ?? response . StatusCode . ToString ( ) } ") ;
1553+
1554+ string ? responseContentType = ContentHelper . GetContentType ( response ) ;
1555+ if ( responseContentType is not null )
1556+ {
1557+ verboseBuilder . Append ( $ " with { responseContentType } payload") ;
1558+ }
1559+
1560+ long ? responseContentLength = response . Content ? . Headers ? . ContentLength ;
1561+ if ( responseContentLength is not null )
1562+ {
1563+ verboseBuilder . Append ( $ " with body size { ContentHelper . GetFriendlyContentLength ( responseContentLength ) } ") ;
1564+ }
1565+
1566+ WriteVerbose ( verboseBuilder . ToString ( ) . Trim ( ) ) ;
1567+ }
1568+ catch ( Exception ex )
1569+ {
1570+ // Just in case there are any edge cases we missed, we don't break workflows with an exception
1571+ WriteVerbose ( $ "Failed to Write WebResponse Verbose Info: { ex } { ex . StackTrace } ") ;
1572+ }
1573+ }
1574+
1575+ private void WriteWebResponseDebugInfo ( HttpResponseMessage response )
1576+ {
1577+ try
1578+ {
1579+ // Typical basic example
1580+ // WebResponse Detail
1581+ // --- HEADERS
1582+ // Date: Fri, 09 May 2025 18:06:44 GMT
1583+ // Server: Kestrel
1584+ // Set-Cookie: ARRAffinity=ee0b467f95b53d8dcfe48aeeb4173f93cf819be6e4721f434341647f4695039d;Path=/;HttpOnly;Secure;Domain=httpstat.us, ARRAffinitySameSite=ee0b467f95b53d8dcfe48aeeb4173f93cf819be6e4721f434341647f4695039d;Path=/;HttpOnly;SameSite=None;Secure;Domain=httpstat.us
1585+ // Strict-Transport-Security: max-age=2592000
1586+ // Request-Context: appId=cid-v1:3548b0f5-7f75-492f-82bb-b6eb0e864e53
1587+ // Content-Length: 6
1588+ // Content-Type: text/plain
1589+ // --- BODY
1590+ // 200 OK
1591+ StringBuilder debugBuilder = new ( "WebResponse Detail" + Environment . NewLine , 512 ) ;
1592+
1593+ debugBuilder . Append ( DebugHeaderPrefix ) . AppendLine ( "HEADERS" ) ;
1594+
1595+ foreach ( var headerSet in new HttpHeaders ? [ ] { response . Headers , response . Content ? . Headers } )
1596+ {
1597+ if ( headerSet is null )
1598+ {
1599+ continue ;
1600+ }
1601+
1602+ debugBuilder . AppendLine ( headerSet . ToString ( ) ) ;
1603+ }
1604+
1605+ if ( response . Content is not null )
1606+ {
1607+ debugBuilder . Append ( DebugHeaderPrefix ) . AppendLine ( "BODY" ) ;
1608+
1609+ if ( ContentHelper . IsTextBasedContentType ( ContentHelper . GetContentType ( response ) ) )
1610+ {
1611+ debugBuilder . AppendLine (
1612+ response . Content . ReadAsStringAsync ( _cancelToken . Token )
1613+ . GetAwaiter ( ) . GetResult ( ) ) ;
1614+ }
1615+ else
1616+ {
1617+ string friendlyContentLength = ContentHelper . GetFriendlyContentLength (
1618+ response . Content ? . Headers ? . ContentLength ) ;
1619+ debugBuilder . AppendLine ( $ "[Binary content: { friendlyContentLength } ]") ;
1620+ }
1621+ }
1622+
1623+ WriteDebug ( debugBuilder . ToString ( ) . Trim ( ) ) ;
1624+ }
1625+ catch ( Exception ex )
1626+ {
1627+ // Just in case there are any edge cases we missed, we don't break workflows with an exception
1628+ WriteVerbose ( $ "Failed to Write WebResponse Debug Info: { ex } { ex . StackTrace } ") ;
1629+ }
1630+ }
1631+
14471632 private Uri PrepareUri ( Uri uri )
14481633 {
14491634 uri = CheckProtocol ( uri ) ;
@@ -1478,6 +1663,7 @@ private static Uri CheckProtocol(Uri uri)
14781663
14791664 return uri . IsAbsoluteUri ? uri : new Uri ( "http://" + uri . OriginalString ) ;
14801665 }
1666+ #nullable restore
14811667
14821668 private string QualifyFilePath ( string path ) => PathUtils . ResolveFilePath ( filePath : path , command : this , isLiteralPath : true ) ;
14831669
0 commit comments