From 7fd17002c1ea77e060a0f902f5c0494dd7676894 Mon Sep 17 00:00:00 2001
From: George <1641829+finsharp@users.noreply.github.com>
Date: Tue, 30 Mar 2021 18:10:14 +0300
Subject: [PATCH 1/4] Fix exception thrown when Debug is called for Service
modules. Show full request body and response when Debug is called.
---
tools/Custom/HttpMessageFormatter.cs | 5 +----
tools/Custom/Module.cs | 18 +++++++-----------
2 files changed, 8 insertions(+), 15 deletions(-)
diff --git a/tools/Custom/HttpMessageFormatter.cs b/tools/Custom/HttpMessageFormatter.cs
index e749d1a41aa..15b3d5f1182 100644
--- a/tools/Custom/HttpMessageFormatter.cs
+++ b/tools/Custom/HttpMessageFormatter.cs
@@ -202,10 +202,7 @@ protected override async Task SerializeToStreamAsync(Stream stream, TransportCon
{
Stream readStream = await _streamTask.Value;
ValidateStreamForReading(readStream);
- if (!_contentConsumed)
- {
- await Content.CopyToAsync(stream);
- }
+ await Content.CopyToAsync(stream);
}
}
diff --git a/tools/Custom/Module.cs b/tools/Custom/Module.cs
index 1cf6dcb3091..7d7cc2058fb 100644
--- a/tools/Custom/Module.cs
+++ b/tools/Custom/Module.cs
@@ -40,7 +40,7 @@ partial void CustomInit()
{
this.EventListener = EventHandler;
}
-
+
///
/// Common Module Event Listener, allows to handle emitted by CmdLets
///
@@ -88,11 +88,9 @@ private async Task Finally(string id, CancellationToken cancellationToken, Func<
using (Extensions.NoSynchronizationContext)
{
var eventData = EventDataConverter.ConvertFrom(getEventData());
- using (var responseFormatter = new HttpMessageFormatter(eventData.ResponseMessage as HttpResponseMessage))
- {
- var responseString = await responseFormatter.ReadAsStringAsync();
- await signal(Events.Debug, cancellationToken, () => EventFactory.CreateLogEvent(responseString));
- }
+ var responseFormatter = new HttpMessageFormatter(eventData.ResponseMessage as HttpResponseMessage);
+ var responseString = await responseFormatter.ReadAsStringAsync();
+ await signal(Events.Debug, cancellationToken, () => EventFactory.CreateLogEvent(responseString));
}
}
@@ -111,11 +109,9 @@ private async Task BeforeCall(string id, CancellationToken cancellationToken, Fu
using (Extensions.NoSynchronizationContext)
{
var eventData = EventDataConverter.ConvertFrom(getEventData());
- using (var requestFormatter = new HttpMessageFormatter(eventData.RequestMessage as HttpRequestMessage))
- {
- var requestString = await requestFormatter.ReadAsStringAsync();
- await signal(Events.Debug, cancellationToken, () => EventFactory.CreateLogEvent(requestString));
- }
+ var requestFormatter = new HttpMessageFormatter(eventData.RequestMessage as HttpRequestMessage);
+ var requestString = await requestFormatter.ReadAsStringAsync();
+ await signal(Events.Debug, cancellationToken, () => EventFactory.CreateLogEvent(requestString));
}
}
}
From 68052e9e73aca7def9ecd208ca9dc70eceb2ab6f Mon Sep 17 00:00:00 2001
From: George
Date: Tue, 6 Apr 2021 05:50:49 +0300
Subject: [PATCH 2/4] Handle empty content for HTTP requests that don't return
body data.
---
tools/Custom/HttpMessageFormatter.cs | 64 ++++++++++++++++++++++++++--
tools/Custom/Module.cs | 13 +++---
2 files changed, 67 insertions(+), 10 deletions(-)
diff --git a/tools/Custom/HttpMessageFormatter.cs b/tools/Custom/HttpMessageFormatter.cs
index 15b3d5f1182..f0db0a7ffd4 100644
--- a/tools/Custom/HttpMessageFormatter.cs
+++ b/tools/Custom/HttpMessageFormatter.cs
@@ -33,6 +33,9 @@ internal class HttpMessageFormatter : HttpContent
private const string DefaultRequestMsgType = "request";
private const string DefaultResponseMsgType = "response";
+ private const string DefaultRequestMediaType = DefaultMediaType + "; " + MsgTypeParameter + "=" + DefaultRequestMsgType;
+ private const string DefaultResponseMediaType = DefaultMediaType + "; " + MsgTypeParameter + "=" + DefaultResponseMsgType;
+
// Set of header fields that only support single values such as Set-Cookie.
private static readonly HashSet _singleValueHeaderFields = new HashSet(StringComparer.OrdinalIgnoreCase)
{
@@ -101,7 +104,7 @@ private HttpContent Content
private void InitializeStreamTask()
{
- _streamTask = new Lazy>(() => Content?.ReadAsStreamAsync());
+ _streamTask = new Lazy>(() => Content == null ? null : Content.ReadAsStreamAsync());
}
///
@@ -198,7 +201,7 @@ protected override async Task SerializeToStreamAsync(Stream stream, TransportCon
byte[] header = SerializeHeader();
await stream.WriteAsync(header, 0, header.Length);
- if (Content != null)
+ if (Content != null && Content.Headers.ContentLength > 0)
{
Stream readStream = await _streamTask.Value;
ValidateStreamForReading(readStream);
@@ -227,6 +230,19 @@ protected override bool TryComputeLength(out long length)
length = 0;
// Cases #1, #2, #3
+ if (hasContent)
+ {
+ Stream readStream;
+ if (!_streamTask.Value.TryGetResult(out readStream) // Case #1
+ || readStream == null || !readStream.CanSeek) // Case #2
+ {
+ length = -1;
+ return false;
+ }
+
+ length = readStream.Length; // Case #3
+ }
+
// We serialize header to a StringBuilder so that we can determine the length
// following the pattern for HttpContent to try and determine the message length.
// The perf overhead is no larger than for the other HttpContent implementations.
@@ -235,6 +251,30 @@ protected override bool TryComputeLength(out long length)
return true;
}
+ ///
+ /// Releases unmanaged and - optionally - managed resources
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (HttpRequestMessage != null)
+ {
+ HttpRequestMessage.Dispose();
+ HttpRequestMessage = null;
+ }
+
+ if (HttpResponseMessage != null)
+ {
+ HttpResponseMessage.Dispose();
+ HttpResponseMessage = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
///
/// Serializes the HTTP request line.
///
@@ -307,8 +347,8 @@ private static void SerializeHeaderFields(StringBuilder message, HttpHeaders hea
private byte[] SerializeHeader()
{
StringBuilder message = new StringBuilder(DefaultHeaderAllocation);
- HttpHeaders headers;
- HttpContent content;
+ HttpHeaders headers = null;
+ HttpContent content = null;
if (HttpRequestMessage != null)
{
SerializeRequestLine(message, HttpRequestMessage);
@@ -351,5 +391,21 @@ private void ValidateStreamForReading(Stream stream)
_contentConsumed = true;
}
+
+ }
+
+ public static class TaskExtensions
+ {
+ public static bool TryGetResult(this Task task, out TResult result)
+ {
+ if (task.Status == TaskStatus.RanToCompletion)
+ {
+ result = task.Result;
+ return true;
+ }
+
+ result = default(TResult);
+ return false;
+ }
}
}
\ No newline at end of file
diff --git a/tools/Custom/Module.cs b/tools/Custom/Module.cs
index 7d7cc2058fb..40adec9c385 100644
--- a/tools/Custom/Module.cs
+++ b/tools/Custom/Module.cs
@@ -52,7 +52,7 @@ partial void CustomInit()
/// The cmdlet's parameterset name
/// the exception that is being thrown (if available)
///
- /// A that will be complete when handling of the event is completed.
+ /// A that will be complete when handling of the event is completed.
///
public async Task EventHandler(string id, CancellationToken cancellationToken, Func getEventData, Func, Task> signal, InvocationInfo invocationInfo, string parameterSetName, System.Exception exception)
{
@@ -81,13 +81,13 @@ public async Task EventHandler(string id, CancellationToken cancellationToken, F
/// A delegate to get the detailed event data
/// The callback for the event dispatcher
///
- /// A that will be complete when handling of the event is completed.
+ /// A that will be complete when handling of the event is completed.
///
private async Task Finally(string id, CancellationToken cancellationToken, Func getEventData, Func, Task> signal)
{
using (Extensions.NoSynchronizationContext)
{
- var eventData = EventDataConverter.ConvertFrom(getEventData());
+ var eventData = EventDataConverter.ConvertFrom(getEventData());
var responseFormatter = new HttpMessageFormatter(eventData.ResponseMessage as HttpResponseMessage);
var responseString = await responseFormatter.ReadAsStringAsync();
await signal(Events.Debug, cancellationToken, () => EventFactory.CreateLogEvent(responseString));
@@ -102,17 +102,18 @@ private async Task Finally(string id, CancellationToken cancellationToken, Func<
/// A delegate to get the detailed event data
/// The callback for the event dispatcher
///
- /// A that will be complete when handling of the event is completed.
+ /// A that will be complete when handling of the event is completed.
///
private async Task BeforeCall(string id, CancellationToken cancellationToken, Func getEventData, Func, Task> signal)
{
using (Extensions.NoSynchronizationContext)
{
- var eventData = EventDataConverter.ConvertFrom(getEventData());
+ var eventData = EventDataConverter.ConvertFrom(getEventData());
var requestFormatter = new HttpMessageFormatter(eventData.RequestMessage as HttpRequestMessage);
var requestString = await requestFormatter.ReadAsStringAsync();
await signal(Events.Debug, cancellationToken, () => EventFactory.CreateLogEvent(requestString));
}
+
}
}
-}
+}
\ No newline at end of file
From e6145f90ccb199538376f2cfa7b37d38eb2a36d9 Mon Sep 17 00:00:00 2001
From: George
Date: Tue, 6 Apr 2021 13:39:51 +0300
Subject: [PATCH 3/4] Add stream tests.
---
tools/Tests/DebugTests.ps1 | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 tools/Tests/DebugTests.ps1
diff --git a/tools/Tests/DebugTests.ps1 b/tools/Tests/DebugTests.ps1
new file mode 100644
index 00000000000..511c4da1c1c
--- /dev/null
+++ b/tools/Tests/DebugTests.ps1
@@ -0,0 +1,23 @@
+BeforeAll {
+ $ModulePrefix = "Microsoft.Graph"
+ $AuthModuleName = "Authentication"
+ $AuthModulePath = Join-Path $PSScriptRoot "..\..\src\$AuthModuleName\$AuthModuleName\artifacts\$ModulePrefix.$AuthModuleName.psd1"
+ $TestModuleName = "DirectoryObjects"
+ $TestModulePath = Join-Path $PSScriptRoot "..\..\src\$TestModuleName\$TestModuleName\$ModulePrefix.$TestModuleName.psd1"
+ Import-Module $AuthModulePath -Force
+ Import-Module $TestModulePath -Force
+
+ Connect-MgGraph
+ Select-MgProfile beta
+}
+Describe 'Cmdlets Streams' {
+ It 'Should Not Throw Exception when Debug Preference is Set'{
+ $ps = [powershell]::Create()
+$ps.AddScript(@'
+ $DebugPreference = 'Continue'
+ Test-MgDirectoryObjectProperty -DisplayName "New Name" -EntityType "Group"
+'@).Invoke()
+ $ps.Streams.Debug | Should -notLike -BeLike "*Exception*"
+ $ps.Streams.Debug -like "*HTTP/1.1 200 OK*"
+ }
+ }
\ No newline at end of file
From b4c95708b31c4eb41499824ea4068d659ca19bd0 Mon Sep 17 00:00:00 2001
From: George
Date: Tue, 13 Apr 2021 15:41:15 +0300
Subject: [PATCH 4/4] Avoid checking for -Debug to respect DebugPreferences.
---
tools/Custom/Module.cs | 27 ++++++++++++---------------
1 file changed, 12 insertions(+), 15 deletions(-)
diff --git a/tools/Custom/Module.cs b/tools/Custom/Module.cs
index 40adec9c385..ff67227ba0c 100644
--- a/tools/Custom/Module.cs
+++ b/tools/Custom/Module.cs
@@ -56,20 +56,17 @@ partial void CustomInit()
///
public async Task EventHandler(string id, CancellationToken cancellationToken, Func getEventData, Func, Task> signal, InvocationInfo invocationInfo, string parameterSetName, System.Exception exception)
{
- if (invocationInfo.BoundParameters.ContainsKey("Debug"))
+ switch (id)
{
- switch (id)
- {
- case Events.BeforeCall:
- await BeforeCall(id, cancellationToken, getEventData, signal);
- break;
- case Events.Finally:
- await Finally(id, cancellationToken, getEventData, signal);
- break;
- default:
- getEventData.Print(signal, cancellationToken, Events.Information, id);
- break;
- }
+ case Events.BeforeCall:
+ await BeforeCall(id, cancellationToken, getEventData, signal);
+ break;
+ case Events.Finally:
+ await Finally(id, cancellationToken, getEventData, signal);
+ break;
+ default:
+ getEventData.Print(signal, cancellationToken, Events.Information, id);
+ break;
}
}
@@ -87,7 +84,7 @@ private async Task Finally(string id, CancellationToken cancellationToken, Func<
{
using (Extensions.NoSynchronizationContext)
{
- var eventData = EventDataConverter.ConvertFrom(getEventData());
+ var eventData = EventDataConverter.ConvertFrom(getEventData());
var responseFormatter = new HttpMessageFormatter(eventData.ResponseMessage as HttpResponseMessage);
var responseString = await responseFormatter.ReadAsStringAsync();
await signal(Events.Debug, cancellationToken, () => EventFactory.CreateLogEvent(responseString));
@@ -108,7 +105,7 @@ private async Task BeforeCall(string id, CancellationToken cancellationToken, Fu
{
using (Extensions.NoSynchronizationContext)
{
- var eventData = EventDataConverter.ConvertFrom(getEventData());
+ var eventData = EventDataConverter.ConvertFrom(getEventData());
var requestFormatter = new HttpMessageFormatter(eventData.RequestMessage as HttpRequestMessage);
var requestString = await requestFormatter.ReadAsStringAsync();
await signal(Events.Debug, cancellationToken, () => EventFactory.CreateLogEvent(requestString));