Add -IncludeTimeStamp and -TimeStampFormat parameters to Start-Transcript by soroshsabz · Pull Request #28230 · PowerShell/PowerShell · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,7 @@
<data name="ErrorStoppingTranscript" xml:space="preserve">
<value>An error occurred stopping transcription: {0}</value>
</data>
<data name="InvalidTimeStampFormat" xml:space="preserve">
<value>"{0}" is not a valid date and time format string for the TimeStampFormat parameter. See https://learn.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings and https://learn.microsoft.com/dotnet/standard/base-types/custom-date-and-time-format-strings for the supported format strings.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,14 @@ internal override void Complete()
bufferText.Append(lineContents.ToString().TrimEnd(Utils.Separators.SpaceOrTab));
}

// Screen-scraped output is only available after the process exits, and the
// console buffer does not track when each row was written. When transcript
// timestamps are enabled (Start-Transcript -IncludeTimeStamp), all lines of
// this block are therefore recorded with the completion time of the command.
// Per-line production times can only be captured when the native command
// output is piped into a PowerShell command (e.g. 'native.exe | Out-Host'),
// which streams each line through the pipeline as it is produced. Redirecting
// native output directly to a file bypasses transcription entirely.
this.Command.Context.InternalHost.UI.TranscribeResult(bufferText.ToString());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,11 +447,11 @@ internal void TranscribeCommand(string commandText, InvocationInfo invocation)
transcript.OutputToLog.Add("**********************");
}

transcript.OutputToLog.Add(TranscriptionData.PromptText + commandText);
transcript.AddOutput(TranscriptionData.PromptText + commandText);
}
else
{
transcript.OutputToLog.Add(">> " + commandText);
transcript.AddOutput(">> " + commandText);
}
}
}
Expand Down Expand Up @@ -588,11 +588,13 @@ private void CheckSystemTranscript()
}
}

internal void StartTranscribing(string path, System.Management.Automation.Remoting.PSSenderInfo senderInfo, bool includeInvocationHeader, bool useMinimalHeader)
internal void StartTranscribing(string path, System.Management.Automation.Remoting.PSSenderInfo senderInfo, bool includeInvocationHeader, bool useMinimalHeader, bool includeTimeStamp, string timeStampFormat)
{
TranscriptionOption transcript = new TranscriptionOption();
transcript.Path = path;
transcript.IncludeInvocationHeader = includeInvocationHeader;
transcript.IncludeTimeStamp = includeTimeStamp;
transcript.TimeStampFormat = timeStampFormat;
TranscriptionData.Transcripts.Add(transcript);

LogTranscriptHeader(senderInfo, transcript, useMinimalHeader);
Expand Down Expand Up @@ -773,7 +775,7 @@ internal void TranscribeResult(Runspace sourceRunspace, string resultText)
{
lock (transcript.OutputToLog)
{
transcript.OutputToLog.Add(resultText);
transcript.AddOutput(resultText);
}
}
}
Expand Down Expand Up @@ -1239,6 +1241,51 @@ internal TranscriptionOption()
/// </summary>
internal bool IncludeInvocationHeader { get; set; }

/// <summary>
/// Whether to prefix each transcribed line of command input and
/// output with the time at which it was transcribed.
/// </summary>
internal bool IncludeTimeStamp { get; set; }

/// <summary>
/// The custom format used for the timestamp prefix. When null, the
/// default <see cref="DefaultTimeStampFormat"/> is used.
/// </summary>
internal string TimeStampFormat { get; set; }

/// <summary>
/// The default format used for the timestamp prefix, i.e. '2024-08-23 11:04:52 '.
/// </summary>
internal const string DefaultTimeStampFormat = "yyyy-MM-dd HH:mm:ss";

private static readonly string[] s_newLineSeparators = { "\r\n", "\n", "\r" };

/// <summary>
/// Adds output to be logged to this transcript. When <see cref="IncludeTimeStamp"/>
/// is set, each non-empty line of the output is prefixed with the current time.
/// </summary>
/// <param name="text">The text to be logged.</param>
internal void AddOutput(string text)
{
if (!IncludeTimeStamp || string.IsNullOrEmpty(text))
{
OutputToLog.Add(text);
return;
}

string timeStamp = DateTime.Now.ToString(TimeStampFormat ?? DefaultTimeStampFormat, CultureInfo.InvariantCulture);
string[] lines = text.Split(s_newLineSeparators, StringSplitOptions.None);
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].Length > 0)
{
lines[i] = string.Concat(timeStamp, " ", lines[i]);
}
}

OutputToLog.Add(string.Join(Environment.NewLine, lines));
}

/// <summary>
/// Logs buffered content to disk. We use this instead of File.AppendAllLines
/// so that we don't need to pay seek penalties all the time, and so that we
Expand Down