From c136a20d4780f345c2c67812ed0ed86e6df9e54f Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 18 Jun 2026 12:56:53 -0400 Subject: [PATCH 1/5] Update CI workflow to also target servicing-* branches (#27612) --- .github/workflows/linux-ci.yml | 2 ++ .github/workflows/macos-ci.yml | 2 ++ .github/workflows/windows-ci.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 77186125a9c..3277b8a0c17 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -10,6 +10,7 @@ on: - master - release/** - github-mirror + - "servicing-*" paths: - "**" - "*" @@ -23,6 +24,7 @@ on: - master - release/** - github-mirror + - "servicing-*" - "*-feature" # Path filters for PRs need to go into the changes job diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 55d852bb68a..f89ce8caa83 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -8,6 +8,7 @@ on: - master - release/** - github-mirror + - "servicing-*" paths: - "**" - "*" @@ -21,6 +22,7 @@ on: - master - release/** - github-mirror + - "servicing-*" - "*-feature" # Path filters for PRs need to go into the changes job diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 8a57b8b9726..42347d2e12f 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -6,6 +6,7 @@ on: - master - release/** - github-mirror + - "servicing-*" paths: - "**" - "*" @@ -20,6 +21,7 @@ on: - master - release/** - github-mirror + - "servicing-*" - "*-feature" # Path filters for PRs need to go into the changes job From f7bef31a7368bc4c2efc791212fa628bd1703319 Mon Sep 17 00:00:00 2001 From: Behrad Bahrami <63627246+behradbhrmi@users.noreply.github.com> Date: Tue, 23 Jun 2026 19:41:26 +0330 Subject: [PATCH 2/5] Add links to changelogs for versions 7.5 and 7.6 (#27080) Co-authored-by: Justin Chung <124807742+jshigetomi@users.noreply.github.com> --- CHANGELOG/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG/README.md b/CHANGELOG/README.md index c20cd311ff5..2b022e75735 100644 --- a/CHANGELOG/README.md +++ b/CHANGELOG/README.md @@ -1,6 +1,8 @@ # Changelogs - [Current preview changelog](preview.md) +- [7.6 changelog](7.6.md) +- [7.5 changelog](7.5.md) - [7.4 changelog](7.4.md) - [7.3 changelog](7.3.md) - [7.2 changelog](7.2.md) From 7d596de5f56db63d2d124bdba9357c6cbc0019e6 Mon Sep 17 00:00:00 2001 From: Yoshifumi Date: Sat, 27 Jun 2026 01:15:10 +0900 Subject: [PATCH 3/5] Fix progress bar rendering with double-width unicode characters (#26185) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Justin Chung <124807742+jshigetomi@users.noreply.github.com> --- .../host/msh/ProgressNode.cs | 138 ++++++-- test/xUnit/csharp/TestProgressRawUI.cs | 56 +++ test/xUnit/csharp/test_ProgressNode.cs | 331 ++++++++++++++++++ 3 files changed, 488 insertions(+), 37 deletions(-) create mode 100644 test/xUnit/csharp/TestProgressRawUI.cs create mode 100644 test/xUnit/csharp/test_ProgressNode.cs diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs index 4f51820524c..01cbd4069c4 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs @@ -67,7 +67,7 @@ namespace Microsoft.PowerShell this.Style = IsMinimalProgressRenderingEnabled() ? RenderStyle.Ansi - : this.Style = RenderStyle.FullPlus; + : RenderStyle.FullPlus; this.SourceId = sourceId; } @@ -111,7 +111,7 @@ namespace Microsoft.PowerShell RenderMinimal(strCollection, indentation, maxWidth, rawUI); break; case RenderStyle.Ansi: - RenderAnsi(strCollection, indentation, maxWidth); + RenderAnsi(strCollection, indentation, maxWidth, rawUI); break; case RenderStyle.Invisible: // do nothing @@ -368,9 +368,12 @@ internal static bool IsMinimalProgressRenderingEnabled() /// /// The maximum number of chars that the rendering is allowed to consume. /// + /// + /// The PSHostRawUserInterface used to gauge string widths in the rendering. + /// private void - RenderAnsi(ArrayList strCollection, int indentation, int maxWidth) + RenderAnsi(ArrayList strCollection, int indentation, int maxWidth, PSHostRawUserInterface rawUI) { string indent = StringUtil.Padding(indentation); string secRemain = string.Empty; @@ -389,68 +392,129 @@ internal static bool IsMinimalProgressRenderingEnabled() // if the activity is really long, only use up to half the width string activity; - if (Activity.Length > maxWidth / 2) + int activityDisplayCellsWidth = rawUI.LengthInBufferCells(Activity); + if (activityDisplayCellsWidth > maxWidth / 2) { - activity = Activity.Substring(0, maxWidth / 2) + PSObjectHelper.Ellipsis; + activity = StringUtil.TruncateToBufferCellWidth(rawUI, Activity, (maxWidth / 2) - 1) + PSObjectHelper.Ellipsis; } else { activity = Activity; } + activityDisplayCellsWidth = rawUI.LengthInBufferCells(activity); + // 4 is for the extra space and square brackets below and one extra space - int barWidth = maxWidth - activity.Length - indentation - 4; + int barWidth = maxWidth - activityDisplayCellsWidth - indentation - 4; var sb = new StringBuilder(); - int padding = maxWidth + PSStyle.Instance.Progress.Style.Length + PSStyle.Instance.Reverse.Length + PSStyle.Instance.ReverseOff.Length; sb.Append(PSStyle.Instance.Reverse); - int maxStatusLength = barWidth - secRemainLength - 1; - if (maxStatusLength > 0 && StatusDescription.Length > barWidth - secRemainLength) + // Build the status description part + int maxStatusWidth = barWidth - secRemainLength; + string statusPart = RenderAnsiStatusPart(rawUI, maxStatusWidth, out int statusPartDisplayWidth); + + sb.Append(statusPart); + + // Calculate padding needed + int emptyPadLength = barWidth - statusPartDisplayWidth - secRemainLength; + if (emptyPadLength > 0) { - sb.Append(StatusDescription.AsSpan(0, barWidth - secRemainLength - 1)); - sb.Append(PSObjectHelper.Ellipsis); + sb.Append(' ', emptyPadLength); } - else + + sb.Append(secRemain); + + // Insert ReverseOff at the correct position for the progress bar + RenderAnsiReverseOff(sb, rawUI, statusPart, barWidth); + + strCollection.Add( + StringUtil.Format( + "{0}{1}{2} [{3}]{4}", + indent, + PSStyle.Instance.Progress.Style, + activity, + sb.ToString(), + PSStyle.Instance.Reset)); + } + + /// + /// Builds the status-description portion of the Ansi progress bar, truncating it + /// with an ellipsis when it would exceed the available status width. + /// + /// + /// The PSHostRawUserInterface used to gauge string widths. + /// + /// + /// The maximum number of buffer cells available for the status description. + /// + /// + /// On return, the width in buffer cells of the produced status part. + /// + /// The status description, possibly truncated with an ellipsis. + private string RenderAnsiStatusPart(PSHostRawUserInterface rawUI, int maxStatusWidth, out int statusPartDisplayWidth) + { + int statusDisplayWidth = rawUI.LengthInBufferCells(StatusDescription); + + if (maxStatusWidth <= 0 || statusDisplayWidth <= maxStatusWidth) { - sb.Append(StatusDescription); + statusPartDisplayWidth = statusDisplayWidth; + return StatusDescription; } - int emptyPadLength = barWidth + PSStyle.Instance.Reverse.Length - sb.Length - secRemainLength; - if (emptyPadLength > 0) + int ellipsisWidth = rawUI.LengthInBufferCells(PSObjectHelper.EllipsisStr); + string statusPart = StringUtil.TruncateToBufferCellWidth(rawUI, StatusDescription, maxStatusWidth - ellipsisWidth) + PSObjectHelper.EllipsisStr; + statusPartDisplayWidth = rawUI.LengthInBufferCells(statusPart); + return statusPart; + } + + /// + /// Appends the ReverseOff VT sequence to the rendered bar at the buffer-cell position + /// that corresponds to the filled portion of the progress bar, respecting character boundaries. + /// + /// The StringBuilder holding the bar contents built so far. + /// + /// The PSHostRawUserInterface used to gauge string widths. + /// + /// The status text at the start of the bar. + /// The total width of the progress bar in buffer cells. + private void RenderAnsiReverseOff(StringBuilder sb, PSHostRawUserInterface rawUI, string statusPart, int barWidth) + { + if (PercentComplete < 0 || PercentComplete >= 100 || barWidth <= 0) { - sb.Append(string.Empty.PadRight(emptyPadLength)); + sb.Append(PSStyle.Instance.ReverseOff); + return; } - sb.Append(secRemain); + int barLength = PercentComplete * barWidth / 100; + if (barLength >= barWidth) + { + barLength = barWidth - 1; + } + + // Calculate the string position where we need to insert ReverseOff. + // We need to find the character position that corresponds to barLength buffer cells. + int stringPos = PSStyle.Instance.Reverse.Length; + int currentCellCount = 0; - if (PercentComplete >= 0 && PercentComplete < 100 && barWidth > 0) + for (int i = 0; i < statusPart.Length && currentCellCount < barLength; i++) { - int barLength = PercentComplete * barWidth / 100; - if (barLength >= barWidth) - { - barLength = barWidth - 1; - } + currentCellCount += rawUI.LengthInBufferCells(statusPart[i]); + stringPos++; + } - if (barLength < sb.Length) - { - sb.Insert(barLength + PSStyle.Instance.Reverse.Length, PSStyle.Instance.ReverseOff); - } + // Add any padding characters. + int remainingCells = barLength - currentCellCount; + stringPos += Math.Max(0, remainingCells); + + if (stringPos < sb.Length) + { + sb.Insert(stringPos, PSStyle.Instance.ReverseOff); } else { sb.Append(PSStyle.Instance.ReverseOff); } - - strCollection.Add( - StringUtil.Format( - "{0}{1}{2} [{3}]{4}", - indent, - PSStyle.Instance.Progress.Style, - activity, - sb.ToString(), - PSStyle.Instance.Reset) - .PadRight(padding)); } /// diff --git a/test/xUnit/csharp/TestProgressRawUI.cs b/test/xUnit/csharp/TestProgressRawUI.cs new file mode 100644 index 00000000000..e357aa9cfdc --- /dev/null +++ b/test/xUnit/csharp/TestProgressRawUI.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation.Host; +using Microsoft.PowerShell; + +namespace PSTests.Parallel +{ + /// + /// Test helper class that implements PSHostRawUserInterface for progress bar testing. + /// Delegates width calculations to ConsoleControl.LengthInBufferCells. + /// + internal sealed class TestProgressRawUI : PSHostRawUserInterface + { + public override ConsoleColor ForegroundColor { get; set; } + + public override ConsoleColor BackgroundColor { get; set; } + + public override Coordinates CursorPosition { get; set; } + + public override Coordinates WindowPosition { get; set; } + + public override int CursorSize { get; set; } + + public override Size BufferSize { get; set; } + + public override Size WindowSize { get; set; } + + public override Size MaxWindowSize => new Size(120, 50); + + public override Size MaxPhysicalWindowSize => new Size(120, 50); + + public override string WindowTitle { get; set; } + + public override bool KeyAvailable => false; + + public override BufferCell[,] GetBufferContents(Rectangle rectangle) => throw new NotImplementedException(); + + public override void SetBufferContents(Rectangle rectangle, BufferCell fill) => throw new NotImplementedException(); + + public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) => throw new NotImplementedException(); + + public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill) => throw new NotImplementedException(); + + public override KeyInfo ReadKey(ReadKeyOptions options) => throw new NotImplementedException(); + + public override void FlushInputBuffer() => throw new NotImplementedException(); + + public override int LengthInBufferCells(string str) => ConsoleControl.LengthInBufferCells(str, 0, checkEscapeSequences: true); + + public override int LengthInBufferCells(string str, int offset) => ConsoleControl.LengthInBufferCells(str, offset, checkEscapeSequences: true); + + public override int LengthInBufferCells(char c) => ConsoleControl.LengthInBufferCells(c); + } +} diff --git a/test/xUnit/csharp/test_ProgressNode.cs b/test/xUnit/csharp/test_ProgressNode.cs new file mode 100644 index 00000000000..e76666d50bf --- /dev/null +++ b/test/xUnit/csharp/test_ProgressNode.cs @@ -0,0 +1,331 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using System.Management.Automation; +using Microsoft.PowerShell; +using Xunit; + +namespace PSTests.Parallel +{ + /// + /// Tests for ProgressNode rendering with double-width unicode characters. + /// These tests verify the fix for Issue #21293 where progress bars with + /// double-width characters (Japanese, Chinese, Korean, emoji) exceeded maxWidth. + /// + public static class ProgressNodeTests + { + /// + /// Verify Issue #21293 scenario - the original bug report. + /// This test reproduces the exact scenario from the issue. + /// Tests with edge cases around standard terminal width (80 columns). + /// + /// The maximum width in buffer cells for the progress bar rendering. + [Theory] + [InlineData(79)] + [InlineData(80)] + [InlineData(81)] + public static void ProgressBar_OriginalBugScenario_MustNotExceedMaxWidth(int maxWidth) + { + var rawUI = new TestProgressRawUI(); + var record = new ProgressRecord(0, "My Status", "1/6 次の段階"); + var node = new ProgressNode(1, record); + node.PercentComplete = 50; + node.SecondsRemaining = 120; + node.Style = ProgressNode.RenderStyle.Ansi; + + var strCollection = new ArrayList(); + + node.Render(strCollection, 0, maxWidth, rawUI); + + var output = strCollection[0] as string; + int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); + + Assert.True( + actualWidth <= maxWidth, + $"Issue #21293 (width {maxWidth}): Progress bar ({actualWidth} cells) exceeds maxWidth ({maxWidth}). Output: {output}"); + } + + /// + /// Verify that progress bar respects maxWidth for Japanese text. + /// + /// The maximum width in buffer cells for the progress bar rendering. + [Theory] + [InlineData(79)] + [InlineData(80)] + [InlineData(81)] + public static void ProgressBar_WithDoubleWidthChars_MustNotExceedMaxWidth(int maxWidth) + { + var rawUI = new TestProgressRawUI(); + var record = new ProgressRecord(0, "Activity", "日本語テキスト"); + var node = new ProgressNode(1, record); + node.PercentComplete = 50; + node.Style = ProgressNode.RenderStyle.Ansi; + + var strCollection = new ArrayList(); + + node.Render(strCollection, 0, maxWidth, rawUI); + + var output = strCollection[0] as string; + int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); + + Assert.True( + actualWidth <= maxWidth, + $"Progress bar width {maxWidth}: ({actualWidth}) exceeds maxWidth ({maxWidth}). Output: {output}"); + } + + /// + /// Verify emoji handling with sufficient text length. + /// + /// The maximum width in buffer cells for the progress bar rendering. + [Theory] + [InlineData(79)] + [InlineData(80)] + [InlineData(81)] + public static void ProgressBar_WithEmoji_MustNotExceedMaxWidth(int maxWidth) + { + var rawUI = new TestProgressRawUI(); + var record = new ProgressRecord(0, "Upload", "📁ファイル転送中🔄処理中📊"); + var node = new ProgressNode(1, record); + node.PercentComplete = 50; + node.Style = ProgressNode.RenderStyle.Ansi; + + var strCollection = new ArrayList(); + + node.Render(strCollection, 0, maxWidth, rawUI); + + var output = strCollection[0] as string; + int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); + + Assert.True( + actualWidth <= maxWidth, + $"Emoji progress bar width {maxWidth}: ({actualWidth} cells) exceeds maxWidth ({maxWidth}). Output: {output}"); + } + + /// + /// Test various lengths of double-width text. + /// All test cases have sufficient length to detect the bug. + /// + /// The status message displayed in the progress bar. + /// The maximum width in buffer cells for the progress bar rendering. + [Theory] + [InlineData("あいうえお", 79)] + [InlineData("あいうえお", 80)] + [InlineData("あいうえお", 81)] + [InlineData("データ処理中", 79)] + [InlineData("データ処理中", 80)] + [InlineData("データ処理中", 81)] + [InlineData("ファイルをアップロード中", 79)] + [InlineData("ファイルをアップロード中", 80)] + [InlineData("ファイルをアップロード中", 81)] + public static void ProgressBar_VariousDoubleWidthLengths_MustNotExceedMaxWidth(string statusText, int maxWidth) + { + var rawUI = new TestProgressRawUI(); + var record = new ProgressRecord(0, "Test", statusText); + var node = new ProgressNode(1, record); + node.PercentComplete = 50; + node.Style = ProgressNode.RenderStyle.Ansi; + + var strCollection = new ArrayList(); + + node.Render(strCollection, 0, maxWidth, rawUI); + + var output = strCollection[0] as string; + int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); + + Assert.True( + actualWidth <= maxWidth, + $"Status '{statusText}' width {maxWidth}: resulted in width {actualWidth} exceeding maxWidth {maxWidth}. Output: {output}"); + } + + /// + /// Verify mixed ASCII and double-width characters. + /// + /// The maximum width in buffer cells for the progress bar rendering. + [Theory] + [InlineData(79)] + [InlineData(80)] + [InlineData(81)] + public static void ProgressBar_MixedWidthText_MustNotExceedMaxWidth(int maxWidth) + { + var rawUI = new TestProgressRawUI(); + var record = new ProgressRecord(0, "Process", "File_日本語_document.txt"); + var node = new ProgressNode(1, record); + node.PercentComplete = 50; + node.Style = ProgressNode.RenderStyle.Ansi; + + var strCollection = new ArrayList(); + + node.Render(strCollection, 0, maxWidth, rawUI); + + var output = strCollection[0] as string; + int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); + + Assert.True( + actualWidth <= maxWidth, + $"Mixed-width text width {maxWidth}: resulted in width {actualWidth} exceeding maxWidth {maxWidth}. Output: {output}"); + } + + /// + /// Verify long double-width string handling and truncation. + /// + /// The maximum width in buffer cells for the progress bar rendering. + [Theory] + [InlineData(79)] + [InlineData(80)] + [InlineData(81)] + public static void ProgressBar_LongDoubleWidthString_MustNotExceedMaxWidth(int maxWidth) + { + var rawUI = new TestProgressRawUI(); + var record = new ProgressRecord(0, "処理", "ファイルを処理中です今しばらくお待ちください"); + var node = new ProgressNode(1, record); + node.PercentComplete = 50; + node.Style = ProgressNode.RenderStyle.Ansi; + + var strCollection = new ArrayList(); + + node.Render(strCollection, 0, maxWidth, rawUI); + + var output = strCollection[0] as string; + int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); + + Assert.True( + actualWidth <= maxWidth, + $"Long double-width text width {maxWidth}: resulted in width {actualWidth} exceeding maxWidth {maxWidth}. Output: {output}"); + } + + /// + /// Verify that truncation respects character boundaries. + /// + /// The maximum width in buffer cells for the progress bar rendering. + [Theory] + [InlineData(79)] + [InlineData(80)] + [InlineData(81)] + public static void ProgressBar_Truncation_MustRespectCharacterBoundaries(int maxWidth) + { + var rawUI = new TestProgressRawUI(); + var record = new ProgressRecord(0, "Downloading", "ファイル処理中123456789012345678901234567890"); + var node = new ProgressNode(1, record); + node.PercentComplete = 50; + node.Style = ProgressNode.RenderStyle.Ansi; + + var strCollection = new ArrayList(); + + node.Render(strCollection, 0, maxWidth, rawUI); + + var output = strCollection[0] as string; + int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); + + Assert.True( + actualWidth <= maxWidth, + $"Truncated progress bar width {maxWidth}: ({actualWidth} cells) exceeds maxWidth ({maxWidth}). Output: {output}"); + } + + /// + /// Test for Issue: Double closing bracket when truncating with double-width characters. + /// Verifies that statusPartDisplayWidth is calculated from actual statusPart length, + /// not from estimated value, to prevent progress bar from exceeding maxWidth. + /// + /// The maximum width in buffer cells for the progress bar rendering. + [Theory] + [InlineData(79)] + [InlineData(80)] + [InlineData(81)] + public static void ProgressBar_LongStatusWithDoubleWidth_MustNotExceedMaxWidth(int maxWidth) + { + var rawUI = new TestProgressRawUI(); + + // Very long status with emojis and mixed width characters that will be truncated + var record = new ProgressRecord(0, "Long Text Test", "🚀🎯⚡💾✅ 絵文字も含めた超長文テストケース with emojis and very long text to test the truncation feature properly"); + var node = new ProgressNode(1, record); + node.PercentComplete = 100; + node.Style = ProgressNode.RenderStyle.Ansi; + + var strCollection = new ArrayList(); + + node.Render(strCollection, 0, maxWidth, rawUI); + + var output = strCollection[0] as string; + int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); + + // The progress bar must not exceed maxWidth + Assert.True( + actualWidth <= maxWidth, + $"Progress bar with long truncated status width {maxWidth}: ({actualWidth} cells) exceeds maxWidth ({maxWidth}). Output: {output}"); + + // Verify no double closing brackets - count ']' occurrences at end + int closingBracketCount = 0; + for (int i = output.Length - 1; i >= 0 && output[i] == ']'; i--) + { + closingBracketCount++; + } + + Assert.True( + closingBracketCount <= 1, + $"Found {closingBracketCount} consecutive closing brackets at end (width {maxWidth}), expected 1. Output: {output}"); + } + + /// + /// Test activityDisplayWidth bug with small maxWidth to amplify error. + /// Bug: After truncation, activityDisplayWidth is set to maxWidth/2 but actual width differs. + /// + /// The maximum width in buffer cells for the progress bar rendering. + [Theory] + [InlineData(79)] + [InlineData(80)] + [InlineData(81)] + public static void ExtremeTest_ActivityWidthBug_SmallMaxWidth(int maxWidth) + { + var rawUI = new TestProgressRawUI(); + + string longActivity = "日本語日本語日本語日本語日本語"; // 20 cells + var record = new ProgressRecord(0, longActivity, "St"); + var node = new ProgressNode(1, record); + node.PercentComplete = 50; + node.SecondsRemaining = 30; + node.Style = ProgressNode.RenderStyle.Ansi; + + var strCollection = new ArrayList(); + + node.Render(strCollection, 0, maxWidth, rawUI); + + var output = strCollection[0] as string; + int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); + + Assert.True( + actualWidth <= maxWidth, + $"maxWidth {maxWidth} test: width {actualWidth} exceeds {maxWidth}. Output: [{output}]"); + } + + /// + /// Test surrogate pair handling with strategically positioned emojis. + /// Bug: Substring(i,1) splits surrogate pairs causing width calculation errors. + /// + /// The maximum width in buffer cells for the progress bar rendering. + [Theory] + [InlineData(79)] + [InlineData(80)] + [InlineData(81)] + public static void ExtremeTest_SurrogatePairAtVTBoundary(int maxWidth) + { + var rawUI = new TestProgressRawUI(); + + var record = new ProgressRecord(0, "A", "X🚀Y🚀Z🚀"); + var node = new ProgressNode(1, record); + node.PercentComplete = 33; + node.Style = ProgressNode.RenderStyle.Ansi; + + var strCollection = new ArrayList(); + + node.Render(strCollection, 0, maxWidth, rawUI); + + var output = strCollection[0] as string; + int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); + + Assert.True( + actualWidth <= maxWidth, + $"Surrogate pair test width {maxWidth}: width {actualWidth} exceeds {maxWidth}. Output: [{output}]"); + } + } +} From 6f7ffc2a17a3c65b934ee5e812b4f4c2e96a8055 Mon Sep 17 00:00:00 2001 From: Ahmed Taha <65625347+SufficientDaikon@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:25:35 +0300 Subject: [PATCH 4/5] Change New-Guid to generate UUID v7 by default (#27033) Co-authored-by: Claude Opus 4.6 Co-authored-by: Justin Chung <124807742+jshigetomi@users.noreply.github.com> --- .../commands/utility/NewGuidCommand.cs | 2 +- .../Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs index 0e466f86d3f..d1d41b312f7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs @@ -49,7 +49,7 @@ protected override void ProcessRecord() } else { - guid = Empty.ToBool() ? Guid.Empty : Guid.NewGuid(); + guid = Empty.ToBool() ? Guid.Empty : Guid.CreateVersion7(); } WriteObject(guid); diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 index 8756fed7726..9b082793413 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 @@ -43,6 +43,12 @@ Describe "New-Guid" -Tags "CI" { $observed | Should -Be $guids } + It "Should generate a UUID v7" { + $guid = New-Guid + # UUID v7 has version nibble '7' at position 14 in the string representation + $guid.ToString()[14] | Should -BeExactly '7' + } + It "Should return different guids with each call" { $guid1 = New-Guid $guid2 = New-Guid From 13d66be5b1ccf008e8c7b7116a4783a2ed19bf90 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Fri, 26 Jun 2026 15:26:20 -0400 Subject: [PATCH 5/5] PMC: Download deb_arm artifact to ensure package is available for PMC publish flow (#27635) --- .pipelines/templates/release-prep-for-ev2.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.pipelines/templates/release-prep-for-ev2.yml b/.pipelines/templates/release-prep-for-ev2.yml index ace2b8f7df4..ec6ea5ec1e9 100644 --- a/.pipelines/templates/release-prep-for-ev2.yml +++ b/.pipelines/templates/release-prep-for-ev2.yml @@ -16,6 +16,9 @@ stages: - input: pipelineArtifact pipeline: PSPackagesOfficial artifactName: drop_linux_package_deb + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_linux_package_deb_arm64 - input: pipelineArtifact pipeline: PSPackagesOfficial artifactName: drop_linux_package_rpm