@@ -93,6 +93,11 @@ public abstract class WebRequestPSCmdlet : PSCmdlet, IDisposable
93
93
{
94
94
#region Fields
95
95
96
+ /// <summary>
97
+ /// Used to prefix the headers in debug and verbose messaging.
98
+ /// </summary>
99
+ internal const string DebugHeaderPrefix = "--- " ;
100
+
96
101
/// <summary>
97
102
/// Cancellation token source.
98
103
/// </summary>
@@ -1280,40 +1285,27 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM
1280
1285
_cancelToken = new CancellationTokenSource ( ) ;
1281
1286
try
1282
1287
{
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
+ }
1298
1292
1299
- WriteDebug ( reqDebugMsg ) ;
1293
+ if ( IsWriteDebugEnabled ( ) )
1294
+ {
1295
+ WriteWebRequestDebugInfo ( currentRequest ) ;
1296
+ }
1300
1297
1301
1298
response = client . SendAsync ( currentRequest , HttpCompletionOption . ResponseHeadersRead , _cancelToken . Token ) . GetAwaiter ( ) . GetResult ( ) ;
1302
1299
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
+ }
1315
1304
1316
- WriteDebug ( resDebugMsg ) ;
1305
+ if ( IsWriteDebugEnabled ( ) )
1306
+ {
1307
+ WriteWebResponseDebugInfo ( response ) ;
1308
+ }
1317
1309
}
1318
1310
catch ( TaskCanceledException ex )
1319
1311
{
@@ -1437,13 +1429,206 @@ internal virtual void UpdateSession(HttpResponseMessage response)
1437
1429
{
1438
1430
ArgumentNullException . ThrowIfNull ( response ) ;
1439
1431
}
1440
-
1441
1432
#endregion Virtual Methods
1442
1433
1443
1434
#region Helper Methods
1444
-
1435
+ #nullable enable
1445
1436
internal static TimeSpan ConvertTimeoutSecondsToTimeSpan ( int timeout ) => timeout > 0 ? TimeSpan . FromSeconds ( timeout ) : Timeout . InfiniteTimeSpan ;
1446
1437
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
+
1447
1632
private Uri PrepareUri ( Uri uri )
1448
1633
{
1449
1634
uri = CheckProtocol ( uri ) ;
@@ -1478,6 +1663,7 @@ private static Uri CheckProtocol(Uri uri)
1478
1663
1479
1664
return uri . IsAbsoluteUri ? uri : new Uri ( "http://" + uri . OriginalString ) ;
1480
1665
}
1666
+ #nullable restore
1481
1667
1482
1668
private string QualifyFilePath ( string path ) => PathUtils . ResolveFilePath ( filePath : path , command : this , isLiteralPath : true ) ;
1483
1669
0 commit comments