diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs
index a51779dc18..d6431fd3ff 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs
@@ -107,7 +107,15 @@ namespace Microsoft.PowerShell.Commands
object objectToProcess = (_inputObjects.Count > 1) ? (_inputObjects.ToArray() as object) : (_inputObjects[0]);
// Pre-process the object so that it serializes the same, except that properties whose
// values cannot be evaluated are treated as having the value null.
- object preprocessedObject = ProcessValue(objectToProcess, 0);
+ object preprocessedObject = null;
+ try
+ {
+ preprocessedObject = ProcessValue(objectToProcess, 0);
+ }
+ catch (StoppingException)
+ {
+ return;
+ }
JsonSerializerSettings jsonSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None, MaxDepth = 1024 };
if (EnumsAsStrings)
{
@@ -124,272 +132,6 @@ namespace Microsoft.PowerShell.Commands
#endregion overrides
- #region convertOutputToPrettierFormat
-
- ///
- /// Convert the Json string to a more readable format
- ///
- ///
- ///
- private string ConvertToPrettyJsonString(string json)
- {
- if (!json.StartsWith("{", StringComparison.OrdinalIgnoreCase) && !json.StartsWith("[", StringComparison.OrdinalIgnoreCase))
- {
- return json;
- }
-
- StringBuilder retStr = new StringBuilder();
- if (json.StartsWith("{", StringComparison.OrdinalIgnoreCase))
- {
- retStr.Append('{');
- ConvertDictionary(json, 1, retStr, "", 0);
- }
- else if (json.StartsWith("[", StringComparison.OrdinalIgnoreCase))
- {
- retStr.Append('[');
- ConvertList(json, 1, retStr, "", 0);
- }
-
- return retStr.ToString();
- }
-
- ///
- /// Convert a Json List, which starts with '['.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- private int ConvertList(string json, int index, StringBuilder result, string padString, int numberOfSpaces)
- {
- result.Append("\r\n");
- StringBuilder newPadString = new StringBuilder();
- newPadString.Append(padString);
- AddSpaces(numberOfSpaces, newPadString);
- AddIndentations(1, newPadString);
-
- bool headChar = true;
-
- for (int i = index; i < json.Length; i++)
- {
- switch (json[i])
- {
- case '{':
- result.Append(newPadString.ToString());
- result.Append(json[i]);
- i = ConvertDictionary(json, i + 1, result, newPadString.ToString(), 0);
- headChar = false;
- break;
- case '[':
- result.Append(newPadString.ToString());
- result.Append(json[i]);
- i = ConvertList(json, i + 1, result, newPadString.ToString(), 0);
- headChar = false;
- break;
- case ']':
- result.Append("\r\n");
- result.Append(padString);
- AddSpaces(numberOfSpaces, result);
- result.Append(json[i]);
- return i;
- case '"':
- if (headChar)
- {
- result.Append(newPadString.ToString());
- }
- result.Append(json[i]);
- i = ConvertQuotedString(json, i + 1, result);
- headChar = false;
- break;
- case ',':
- result.Append(json[i]);
- result.Append("\r\n");
- headChar = true;
- break;
- default:
- if (headChar)
- {
- result.Append(newPadString.ToString());
- }
- result.Append(json[i]);
- headChar = false;
- break;
- }
- }
-
- Dbg.Diagnostics.Assert(false, "ConvertDictionary should return when encounter '}'");
- ThrowTerminatingError(NewError());
- return -1;
- }
-
- ///
- /// Convert the quoted string.
- ///
- ///
- ///
- ///
- ///
- private int ConvertQuotedString(string json, int index, StringBuilder result)
- {
- for (int i = index; i < json.Length; i++)
- {
- result.Append(json[i]);
- if (json[i] == '"')
- {
- // Ensure that the quote is not escaped by iteratively searching backwards for the backslash.
- // Examples:
- // "a \" b" --> here second quote is escaped
- // "c:\\" --> here second quote is not escaped
- //
- var j = i;
- var escaped = false;
- while (j > 0 && json[--j] == '\\')
- {
- escaped = !escaped;
- }
-
- if (!escaped)
- {
- return i;
- }
- }
- }
-
- Dbg.Diagnostics.Assert(false, "ConvertDictionary should return when encounter '}'");
- ThrowTerminatingError(NewError());
- return -1;
- }
-
- ///
- /// Convert a Json dictionary, which starts with '{'.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- private int ConvertDictionary(string json, int index, StringBuilder result, string padString, int numberOfSpaces)
- {
- result.Append("\r\n");
- StringBuilder newPadString = new StringBuilder();
- newPadString.Append(padString);
- AddSpaces(numberOfSpaces, newPadString);
- AddIndentations(1, newPadString);
-
- bool headChar = true;
- bool beforeQuote = true;
- int newSpaceCount = 0;
- const int spaceCountAfterQuoteMark = 1;
-
- for (int i = index; i < json.Length; i++)
- {
- switch (json[i])
- {
- case '{':
- result.Append(json[i]);
- i = ConvertDictionary(json, i + 1, result, newPadString.ToString(), newSpaceCount);
- headChar = false;
- break;
- case '[':
- result.Append(json[i]);
- i = ConvertList(json, i + 1, result, newPadString.ToString(), newSpaceCount);
- headChar = false;
- break;
- case '}':
- result.Append("\r\n");
- result.Append(padString);
- AddSpaces(numberOfSpaces, result);
- result.Append(json[i]);
- return i;
- case '"':
- if (headChar)
- {
- result.Append(newPadString.ToString());
- }
- result.Append(json[i]);
- int end = ConvertQuotedString(json, i + 1, result);
- if (beforeQuote)
- {
- newSpaceCount = 0;
- }
- i = end;
- headChar = false;
- break;
- case ':':
- result.Append(json[i]);
- AddSpaces(spaceCountAfterQuoteMark, result);
- headChar = false;
- beforeQuote = false;
- break;
- case ',':
- result.Append(json[i]);
- result.Append("\r\n");
- headChar = true;
- beforeQuote = true;
- newSpaceCount = 0;
- break;
- default:
- if (headChar)
- {
- result.Append(newPadString.ToString());
- }
- result.Append(json[i]);
- if (beforeQuote)
- {
- newSpaceCount += 1;
- }
- headChar = false;
- break;
- }
- }
-
- Dbg.Diagnostics.Assert(false, "ConvertDictionary should return when encounter '}'");
- ThrowTerminatingError(NewError());
- return -1;
- }
-
- ///
- /// Add tabs to result
- ///
- ///
- ///
- private void AddIndentations(int numberOfTabsToReturn, StringBuilder result)
- {
- int realNumber = numberOfTabsToReturn * 2;
- for (int i = 0; i < realNumber; i++)
- {
- result.Append(' ');
- }
- }
-
- ///
- /// Add spaces to result
- ///
- ///
- ///
- private void AddSpaces(int numberOfSpacesToReturn, StringBuilder result)
- {
- for (int i = 0; i < numberOfSpacesToReturn; i++)
- {
- result.Append(' ');
- }
- }
-
- private ErrorRecord NewError()
- {
- ErrorRecord errorRecord = new ErrorRecord(
- new InvalidOperationException(WebCmdletStrings.JsonStringInBadFormat),
- "JsonStringInBadFormat",
- ErrorCategory.InvalidOperation,
- InputObject);
- return errorRecord;
- }
-
- #endregion convertOutputToPrettierFormat
-
///
/// Return an alternate representation of the specified object that serializes the same JSON, except
/// that properties that cannot be evaluated are treated as having the value null.
@@ -401,6 +143,11 @@ namespace Microsoft.PowerShell.Commands
/// An object suitable for serializing to JSON
private object ProcessValue(object obj, int depth)
{
+ if (Stopping)
+ {
+ throw new StoppingException();
+ }
+
PSObject pso = obj as PSObject;
if (pso != null)
@@ -432,6 +179,8 @@ namespace Microsoft.PowerShell.Commands
else
{
TypeInfo t = obj.GetType().GetTypeInfo();
+ WriteVerbose(StringUtil.Format(UtilityCommonStrings.ConvertToJsonProcessValueVerboseMessage, t.Name, depth));
+
if (t.IsPrimitive)
{
@@ -683,5 +432,10 @@ namespace Microsoft.PowerShell.Commands
}
return result;
}
+
+ ///
+ /// Exception used for Stopping.
+ ///
+ private class StoppingException : System.Exception {}
}
}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx
index 651d75d1a3..ce98a18260 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx
+++ b/src/Microsoft.PowerShell.Commands.Utility/resources/UtilityCommonStrings.resx
@@ -177,4 +177,7 @@
The file is not blocked: {0}
+
+ Processing object of type [{0}] at depth {1}
+
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1
index 092a40baa2..8e9336a51f 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1
@@ -16,4 +16,23 @@ Describe 'ConvertTo-Json' -tags "CI" {
$jsonFormat | Should Match '"TestValue2": 78910'
$jsonFormat | Should Match '"TestValue3": 99999'
}
+
+ It "StopProcessing should succeed" {
+ $ps = [PowerShell]::Create()
+ $null = $ps.AddScript({
+ $obj = [PSCustomObject]@{P1 = ''; P2 = ''; P3 = ''; P4 = ''; P5 = ''; P6 = ''}
+ $obj.P1 = $obj.P2 = $obj.P3 = $obj.P4 = $obj.P5 = $obj.P6 = $obj
+ 1..100 | Foreach-Object { $obj } | ConvertTo-Json -Depth 10 -Verbose
+ # the conversion is expected to take some time, this throw is in case it doesn't
+ throw "Should not have thrown exception"
+ })
+ $null = $ps.BeginInvoke()
+ # wait for verbose message from ConvertTo-Json to ensure cmdlet is processing
+ Wait-UntilTrue { $ps.Streams.Verbose.Count -gt 0 }
+ $null = $ps.BeginStop($null, $null)
+ # wait a bit to ensure state has changed, not using synchronous Stop() to avoid blocking Pester
+ Start-Sleep -Milliseconds 100
+ $ps.InvocationStateInfo.State | Should -BeExactly "Stopped"
+ $ps.Dispose()
+ }
}