Stop 'ConvertTo-Json' when 'Ctrl+c' is hit (#6392)

This commit is contained in:
Steve Lee
2018-03-19 09:13:05 -07:00
committed by Dongbo Wang
parent be109922f3
commit 876085c6f0
3 changed files with 43 additions and 267 deletions
@@ -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
/// <summary>
/// Convert the Json string to a more readable format
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
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();
}
/// <summary>
/// Convert a Json List, which starts with '['.
/// </summary>
/// <param name="json"></param>
/// <param name="index"></param>
/// <param name="result"></param>
/// <param name="padString"></param>
/// <param name="numberOfSpaces"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Convert the quoted string.
/// </summary>
/// <param name="json"></param>
/// <param name="index"></param>
/// <param name="result"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Convert a Json dictionary, which starts with '{'.
/// </summary>
/// <param name="json"></param>
/// <param name="index"></param>
/// <param name="result"></param>
/// <param name="padString"></param>
/// <param name="numberOfSpaces"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Add tabs to result
/// </summary>
/// <param name="numberOfTabsToReturn"></param>
/// <param name="result"></param>
private void AddIndentations(int numberOfTabsToReturn, StringBuilder result)
{
int realNumber = numberOfTabsToReturn * 2;
for (int i = 0; i < realNumber; i++)
{
result.Append(' ');
}
}
/// <summary>
/// Add spaces to result
/// </summary>
/// <param name="numberOfSpacesToReturn"></param>
/// <param name="result"></param>
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
/// <summary>
/// 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
/// <returns>An object suitable for serializing to JSON</returns>
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;
}
/// <summary>
/// Exception used for Stopping.
/// </summary>
private class StoppingException : System.Exception {}
}
}
@@ -177,4 +177,7 @@
<data name="NoZoneIdentifierFileStream" xml:space="preserve">
<value>The file is not blocked: {0}</value>
</data>
<data name="ConvertToJsonProcessValueVerboseMessage" xml:space="preserve">
<value>Processing object of type [{0}] at depth {1}</value>
</data>
</root>
@@ -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()
}
}