diff --git a/src/PowerShell/ErrorRecordFormatter.cs b/src/PowerShell/ErrorRecordFormatter.cs
new file mode 100644
index 00000000..b82b6e90
--- /dev/null
+++ b/src/PowerShell/ErrorRecordFormatter.cs
@@ -0,0 +1,113 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell
+{
+ using System;
+ using System.Linq;
+ using System.Management.Automation;
+ using System.Text;
+
+ internal class ErrorRecordFormatter
+ {
+ private const string TruncationPostfix = "...";
+ private const string Indent = " ";
+
+ private readonly PowerShell _pwsh = PowerShell.Create();
+
+ ///
+ /// maxSize limits the maximum size of the formatted error string (in characters).
+ /// The rest will be truncated. This value should be high enough to allow the result
+ /// contain the most important and relevant information, but low enough to create
+ /// no problems for the communication channels used to propagate this data.
+ /// The default value is somewhat arbitrary but satisfies both conditions.
+ ///
+ public string Format(ErrorRecord errorRecord, int maxSize = 1 * 1024 * 1024)
+ {
+ var errorDetails = _pwsh.AddCommand("Microsoft.PowerShell.Utility\\Out-String")
+ .AddParameter("InputObject", errorRecord)
+ .InvokeAndClearCommands();
+
+ var result = new StringBuilder(
+ capacity: Math.Min(1024, maxSize),
+ maxCapacity: maxSize);
+
+ try
+ {
+ result.Append(errorDetails.Single());
+ result.AppendLine("Script stack trace:");
+ AppendStackTrace(result, errorRecord.ScriptStackTrace, Indent);
+ result.AppendLine();
+
+ if (errorRecord.Exception != null)
+ {
+ AppendExceptionWithInners(result, errorRecord.Exception);
+ }
+
+ return result.ToString();
+ }
+ catch (ArgumentOutOfRangeException) // exceeding StringBuilder max capacity
+ {
+ return Truncate(result, maxSize);
+ }
+ }
+
+ private static void AppendExceptionWithInners(StringBuilder result, Exception exception)
+ {
+ AppendExceptionInfo(result, exception);
+
+ if (exception is AggregateException aggregateException)
+ {
+ foreach (var innerException in aggregateException.Flatten().InnerExceptions)
+ {
+ AppendInnerExceptionIfNotNull(result, innerException);
+ }
+ }
+ else
+ {
+ AppendInnerExceptionIfNotNull(result, exception.InnerException);
+ }
+ }
+
+ private static void AppendInnerExceptionIfNotNull(StringBuilder result, Exception innerException)
+ {
+ if (innerException != null)
+ {
+ result.Append("Inner exception: ");
+ AppendExceptionWithInners(result, innerException);
+ }
+ }
+
+ private static void AppendExceptionInfo(StringBuilder stringBuilder, Exception exception)
+ {
+ stringBuilder.Append(exception.GetType().FullName);
+ stringBuilder.Append(": ");
+ stringBuilder.AppendLine(exception.Message);
+
+ AppendStackTrace(stringBuilder, exception.StackTrace, string.Empty);
+ stringBuilder.AppendLine();
+ }
+
+ private static void AppendStackTrace(StringBuilder stringBuilder, string stackTrace, string indent)
+ {
+ if (stackTrace != null)
+ {
+ stringBuilder.Append(indent);
+ stringBuilder.AppendLine(stackTrace.Replace(Environment.NewLine, Environment.NewLine + indent));
+ }
+ }
+
+ private static string Truncate(StringBuilder result, int maxSize)
+ {
+ var charactersToRemove = result.Length + TruncationPostfix.Length - maxSize;
+ if (charactersToRemove > 0)
+ {
+ result.Remove(result.Length - charactersToRemove, charactersToRemove);
+ }
+
+ return result + TruncationPostfix;
+ }
+ }
+}
diff --git a/src/PowerShell/PowerShellManager.cs b/src/PowerShell/PowerShellManager.cs
index 2180be2c..e35ec1c5 100644
--- a/src/PowerShell/PowerShellManager.cs
+++ b/src/PowerShell/PowerShellManager.cs
@@ -7,12 +7,10 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.IO;
using System.Reflection;
using Microsoft.Azure.Functions.PowerShellWorker.Utility;
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
-using System.Management.Automation.Runspaces;
using LogLevel = Microsoft.Azure.WebJobs.Script.Grpc.Messages.RpcLog.Types.Level;
namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell
@@ -36,6 +34,8 @@ internal class PowerShellManager
private readonly PowerShell _pwsh;
private bool _runspaceInited;
+ private readonly ErrorRecordFormatter _errorRecordFormatter = new ErrorRecordFormatter();
+
///
/// Gets the Runspace InstanceId.
///
@@ -114,7 +114,7 @@ internal void Initialize()
///
private void RegisterStreamEvents()
{
- var streamHandler = new StreamHandler(_logger);
+ var streamHandler = new StreamHandler(_logger, _errorRecordFormatter);
_pwsh.Streams.Debug.DataAdding += streamHandler.DebugDataAdding;
_pwsh.Streams.Error.DataAdding += streamHandler.ErrorDataAdding;
_pwsh.Streams.Information.DataAdding += streamHandler.InformationDataAdding;
@@ -230,8 +230,16 @@ internal Hashtable InvokeFunction(
_pwsh.AddParameter(AzFunctionInfo.TriggerMetadata, triggerMetadata);
}
- Collection